From d2352cc51487b86c385b6d40f75b5d89748c1351 Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Mon, 20 Oct 2025 12:48:19 +0800 Subject: [PATCH 001/104] =?UTF-8?q?1020=5FYB=E5=A5=94=E8=80=80=E4=BB=BF?= =?UTF-8?q?=E7=9C=9F=E6=9C=BA=E5=90=8C=E6=AD=A5=E5=AF=B9=E9=BD=90dev=5Funi?= =?UTF-8?q?lab=E5=8F=AF=E6=8E=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 待修改unilab的http服务 --- ..._cellconfig4.json => bioyond_yihua_YB.json | 18 +- .../bioyond_cell/2025092701.xlsx | Bin 0 -> 9287 bytes .../bioyond_cell/bioyond_cell_workstation.py | 681 + .../bioyond_material_management.py | 374 + .../bioyond_cell/bioyond_workstation copy.py | 715 + .../bioyond_cell/bioyond_yihua_YB.json | 14492 ++++++++++++++++ .../bioyond_cell/样品导入模板.xlsx | Bin 0 -> 22007 bytes .../coin_cell_assembly/__init__.py | 0 .../button_battery_station.py | 1289 -- .../coin_cell_assembly/coin_cell_assembly.py | 1142 -- unilabos/registry/devices/bioyond.yaml | 3 + unilabos/registry/devices/bioyond_cell.yaml | 834 + .../devices/characterization_chromatic.yaml | 228 - .../devices/characterization_optic.yaml | 195 +- .../devices/reaction_station_bioyond.yaml | 31 +- .../registry/devices/robot_linear_motion.yaml | 171 - unilabos/registry/devices/work_station.yaml | 31 +- unilabos/registry/resources/bioyond/deck.yaml | 12 + unilabos/resources/bioyond/decks.py | 60 +- unilabos/resources/bioyond/warehouses.py | 116 +- 20 files changed, 17306 insertions(+), 3086 deletions(-) rename unilabos/devices/workstation/coin_cell_assembly/new_cellconfig4.json => bioyond_yihua_YB.json (99%) create mode 100644 unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx create mode 100644 unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py create mode 100644 unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_material_management.py create mode 100644 unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_workstation copy.py create mode 100644 unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json create mode 100644 unilabos/devices/workstation/bioyond_studio/bioyond_cell/样品导入模板.xlsx delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/__init__.py delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py create mode 100644 unilabos/registry/devices/bioyond_cell.yaml diff --git a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig4.json b/bioyond_yihua_YB.json similarity index 99% rename from unilabos/devices/workstation/coin_cell_assembly/new_cellconfig4.json rename to bioyond_yihua_YB.json index 7e37132..1cd7f00 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig4.json +++ b/bioyond_yihua_YB.json @@ -1,8 +1,22 @@ { "nodes": [ + { + "id": "bioyond_cell_workstation", + "name": "配液分液工站", + "children": [ + ], + "parent": null, + "type": "device", + "class": "bioyond_cell", + "config": { + "protocol_type": [], + "station_resource": {} + }, + "data": {} + }, { "id": "BatteryStation", - "name": "扣电工作站", + "name": "扣电组装工作站", "children": [ "coin_cell_deck" ], @@ -15,7 +29,7 @@ "z": 0 }, "config": { - "debug_mode": false, + "debug_mode": true, "_comment": "protocol_type接外部工站固定写法字段,一般为空,deck写法也固定", "protocol_type": [], "deck": { diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..3ce894f1e5033c9a9bbfc0b61024bce1ce3fbf49 GIT binary patch literal 9287 zcmeHtg;yNe_I2Z~jXMOF-~k%f1b2eFGz1IonxG*#1P$&D!3pk8fDj-!1lPuc1%5r5 znQvw?@B0hhyS@6})vIcsRb6%V+2@{9YKjPm1OQ|JDgXeW1(+XYfsNq+fH*_|00=;Z zH;{I6bhC1FGtu&PwsJLk?B!rjlZOb;oCAP|o&Ue_Uwj8Dl15Z|xNv2^Kf9CO;80ks zkwE1?3>m;@RTt~*P3$kTFvzyGeaMcy#g)n>v=gYtA7Aw4IT-`jI65?hMD(|4U`7WI zc4`}w3vl)g958eclP0+6>p^n}$R)T*O-y2}GXU>iHuh-n$*f5#RFHuQiNu1|v*rhM z@K*XiVwUNb2n{ZDt*YoO;|UIzPT{Uk0>9YVviKUGt#imDM$`E&5_uRGs?q9dvY2ap zbh$}YYa=ZS=X? zP4g2%unC7jwnQ*($mg#BwHMKKL=YawasOU%!hEHX<8GRzTZT0tMeho^Wpj3HoTUm0 zodf%7;Xt;Gs*nI{NI>!6P?KP%>Y)XNpJUM^$_AqB)dRsL-fr_*rxfl`CoR2y_~Gi{ zuCVXH#HK;$(Mif~AkRHA0PygD08sk}E$g+p9-YH{O$o+23>Ymd1o_wi;i2_%PEEM5s zU|Yc5@WPTv%-$gF+t{Ncx; zk~yeB$A^E#GyZgdA?G4czmD%@B+I*30IjEd@OUUBn!02ercnPTiT0KXE-nH9-~yu% z4(!f&*>ie2xx6rUa(eNbZWU-ts(z6mXwSH!PXCzd(L7SaM*xq(jK++YJi=(%kW~nr zPXqBsad0<{}dz4-gGT7D&uPJE|B2p_IZw;6xDyu0cVM`8B?SDDs4gybpcA0f+>rFh@8&G@4eMQob>*bd8ivuZ6yRo>b?M z0<-10Tn`OIicO3jngCw6Srp+Dowi16Gf|kO)ANWfDAGWy!OuIF>g9`p=Y!Ln%$26L z%pTihhpQ5ZY(v{ndOzf?t)CO1Z@v_hrVrg;WQSKCWlUF4;d4N(lnnV1SIOE4--3sM z+vpxHHkej0p-IDm3S>v~|Ey6iGobT05x{@9HSBVRJSA|Zb4F!z$zwa$IGZkHgJz31 z{YCsMX5wm6b;SKoGTB?KpEN}!E(>* z>sI}314#Gk`jaMHq-#7h7Z13 zjzMN^_LD_X@<#O|F-@1 zjmMXdZlhoJDtW0QRf6p85j%KA4hcTF6YY}JZBL8nY6P^%&sk%>d1}So?#Q@vW#}O#<->fS z$LZ!2biIdqW%(jo{QBb9AG)dzohzRzrVa=Y-=jQA#aJ~leGAQ#8BAz5V@{KW`vGfZK~0H|;<^879`f2Yu&f&&N3|6mmS z?;aHz%8I>QxNR8s;ar{>9)x&vZk%)n+WR=DLv<_*jI_J~=PTr_%|^O2ikt{eVcyWO z0gvze*xwL<7hP;6QTT{HKzpb#lGE7P7y_#KA%rUADH0I>U~j+t5Cc2i75GssVURtW zKyd4dnSnGszlcue{Nv{^KD$p`?mTv`6O_ZSfICvR5p?rP^8gZ3*Vsst&SJg!@KJi< z68U|IH}&KhDVn?pmN#H>=L;>{RhR4~>S^npwX*l87hU-`n41*kid1F%qJCj+G~t6! zsRLOOTsQO5TC|~SZc?|yEFBA{>?7w;>y4p-=@rqx!s4v~1^_?;JL8Xd z>k78Aa&zVUxp4m$crz1?oEB&Z!k|ot;xtDV_5v3=Y%0?nWiu_dOEWJ*Vl4=`s8;>J z^MNaH+xZ$kc+)j-2;1J{+*LN;g&~8;6^TXV;FN+S2__|u7H!$UAAXsBMZMn3(}ro- ztj#D-q1)8rqYeF)Mi{GT_@L*LGq>%Qqme{>o{_t`J<0vLW1{WzgsyE!#sf4E-RvTy zDMJr8`lL&;Fk3PfQ?uTj+_Oi$A>XLZJRt{5@kM?7f@6n1J51d>RCtP|1uIXP8T6A> z%RlM1Yw3P}oZ%wrK?J3EpOLSW*`fAXLw8+i9Y|lxy*+XY{Ytt1O{TfxfmP{=710wO zidE9&4$i`Z%iiJf4;h}_Q9yRT#|GFnl{+iv&dMLgr4q9<*5}1R@2Bxq1+`?^`3U_7 zhr>CAtj_fKCY;a=zS}8@n23eKuffUkD1?Zo_E+@uGi55^?S2aj+UOp^@v^cPJ27K9kR$kViZ0(*a+jHfpq&?G)HEG6#7kVbwL9vA$Z-JRD|E4RzSN9=_ppTDZ8QW6*?Duf-(*ZK4 zM^r-92wtx}rxgiGlAfHWLF0h5fuY|?B@^D3u@vkr_`Xq|dx3S?NqW2~aU~=yvsukt z##de=2U!aECvPB@{Hzq8=L(nrduWm*3Xxa*>ko%=*h}fkP2>C7E|`V14;v^rT1z* z@nIGcH;zDuO|71^Fjp*Mvd8>}woNn6`D;2oSz*3dIAn}y%&M67pK_Kl0(sY_#dD~C z-oBrc;oC9vZZ6zcCJYA%x|(>B=~8S|BB&wl5-%DZCal-3fB5=p=Awum`XTVvra^~t zzn%qaE2?95>v0A45tuXMD>~3Hy2P@8>^G}{1=qWmp3*fdB z?*!6ZX6x243_PJjNnvMj_}2R&nLtRR<;!D>BNs`H26wgui(q)!sk~J9whOg7tT;eh zK}Wws)DxsrVi5iF@B=Qwd&w<^M^M`&GL4HRMJFT z5{;$PQ6)vs_{L1LWiSLHnA#D|9(o8b`3#K%oJm58bW&Ky<(vwavyUr+4mR1LDiFOF zO;_j-g`{cE7Y;pK>*M-(_-TCS-gQ-dNg*eX#$FC$Zx;%6suKWZS)n*d5Os)zLXGr{ z9Gkol6iCJ{ttaMl+l;%~USSHgti7%R_!a7O7BfeL@id>~OJ|pQSEqbKF+2z41Y@$L z)AD~B!VSmmqs+uc?DK(Zm2=*V@h2Egd+NhMFD>ahjV?CIZP}Z&Qog;?;d4*W=5xQX zvsRlQE>0=x<92@hbYz6KHMq7(;p%i@{QmC7;(TDr$yn^ZJ2qAOu1ehRbY%SfZE&2V zW~8y-^+uzOs zCKBzJ!X(h!nWNRwmkypu($krUg%-CeQWV^cq@M`h%F7XahF($8QXL*??tOKS4hvBf z)7lY}bx>+!+?@&rKGV!2=R@jv;|~fgr(CdKl6Lsyr|*>DPmG2Zoc8oe%afRKnfi8m>t>*w;#kXdS;9`qz!96({1?n+MxEr0!0HAc+X|sQHj3zM)!P41D z>uaGZI)Q6rrY_r~gzD&UF=E@rM0qu9z*((K^e+nh7jd* zJb{U1&BKmTEfPGEH|FVF-5G|ARw1homjjUTbWEXoGZG6M=v1DSNHDNVEmslv4 zt+JeaMOg}rZ$velHJ&~#A!LLKBuadF(JhzF^^Q!hc&XanT#vs|Hw;_9%g(i$GzWJZ zVv3sh*sw@QPv=oE=m+bUt#`z+$s+;^W>~P{I@z#06^6Q4r>Labp{lUhB zmF8(_BQbZ;V`1100{KB$Uof6k*TK`CHp;_!xEU#iN#mE5)v~FMR=X2wLaAs zx+xW=mULxwm7|ca2sNs@CbOMZMxh8v5+xaS8cA@W!c>7r{_&Eby5E@}zV!Doo_TwE zwtZZ{9{T*gIvK1L6=21td(p_tBtW8x_o)VMWY?v^d<@+BHM8YxM+Y)LzTu4h>YV!P zLkViy593^cFJddf^HHm3p@dYDS|*>hvFM%=Kl9}eY9A1*>nhNjBaGTQ=kSo+Y>q1= zw$&Q9J*bIvNp*EO*=XPL>wnCC@U@xjDV1iZO>A+AV=SX<#LG9=6WTwB`cQB>Co`P) z#LX}FlSbD*ZAi9rD7j&I@jZT=R!3eL0}}1|%4ijii5G*-bT&{enSb?F^U0H-Cy($E z_2rv3$jJ<)DBmHKE^#P+>zj&HM6cyZI!O3RQF2Yv`Mp}oQgl8o*`3#>e&#t)vHHMW z$pd*!Z;Ln0^Ao6i=pe@{FaNPAkGogR5V~UUi8TUFyE8gwPw5Y1q>Q&5tc;ek53v2w zzf)n$zJ;&}*pZJgg~a+@9bMhL?X6sYFFcGmPDv0fJvzb3pKA%A$9`$qLZ{534UbkT zYc5ebMIgl$^7hbiJ8^ zwV((ZS6+=4VPZ69M+a*ww_HEI9$qE=*l`${UwVgMJXy=yxr=h~Ojna*W*yOrLxd6TNw(Wog=Mp)QgzF$;2ObfCJs8dg|EVncDknnA;jtS z-ONX##|Givpy$mYaPx&rR!y3lEXDOmm+2fSM=z$)1Gb0t!8IZf5C4q(iJB6mC6QJ-!7~Ue=naYf`Txn8(6OyKTN3 z^Q=9djg5f;ey@?2q;js)Uyf{uqas*OQ)u8eq3a5)Ik;RJ(*Ow}L+K0UKdi!VP7U;{ z>z`ffK)=S+aL9z=xY$fLP&gcrBRF81)XlK>ydN*FN|^J;^IkUvU4|i$`O1rAd7m8H zgE9!^agl6He0;Y^4T_ASz0uEvxhXv2myuhrPEK=1oT114{QAOeViQNMs(I?iAZ&yx z62x2qiuh^)DlGG=h#v40GL`Xlx}%q|@0()a#W7pjwgv})BZ!+yM|Sn)Y9<)6A?pZ| zO=|NBSK$wtcbxw*G0gY3IQp#~2ZnWw2>x(=u$haMrKX#Ut)tCP2E0m=beiI#=z~7< z3m+_gse4#xH=>qc%NJQv=N^yW^b|5HXP1fF60szxR%a_sA-4;MC{Sb4oYeg6m{<>+iibYu z8totL(`2)cI+VA?H#ZXfzQgJB3XiZMla@JJ$t13Gm6hPJLr>`YIJ+wG@mHhkCjv$#o~^Q>os%G{cLLu=Snced!mOHLO~Dz z!so+ngzqjt`Acw#0&|WS1$#UUXgoc%43G%Q(K?^vF4{|TQ+bU&Nt&}W09-a0q4ZDm za}2%+iG(;X*I>vYK%NNBxM}6h3^vcxSVFww_H`Xh>ycNM&(F03?qnecOmV<1TI5H55eO&!})E2jr8MA|iDJoUjTa+5|aKi1;6QXfmR%v}$DvygotsPoZ$U z-YS(5<_}QV7Y|lQvUIXgb8&KZ<+N~evHGKb_`muQj41Z;iZ5XOvscj9_bB--9CPv} z-5xm{LJVppiqcL}^MZLP?DU0iAN;MD+@}WAM-Lin^Zj0`DWAj54CxRvE2Bn9oasC> zBHk&~?{8#w$+qoE*O3nPaR#d!8*ePCE$e&LK;ZTnnwaD2@)rrf+ikfd!tDrshWHeq zh=m!}t)8iwuUz+^>^ezlXLW;ZLcGIJOzj!zK(t8ju!aYaZQX&i zJ|@!~-MTz-?}*jm83?R5Jm#j$w~J0Mgb5#yZCG^`t!8u|=`&>FC(|=}=5bp;n48 zHWE2(E%+ggCpa?h=E9y2chp;8|zV;y4eBpzD3gdG)HCL00o>S41d%6AQlT zEK_uCk?#XDZ11ZwNs8l2{ zKa;d8=}fl}BBoV8TESa)72xLK<8`w3&a`T_t;}9OqrOQoe|R-o)`C>c)ypjY15+mA zE&U{|8Yq4`(eCSc*iF3;fb0e|a(Dob3lNECxmya#tx0joL**%n`i6hP}%B-`@4(c=qX$7IS7`3<@P9_tzr){C{SE#{j8#)af<0XT Q03G%Tg6(;#X@C3mf3EfhZ2$lO literal 0 HcmV?d00001 diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py new file mode 100644 index 0000000..7119b1b --- /dev/null +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -0,0 +1,681 @@ +# -*- coding: utf-8 -*- +from cgi import print_arguments +from doctest import debug +from typing import Dict, Any, List, Optional +import requests +from pathlib import Path +import pandas as pd +import time +from datetime import datetime, timedelta +import re +import threading + +from urllib3 import response +from unilabos.devices.workstation.workstation_base import WorkstationBase +from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService +from unilabos.utils.log import logger +from pylabrobot.resources.deck import Deck + +def _iso_local_now_ms() -> str: + # 文档要求:到毫秒 + Z,例如 2025-08-15T05:43:22.814Z + dt = datetime.now() + # print(dt) + return dt.strftime("%Y-%m-%dT%H:%M:%S.") + f"{int(dt.microsecond/1000):03d}Z" + + +class BioyondCellWorkstation(WorkstationBase): + """ + 集成 Bioyond LIMS 的工作站示例, + 覆盖:入库(2.17/2.18) → 新建实验(2.14) → 启动调度(2.7) → + 运行中推送:物料变更(2.24)、步骤完成(2.21)、订单完成(2.23) → + 查询实验(2.5/2.6) → 3-2-1 转运(2.32) → 样品/废料取出(2.28) + """ + + def __init__( + self, + bioyond_config: Optional[Dict[str, Any]] = None, + station_resource: Optional[Dict[str, Any]] = None, + *args, **kwargs, + ): + + self.bioyond_config = bioyond_config or { + "base_url": "http://172.16.11.219:44388", + "api_key": "8A819E5C", + "timeout": 30, + "report_token": "CHANGE_ME_TOKEN", + "HTTP_host": "192.168.1.104", + "HTTP_port": 8080, + "debug_mode": False + } # report_token :unilab自己的令牌report_token(0928未启用) + self.debug_mode = self.bioyond_config["debug_mode"] + self.http_service_started = False + super().__init__(station_resource=station_resource, *args, **kwargs) + logger.info(f"Bioyond工作站初始化完成 (debug_mode={self.debug_mode})") + + # 实例化并在后台线程启动 HTTP 报送服务 + self.order_status = {} # 记录任务完成情况,用于接受bioyond post信息和反馈信息,尤其用于硬件查询和物料信息变化 + try: + logger.info("准备开始unilab_HTTP后台线程") + t = threading.Thread(target=self._start_http_service, daemon=True, name="unilab_http") + t.start() + except Exception as e: + logger.error(f"unilab-HTTP后台线程启动失败: {e}") + + def _start_http_service(self, host: Optional[str] = None, port: Optional[int] = None) -> None: + host = host or self.bioyond_config.get("HTTP_host", "") + port = port or self.bioyond_config.get("HTTP_port", ) + + logger.info("准备开始unilab_HTTP服务") + try: + self.service = WorkstationHTTPService(self, host=host, port=port) + logger.info("WorkstationHTTPService 实例化完成") + self.service.start() + self.http_service_started = True + logger.info(f"WorkstationHTTPService成功启动: {host}:{port}") + #一直挂着,直到进程退出 + while True: + time.sleep(1) + except Exception as e: + self.http_service_started = False # 调试用 + logger.error(f"启动WorkstationHTTPService失败: {e}", exc_info=True) + + # -------------------- 基础HTTP封装 -------------------- + def _url(self, path: str) -> str: + + return f"{self.bioyond_config['base_url'].rstrip('/')}/{path.lstrip('/')}" + + def _post_lims(self, path: str, data: Optional[Any] = None) -> Dict[str, Any]: + """LIMS API:大多数接口用 {apiKey/requestTime,data} 包装""" + payload = { + "apiKey": self.bioyond_config["api_key"], + "requestTime": _iso_local_now_ms() + } + if data is not None: + payload["data"] = data + + if self.debug_mode: + # 模拟返回,不发真实请求 + logger.info(f"[DEBUG] POST {path} with payload={payload}") + return {"debug": True, "url": self._url(path), "payload": payload, "status": "ok"} + + try: + response = requests.post( + self._url(path), + json=payload, + timeout=self.bioyond_config.get("timeout", 30), + headers={"Content-Type": "application/json"} + ) # 拼接网址+post bioyond接口 + response.raise_for_status() + return response.json() + except Exception as e: + logger.info(f"{self.bioyond_config['base_url'].rstrip('/')}/{path.lstrip('/')}") + logger.error(f"POST {path} 失败: {e}") + return {"error": str(e)} + + # -------------------- 单点接口封装 -------------------- + # 2.17 入库物料(单个) + def storage_inbound(self, material_id: str, location_id: str) -> Dict[str, Any]: + return self._post_lims("/api/lims/storage/inbound", { + "materialId": material_id, + "locationId": location_id + }) + + # 2.18 批量入库(多个) + def storage_batch_inbound(self, items: List[Dict[str, str]]) -> Dict[str, Any]: + """ + items = [{"materialId": "...", "locationId": "..."}, ...] + """ + return self._post_lims("/api/lims/storage/batch-inbound", items) + + + + def auto_feeding4to3( + self, + # ★ 修改点:默认模板路径 + xlsx_path: Optional[str] = "unilabos/devices/workstation/bioyond_cell/样品导入模板.xlsx", + + # ---------------- WH4 - 加样头面 (Z=1, 12个点位) ---------------- + WH4_x1_y1_z1_1_materialName: str = "", WH4_x1_y1_z1_1_quantity: float = 0.0, + WH4_x2_y1_z1_2_materialName: str = "", WH4_x2_y1_z1_2_quantity: float = 0.0, + WH4_x3_y1_z1_3_materialName: str = "", WH4_x3_y1_z1_3_quantity: float = 0.0, + WH4_x4_y1_z1_4_materialName: str = "", WH4_x4_y1_z1_4_quantity: float = 0.0, + WH4_x5_y1_z1_5_materialName: str = "", WH4_x5_y1_z1_5_quantity: float = 0.0, + WH4_x1_y2_z1_6_materialName: str = "", WH4_x1_y2_z1_6_quantity: float = 0.0, + WH4_x2_y2_z1_7_materialName: str = "", WH4_x2_y2_z1_7_quantity: float = 0.0, + WH4_x3_y2_z1_8_materialName: str = "", WH4_x3_y2_z1_8_quantity: float = 0.0, + WH4_x4_y2_z1_9_materialName: str = "", WH4_x4_y2_z1_9_quantity: float = 0.0, + WH4_x5_y2_z1_10_materialName: str = "", WH4_x5_y2_z1_10_quantity: float = 0.0, + WH4_x1_y3_z1_11_materialName: str = "", WH4_x1_y3_z1_11_quantity: float = 0.0, + WH4_x2_y3_z1_12_materialName: str = "", WH4_x2_y3_z1_12_quantity: float = 0.0, + + # ---------------- WH4 - 原液瓶面 (Z=2, 9个点位) ---------------- + WH4_x1_y1_z2_1_materialName: str = "", WH4_x1_y1_z2_1_quantity: float = 0.0, WH4_x1_y1_z2_1_materialType: str = "", WH4_x1_y1_z2_1_targetWH: str = "", + WH4_x2_y1_z2_2_materialName: str = "", WH4_x2_y1_z2_2_quantity: float = 0.0, WH4_x2_y1_z2_2_materialType: str = "", WH4_x2_y1_z2_2_targetWH: str = "", + WH4_x3_y1_z2_3_materialName: str = "", WH4_x3_y1_z2_3_quantity: float = 0.0, WH4_x3_y1_z2_3_materialType: str = "", WH4_x3_y1_z2_3_targetWH: str = "", + WH4_x1_y2_z2_4_materialName: str = "", WH4_x1_y2_z2_4_quantity: float = 0.0, WH4_x1_y2_z2_4_materialType: str = "", WH4_x1_y2_z2_4_targetWH: str = "", + WH4_x2_y2_z2_5_materialName: str = "", WH4_x2_y2_z2_5_quantity: float = 0.0, WH4_x2_y2_z2_5_materialType: str = "", WH4_x2_y2_z2_5_targetWH: str = "", + WH4_x3_y2_z2_6_materialName: str = "", WH4_x3_y2_z2_6_quantity: float = 0.0, WH4_x3_y2_z2_6_materialType: str = "", WH4_x3_y2_z2_6_targetWH: str = "", + WH4_x1_y3_z2_7_materialName: str = "", WH4_x1_y3_z2_7_quantity: float = 0.0, WH4_x1_y3_z2_7_materialType: str = "", WH4_x1_y3_z2_7_targetWH: str = "", + WH4_x2_y3_z2_8_materialName: str = "", WH4_x2_y3_z2_8_quantity: float = 0.0, WH4_x2_y3_z2_8_materialType: str = "", WH4_x2_y3_z2_8_targetWH: str = "", + WH4_x3_y3_z2_9_materialName: str = "", WH4_x3_y3_z2_9_quantity: float = 0.0, WH4_x3_y3_z2_9_materialType: str = "", WH4_x3_y3_z2_9_targetWH: str = "", + + # ---------------- WH3 - 人工堆栈 (Z=3, 15个点位) ---------------- + WH3_x1_y1_z3_1_materialType: str = "", WH3_x1_y1_z3_1_materialId: str = "", WH3_x1_y1_z3_1_quantity: float = 0, + WH3_x2_y1_z3_2_materialType: str = "", WH3_x2_y1_z3_2_materialId: str = "", WH3_x2_y1_z3_2_quantity: float = 0, + WH3_x3_y1_z3_3_materialType: str = "", WH3_x3_y1_z3_3_materialId: str = "", WH3_x3_y1_z3_3_quantity: float = 0, + WH3_x1_y2_z3_4_materialType: str = "", WH3_x1_y2_z3_4_materialId: str = "", WH3_x1_y2_z3_4_quantity: float = 0, + WH3_x2_y2_z3_5_materialType: str = "", WH3_x2_y2_z3_5_materialId: str = "", WH3_x2_y2_z3_5_quantity: float = 0, + WH3_x3_y2_z3_6_materialType: str = "", WH3_x3_y2_z3_6_materialId: str = "", WH3_x3_y2_z3_6_quantity: float = 0, + WH3_x1_y3_z3_7_materialType: str = "", WH3_x1_y3_z3_7_materialId: str = "", WH3_x1_y3_z3_7_quantity: float = 0, + WH3_x2_y3_z3_8_materialType: str = "", WH3_x2_y3_z3_8_materialId: str = "", WH3_x2_y3_z3_8_quantity: float = 0, + WH3_x3_y3_z3_9_materialType: str = "", WH3_x3_y3_z3_9_materialId: str = "", WH3_x3_y3_z3_9_quantity: float = 0, + WH3_x1_y4_z3_10_materialType: str = "", WH3_x1_y4_z3_10_materialId: str = "", WH3_x1_y4_z3_10_quantity: float = 0, + WH3_x2_y4_z3_11_materialType: str = "", WH3_x2_y4_z3_11_materialId: str = "", WH3_x2_y4_z3_11_quantity: float = 0, + WH3_x3_y4_z3_12_materialType: str = "", WH3_x3_y4_z3_12_materialId: str = "", WH3_x3_y4_z3_12_quantity: float = 0, + WH3_x1_y5_z3_13_materialType: str = "", WH3_x1_y5_z3_13_materialId: str = "", WH3_x1_y5_z3_13_quantity: float = 0, + WH3_x2_y5_z3_14_materialType: str = "", WH3_x2_y5_z3_14_materialId: str = "", WH3_x2_y5_z3_14_quantity: float = 0, + WH3_x3_y5_z3_15_materialType: str = "", WH3_x3_y5_z3_15_materialId: str = "", WH3_x3_y5_z3_15_quantity: float = 0, + ): + """ + 自动化上料(支持两种模式) + - Excel 路径存在 → 从 Excel 模板解析 + - Excel 路径不存在 → 使用手动参数 + """ + items: List[Dict[str, Any]] = [] + + # ---------- 模式 1: Excel 导入 ---------- + if xlsx_path: + path = Path(xlsx_path) + if path.exists(): # ★ 修改点:路径存在才加载 + try: + df = pd.read_excel(path, sheet_name=0, header=None, engine="openpyxl") + except Exception as e: + raise RuntimeError(f"读取 Excel 失败:{e}") + + # 四号手套箱加样头面 + for _, row in df.iloc[1:13, 2:7].iterrows(): + if pd.notna(row[5]): + items.append({ + "sourceWHName": "WH4", + "posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]), + "materialName": str(row[5]).strip(), + "quantity": float(row[6]) if pd.notna(row[6]) else 0.0, + }) + # 四号手套箱原液瓶面 + for _, row in df.iloc[14:23, 2:9].iterrows(): + if pd.notna(row[5]): + items.append({ + "sourceWHName": "WH4", + "posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]), + "materialName": str(row[5]).strip(), + "quantity": float(row[6]) if pd.notna(row[6]) else 0.0, + "materialType": str(row[7]).strip() if pd.notna(row[7]) else "", + "targetWH": str(row[8]).strip() if pd.notna(row[8]) else "", + }) + # 三号手套箱人工堆栈 + for _, row in df.iloc[25:40, 2:7].iterrows(): + if pd.notna(row[5]) or pd.notna(row[6]): + items.append({ + "sourceWHName": "WH3", + "posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]), + "materialType": str(row[5]).strip() if pd.notna(row[5]) else "", + "materialId": str(row[6]).strip() if pd.notna(row[6]) else "", + "quantity": 1 + }) + else: + logger.warning(f"未找到 Excel 文件 {xlsx_path},自动切换到手动参数模式。") + + # ---------- 模式 2: 手动填写 ---------- + if not items: + params = locals() + for name, value in params.items(): + if name.startswith("WH4") and "materialName" in name and value: + idx = name.split("_") + items.append({ + "sourceWHName": "WH4", + "posX": int(idx[1][1:]), "posY": int(idx[2][1:]), "posZ": int(idx[3][1:]), + "materialName": value, + "quantity": float(params.get(name.replace("materialName", "quantity"), 0.0)) + }) + elif name.startswith("WH4") and "materialType" in name and (value or params.get(name.replace("materialType", "materialName"), "")): + idx = name.split("_") + items.append({ + "sourceWHName": "WH4", + "posX": int(idx[1][1:]), "posY": int(idx[2][1:]), "posZ": int(idx[3][1:]), + "materialName": params.get(name.replace("materialType", "materialName"), ""), + "quantity": float(params.get(name.replace("materialType", "quantity"), 0.0)), + "materialType": value, + "targetWH": params.get(name.replace("materialType", "targetWH"), ""), + }) + elif name.startswith("WH3") and "materialType" in name and (value or params.get(name.replace("materialType", "materialId"), "")): + idx = name.split("_") + items.append({ + "sourceWHName": "WH3", + "posX": int(idx[1][1:]), "posY": int(idx[2][1:]), "posZ": int(idx[3][1:]), + "materialType": value, + "materialId": params.get(name.replace("materialType", "materialId"), ""), + "quantity": int(params.get(name.replace("materialType", "quantity"), 1)), + }) + + if not items: + logger.warning("没有有效的上料条目,已跳过提交。") + return {"code": 0, "message": "no valid items", "data": []} + + return self._post_lims("/api/lims/order/auto-feeding4to3", items) + + + + + # 3.30 自动化上料(老版本) + def auto_feeding4to3_from_xlsx(self, xlsx_path: str) -> Dict[str, Any]: + """ + 根据固定模板解析 Excel: + - 四号手套箱加样头面 (2-13行, 3-7列) + - 四号手套箱原液瓶面 (15-23行, 3-9列) + - 三号手套箱人工堆栈 (26-40行, 3-7列) + """ + path = Path(xlsx_path) + if not path.exists(): + raise FileNotFoundError(f"未找到 Excel 文件:{path}") + + try: + df = pd.read_excel(path, sheet_name=0, header=None, engine="openpyxl") + except Exception as e: + raise RuntimeError(f"读取 Excel 失败:{e}") + + items: List[Dict[str, Any]] = [] + + # 四号手套箱 - 加样头面(2-13行, 3-7列) + for _, row in df.iloc[1:13, 2:7].iterrows(): + item = { + "sourceWHName": "四号手套箱堆栈", + "posX": int(row[2]), + "posY": int(row[3]), + "posZ": int(row[4]), + "materialName": str(row[5]).strip() if pd.notna(row[5]) else "", + "quantity": float(row[6]) if pd.notna(row[6]) else 0.0, + } + if item["materialName"]: + items.append(item) + + # 四号手套箱 - 原液瓶面(15-23行, 3-9列) + for _, row in df.iloc[14:23, 2:9].iterrows(): + item = { + "sourceWHName": "四号手套箱堆栈", + "posX": int(row[2]), + "posY": int(row[3]), + "posZ": int(row[4]), + "materialName": str(row[5]).strip() if pd.notna(row[5]) else "", + "quantity": float(row[6]) if pd.notna(row[6]) else 0.0, + "materialType": str(row[7]).strip() if pd.notna(row[7]) else "", + "targetWH": str(row[8]).strip() if pd.notna(row[8]) else "", + } + if item["materialName"]: + items.append(item) + + # 三号手套箱人工堆栈(26-40行, 3-7列) + for _, row in df.iloc[25:40, 2:7].iterrows(): + item = { + "sourceWHName": "三号手套箱人工堆栈", + "posX": int(row[2]), + "posY": int(row[3]), + "posZ": int(row[4]), + "materialType": str(row[5]).strip() if pd.notna(row[5]) else "", + "materialId": str(row[6]).strip() if pd.notna(row[6]) else "", + "quantity": 1 # 默认数量1 + } + if item["materialId"] or item["materialType"]: + items.append(item) + + return self._post_lims("/api/lims/order/auto-feeding4to3", items) + + def auto_batch_outbound_from_xlsx(self, xlsx_path: str) -> Dict[str, Any]: + """ + 3.31 自动化下料(Excel -> JSON -> POST /api/lims/storage/auto-batch-out-bound) + """ + path = Path(xlsx_path) + if not path.exists(): + raise FileNotFoundError(f"未找到 Excel 文件:{path}") + + try: + df = pd.read_excel(path, sheet_name=0, engine="openpyxl") + except Exception as e: + raise RuntimeError(f"读取 Excel 失败:{e}") + + def pick(names: List[str]) -> Optional[str]: + for n in names: + if n in df.columns: + return n + return None + + c_loc = pick(["locationId", "库位ID", "库位Id", "库位id"]) + c_wh = pick(["warehouseId", "仓库ID", "仓库Id", "仓库id"]) + c_qty = pick(["数量", "quantity"]) + c_x = pick(["x", "X", "posX", "坐标X"]) + c_y = pick(["y", "Y", "posY", "坐标Y"]) + c_z = pick(["z", "Z", "posZ", "坐标Z"]) + + required = [c_loc, c_wh, c_qty, c_x, c_y, c_z] + if any(c is None for c in required): + raise KeyError("Excel 缺少必要列:locationId/warehouseId/数量/x/y/z(支持多别名,至少要能匹配到)。") + + def as_int(v, d=0): + try: + if pd.isna(v): return d + return int(v) + except Exception: + try: + return int(float(v)) + except Exception: + return d + + def as_float(v, d=0.0): + try: + if pd.isna(v): return d + return float(v) + except Exception: + return d + + def as_str(v, d=""): + if v is None or (isinstance(v, float) and pd.isna(v)): return d + s = str(v).strip() + return s if s else d + + items: List[Dict[str, Any]] = [] + for _, row in df.iterrows(): + items.append({ + "locationId": as_str(row[c_loc]), + "warehouseId": as_str(row[c_wh]), + "quantity": as_float(row[c_qty]), + "x": as_int(row[c_x]), + "y": as_int(row[c_y]), + "z": as_int(row[c_z]), + }) + + return self._post_lims("/api/lims/storage/auto-batch-out-bound", items) + + # 2.14 新建实验 + def create_orders(self, xlsx_path: str) -> Dict[str, Any]: + """ + 从 Excel 解析并创建实验(2.14) + 约定: + - batchId = Excel 文件名(不含扩展名) + - 物料列:所有以 "(g)" 结尾(不再读取“总质量(g)”列) + - totalMass 自动计算为所有物料质量之和 + - createTime 缺失或为空时自动填充为当前日期(YYYY/M/D) + """ + path = Path(xlsx_path) + if not path.exists(): + raise FileNotFoundError(f"未找到 Excel 文件:{path}") + + try: + df = pd.read_excel(path, sheet_name=0, engine="openpyxl") + except Exception as e: + raise RuntimeError(f"读取 Excel 失败:{e}") + + # 列名容错:返回可选列名,找不到则返回 None + def _pick(col_names: List[str]) -> Optional[str]: + for c in col_names: + if c in df.columns: + return c + return None + + col_order_name = _pick(["配方ID", "orderName", "订单编号"]) + col_create_time = _pick(["创建日期", "createTime"]) + col_bottle_type = _pick(["配液瓶类型", "bottleType"]) + col_mix_time = _pick(["混匀时间(s)", "mixTime"]) + col_load = _pick(["扣电组装分液体积", "loadSheddingInfo"]) + col_pouch = _pick(["软包组装分液体积", "pouchCellInfo"]) + col_cond = _pick(["电导测试分液体积", "conductivityInfo"]) + col_cond_cnt = _pick(["电导测试分液瓶数", "conductivityBottleCount"]) + + # 物料列:所有以 (g) 结尾 + material_cols = [c for c in df.columns if isinstance(c, str) and c.endswith("(g)")] + if not material_cols: + raise KeyError("未发现任何以“(g)”结尾的物料列,请检查表头。") + + batch_id = path.stem + + def _to_ymd_slash(v) -> str: + # 统一为 "YYYY/M/D";为空或解析失败则用当前日期 + if v is None or (isinstance(v, float) and pd.isna(v)) or str(v).strip() == "": + ts = datetime.now() + else: + try: + ts = pd.to_datetime(v) + except Exception: + ts = datetime.now() + return f"{ts.year}/{ts.month}/{ts.day}" + + def _as_int(val, default=0) -> int: + try: + if pd.isna(val): + return default + return int(val) + except Exception: + return default + + def _as_str(val, default="") -> str: + if val is None or (isinstance(val, float) and pd.isna(val)): + return default + s = str(val).strip() + return s if s else default + + orders: List[Dict[str, Any]] = [] + + for idx, row in df.iterrows(): + mats: List[Dict[str, Any]] = [] + total_mass = 0.0 + + for mcol in material_cols: + val = row.get(mcol, None) + if val is None or (isinstance(val, float) and pd.isna(val)): + continue + try: + mass = float(val) + except Exception: + continue + if mass > 0: + mats.append({"name": mcol.replace("(g)", ""), "mass": mass}) + total_mass += mass + + order_data = { + "batchId": batch_id, + "orderName": _as_str(row[col_order_name], default=f"{batch_id}_order_{idx+1}") if col_order_name else f"{batch_id}_order_{idx+1}", + "createTime": _to_ymd_slash(row[col_create_time]) if col_create_time else _to_ymd_slash(None), + "bottleType": _as_str(row[col_bottle_type], default="配液小瓶") if col_bottle_type else "配液小瓶", + "mixTime": _as_int(row[col_mix_time]) if col_mix_time else 0, + "loadSheddingInfo": _as_int(row[col_load]) if col_load else 0, + "pouchCellInfo": _as_int(row[col_pouch]) if col_pouch else 0, + "conductivityInfo": _as_int(row[col_cond]) if col_cond else 0, + "conductivityBottleCount": _as_int(row[col_cond_cnt]) if col_cond_cnt else 0, + "materialInfos": mats, + "totalMass": round(total_mass, 4) # 自动汇总 + } + orders.append(order_data) + + # print(orders) + while True: + time.sleep(5) + response = self._post_lims("/api/lims/order/orders", orders) + if response.get("data", []): + break + logger.info(f"等待配液实验创建完成") + + + + # self.order_status[response["data"]["orderCode"]] = "running" + + # while True: + # time.sleep(5) + # if self.order_status.get(response["data"]["orderCode"], None) == "finished": + # logger.info(f"配液实验已完成 ,即将执行 3-2-1 转运") + # break + # logger.info(f"等待配液实验完成") + + # self.transfer_3_to_2_to_1() + # self.wait_for_transfer_task() + # logger.info(f"3-2-1 转运完成,返回结果") + # return r321 + return response + + # 2.7 启动调度 + def scheduler_start(self) -> Dict[str, Any]: + return self._post_lims("/api/lims/scheduler/start") + # 3.10 停止调度 + def scheduler_stop(self) -> Dict[str, Any]: + + """ + 停止调度 (3.10) + 请求体只包含 apiKey 和 requestTime + """ + return self._post_lims("/api/lims/scheduler/stop") + # 2.9 继续调度 + # 2.9 继续调度 + def scheduler_continue(self) -> Dict[str, Any]: + """ + 继续调度 (2.9) + 请求体只包含 apiKey 和 requestTime + """ + return self._post_lims("/api/lims/scheduler/continue") + + # 2.24 物料变更推送 + def report_material_change(self, material_obj: Dict[str, Any]) -> Dict[str, Any]: + """ + material_obj 按 2.24 的裸对象格式(包含 id/typeName/locations/detail 等) + """ + return self._post_report_raw("/report/material_change", material_obj) + + # 2.32 3-2-1 物料转运 + def transfer_3_to_2_to_1(self, + # source_wh_id: Optional[str] = None, + source_wh_id: Optional[str] = '3a19debc-84b4-0359-e2d4-b3beea49348b', + source_x: int = 1, source_y: int = 1, source_z: int = 1) -> Dict[str, Any]: + payload: Dict[str, Any] = { + "sourcePosX": source_x, "sourcePosY": source_y, "sourcePosZ": source_z + } + if source_wh_id: + payload["sourceWHID"] = source_wh_id + return self._post_lims("/api/lims/order/transfer-task3To2To1", payload) + + # 3.35 1→2 物料转运 + def transfer_1_to_2(self) -> Dict[str, Any]: + """ + 1→2 物料转运 + URL: /api/lims/order/transfer-task1To2 + 只需要 apiKey 和 requestTime + """ + return self._post_lims("/api/lims/order/transfer-task1To2") + + # 2.5 批量查询实验报告(post过滤关键字查询) + def order_list_v2(self, + timeType: str = "", + beginTime: str = "", + endTime: str = "", + status: str = "", # 60表示正在运行,80表示完成,90表示失败 + filter: str = "", + skipCount: int = 0, + pageCount: int = 1, # 显示多少页数据 + sorting: str = "") -> Dict[str, Any]: + """ + 批量查询实验报告的详细信息 (2.5) + URL: /api/lims/order/order-list + 参数默认值和接口文档保持一致 + """ + data: Dict[str, Any] = { + "timeType": timeType, + "beginTime": beginTime, + "endTime": endTime, + "status": status, + "filter": filter, + "skipCount": skipCount, + "pageCount": pageCount, + "sorting": sorting + } + return self._post_lims("/api/lims/order/order-list", data) + + # 一直post执行bioyond接口查询任务状态 + def wait_for_transfer_task(self, timeout: int = 3000, interval: int = 5, filter_text: Optional[str] = None) -> bool: + """ + 轮询查询物料转移任务是否成功完成 (status=80) + - timeout: 最大等待秒数 (默认600秒) + - interval: 轮询间隔秒数 (默认3秒) + 返回 True 表示找到并成功完成,False 表示超时未找到 + """ + now = datetime.now() + beginTime = now.strftime("%Y-%m-%dT%H:%M:%SZ") + endTime = (now + timedelta(minutes=5)).strftime("%Y-%m-%dT%H:%M:%SZ") + print(beginTime, endTime) + + deadline = time.time() + timeout + + while time.time() < deadline: + result = self.order_list_v2( + timeType="", + beginTime=beginTime, + endTime=endTime, + status="", + filter=filter_text, + skipCount=0, + pageCount=1, + sorting="" + ) + print(result) + + items = result.get("data", {}).get("items", []) + for item in items: + name = item.get("name", "") + status = item.get("status") + # 改成用 filter_text 判断 + if (not filter_text or filter_text in name) and status == 80: + logger.info(f"硬件转移动作完成: {name}, status={status}") + return True + + logger.info(f"等待中: {name}, status={status}") + time.sleep(interval) + + logger.warning("超时未找到成功的物料转移任务") + return False + + +# -------------------------------- +if __name__ == "__main__": + ws = BioyondCellWorkstation() + ws.scheduler_stop() + # re=ws.scheduler_start() + print(re) + # logger.info("调度启动完成") + + # ws.scheduler_continue() + # 3.30 上料:读取模板 Excel 自动解析并 POST + # r1 = ws.auto_feeding4to3_from_xlsx(r"C:\ML\GitHub\Uni-Lab-OS\unilabos\devices\workstation\bioyond_cell\样品导入模板.xlsx") + # ws.wait_for_transfer_task(filter_text="物料转移任务") + # logger.info("4号箱向3号箱转运物料转移任务已完成") + + # ws.scheduler_start() + # print(r1["payload"]["data"]) # 调试模式下可直接看到要发的 JSON items + + # # 新建实验 + # response = ws.create_orders("C:/ML/GitHub/Uni-Lab-OS/unilabos/devices/workstation/bioyond_cell/2025092701.xlsx") + # logger.info(response) + # data_list = response.get("data", []) + # order_name = data_list[0].get("orderName", "") + + # ws.wait_for_transfer_task(filter_text=order_name) + # ws.wait_for_transfer_task(filter_text='DP20250927001') + # logger.info("3号站内实验完成") + # # ws.scheduler_start() + # # print(res) + # ws.transfer_3_to_2_to_1() + # ws.wait_for_transfer_task(filter_text="物料转移任务") + # logger.info("3号站向2号站向1号站转移任务完成") + # r321 = self.wait_for_transfer_task() + #1号站启动 + # ws.transfer_1_to_2() + # ws.wait_for_transfer_task(filter_text="物料转移任务") + # logger.info("1号站向2号站转移任务完成") + # logger.info("全流程结束") + + # 3.31 下料:同理 + # r2 = ws.auto_batch_outbound_from_xlsx(r"C:/path/样品导入模板 (8).xlsx") + # print(r2["payload"]["data"]) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_material_management.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_material_management.py new file mode 100644 index 0000000..245da15 --- /dev/null +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_material_management.py @@ -0,0 +1,374 @@ +""" +Bioyond物料管理实现 +Bioyond Material Management Implementation + +基于Bioyond系统的物料管理,支持从Bioyond系统同步物料到UniLab工作站 +""" +from typing import Dict, Any, List, Optional, Union +import json +import asyncio +from abc import ABC, abstractmethod + +from pylabrobot.resources import ( + Resource as PLRResource, + Container, + Deck, + Coordinate as PLRCoordinate, +) + +from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker +from unilabos.utils.log import logger +from unilabos.resources.graphio import ( + resource_plr_to_ulab, + resource_ulab_to_plr, + resource_bioyond_to_ulab, + resource_bioyond_container_to_ulab, + resource_ulab_to_bioyond +) +from .workstation_material_management import MaterialManagementBase + + +class BioyondMaterialManagement(MaterialManagementBase): + """Bioyond物料管理类 + + 实现从Bioyond系统同步物料到UniLab工作站的功能: + 1. 从Bioyond系统获取物料数据 + 2. 转换为UniLab格式 + 3. 同步到PyLabRobot Deck + 4. 支持双向同步 + """ + + def __init__( + self, + device_id: str, + deck_config: Dict[str, Any], + resource_tracker: DeviceNodeResourceTracker, + children_config: Dict[str, Dict[str, Any]] = None, + bioyond_config: Dict[str, Any] = None + ): + self.bioyond_config = bioyond_config or {} + self.bioyond_api_client = None + self.sync_interval = self.bioyond_config.get("sync_interval", 30) # 同步间隔(秒) + + # 初始化父类 + super().__init__(device_id, deck_config, resource_tracker, children_config) + + # 初始化Bioyond API客户端 + self._initialize_bioyond_client() + + # 启动同步任务 + self._start_sync_task() + + def _initialize_bioyond_client(self): + """初始化Bioyond API客户端""" + try: + # 这里应该根据实际的Bioyond API实现 + # 暂时使用模拟客户端 + self.bioyond_api_client = BioyondAPIClient(self.bioyond_config) + logger.info(f"Bioyond API客户端初始化成功") + except Exception as e: + logger.error(f"Bioyond API客户端初始化失败: {e}") + self.bioyond_api_client = None + + def _start_sync_task(self): + """启动同步任务""" + if self.bioyond_api_client: + # 创建异步同步任务 + asyncio.create_task(self._periodic_sync()) + logger.info(f"Bioyond同步任务已启动,间隔: {self.sync_interval}秒") + + async def _periodic_sync(self): + """定期同步任务""" + while True: + try: + await self.sync_from_bioyond() + await asyncio.sleep(self.sync_interval) + except Exception as e: + logger.error(f"Bioyond同步任务出错: {e}") + await asyncio.sleep(self.sync_interval) + + async def sync_from_bioyond(self) -> bool: + """从Bioyond系统同步物料""" + try: + if not self.bioyond_api_client: + logger.warning("Bioyond API客户端未初始化") + return False + + # 1. 从Bioyond获取物料数据 + bioyond_data = await self.bioyond_api_client.get_materials() + if not bioyond_data: + logger.warning("从Bioyond获取物料数据为空") + return False + + # 2. 转换为UniLab格式 + if isinstance(bioyond_data, dict) and "data" in bioyond_data: + # 容器格式数据 + unilab_resources = resource_bioyond_container_to_ulab(bioyond_data) + else: + # 物料列表格式数据 + unilab_resources = resource_bioyond_to_ulab(bioyond_data) + + # 3. 转换为PLR格式并分配到Deck + await self._assign_resources_to_deck(unilab_resources) + + logger.info(f"从Bioyond同步了 {len(unilab_resources)} 个资源") + return True + + except Exception as e: + logger.error(f"从Bioyond同步物料失败: {e}") + return False + + async def sync_to_bioyond(self, plr_resource: PLRResource) -> bool: + """将本地物料变更同步到Bioyond系统""" + try: + if not self.bioyond_api_client: + logger.warning("Bioyond API客户端未初始化") + return False + + # 1. 转换为UniLab格式 + unilab_resource = resource_plr_to_ulab(plr_resource) + + # 2. 转换为Bioyond格式 + bioyond_materials = resource_ulab_to_bioyond([unilab_resource]) + + # 3. 发送到Bioyond系统 + success = await self.bioyond_api_client.update_materials(bioyond_materials) + + if success: + logger.info(f"成功同步物料 {plr_resource.name} 到Bioyond") + else: + logger.warning(f"同步物料 {plr_resource.name} 到Bioyond失败") + + return success + + except Exception as e: + logger.error(f"同步物料到Bioyond失败: {e}") + return False + + async def _assign_resources_to_deck(self, unilab_resources: List[Dict[str, Any]]): + """将UniLab资源分配到Deck""" + try: + # 转换为PLR格式 + from unilabos.resources.graphio import list_to_nested_dict + nested_resources = list_to_nested_dict(unilab_resources) + plr_resources = resource_ulab_to_plr(nested_resources) + + # 分配资源到Deck + if hasattr(plr_resources, 'children'): + resources_to_assign = plr_resources.children + elif isinstance(plr_resources, list): + resources_to_assign = plr_resources + else: + resources_to_assign = [plr_resources] + + for resource in resources_to_assign: + try: + # 获取资源位置 + if hasattr(resource, 'location') and resource.location: + location = PLRCoordinate(resource.location.x, resource.location.y, resource.location.z) + else: + location = PLRCoordinate(0, 0, 0) + + # 分配资源到Deck + self.plr_deck.assign_child_resource(resource, location) + + # 注册到resource tracker + self.resource_tracker.add_resource(resource) + + # 保存资源引用 + self.plr_resources[resource.name] = resource + + except Exception as e: + logger.error(f"分配资源 {resource.name} 到Deck失败: {e}") + + logger.info(f"成功分配了 {len(resources_to_assign)} 个资源到Deck") + + except Exception as e: + logger.error(f"分配资源到Deck失败: {e}") + + def _create_resource_by_type( + self, + resource_id: str, + resource_type: str, + config: Dict[str, Any], + data: Dict[str, Any], + location: PLRCoordinate + ) -> Optional[PLRResource]: + """根据类型创建Bioyond相关资源""" + try: + # 这里可以根据需要实现特定的Bioyond资源类型 + # 目前使用通用的容器类型 + if resource_type in ["container", "plate", "well"]: + return self._create_generic_container(resource_id, resource_type, config, data, location) + else: + logger.warning(f"未知的Bioyond资源类型: {resource_type}") + return None + + except Exception as e: + logger.error(f"创建Bioyond资源失败 {resource_id} ({resource_type}): {e}") + return None + + def _create_generic_container( + self, + resource_id: str, + resource_type: str, + config: Dict[str, Any], + data: Dict[str, Any], + location: PLRCoordinate + ) -> Optional[PLRResource]: + """创建通用容器资源""" + try: + from pylabrobot.resources import Plate, Well + + if resource_type == "plate": + return Plate( + name=resource_id, + size_x=config.get("size_x", 127.76), + size_y=config.get("size_y", 85.48), + size_z=config.get("size_z", 14.35), + location=location, + category="plate" + ) + elif resource_type == "well": + return Well( + name=resource_id, + size_x=config.get("size_x", 9.0), + size_y=config.get("size_y", 9.0), + size_z=config.get("size_z", 10.0), + location=location, + category="well" + ) + else: + return Container( + name=resource_id, + size_x=config.get("size_x", 50.0), + size_y=config.get("size_y", 50.0), + size_z=config.get("size_z", 10.0), + location=location, + category="container" + ) + + except Exception as e: + logger.error(f"创建通用容器失败 {resource_id}: {e}") + return None + + def get_bioyond_materials(self) -> List[Dict[str, Any]]: + """获取当前Bioyond物料列表""" + try: + # 将当前PLR资源转换为Bioyond格式 + bioyond_materials = [] + for resource in self.plr_resources.values(): + unilab_resource = resource_plr_to_ulab(resource) + bioyond_materials.extend(resource_ulab_to_bioyond([unilab_resource])) + return bioyond_materials + except Exception as e: + logger.error(f"获取Bioyond物料列表失败: {e}") + return [] + + def update_material_from_bioyond(self, material_id: str, bioyond_data: Dict[str, Any]) -> bool: + """从Bioyond数据更新指定物料""" + try: + # 查找现有物料 + material = self.find_material_by_id(material_id) + if not material: + logger.warning(f"未找到物料: {material_id}") + return False + + # 转换Bioyond数据为UniLab格式 + unilab_resources = resource_bioyond_to_ulab([bioyond_data]) + if not unilab_resources: + logger.warning(f"转换Bioyond数据失败: {material_id}") + return False + + # 更新物料属性 + unilab_resource = unilab_resources[0] + material.name = unilab_resource.get("name", material.name) + + # 更新位置 + position = unilab_resource.get("position", {}) + if position: + material.location = PLRCoordinate( + position.get("x", 0), + position.get("y", 0), + position.get("z", 0) + ) + + logger.info(f"成功更新物料: {material_id}") + return True + + except Exception as e: + logger.error(f"更新物料失败 {material_id}: {e}") + return False + + +class BioyondAPIClient: + """Bioyond API客户端(模拟实现) + + 实际使用时需要根据Bioyond系统的API接口实现 + """ + + def __init__(self, config: Dict[str, Any]): + self.config = config + self.base_url = config.get("base_url", "http://localhost:8080") + self.api_key = config.get("api_key", "") + self.timeout = config.get("timeout", 30) + + async def get_materials(self) -> Optional[Union[Dict[str, Any], List[Dict[str, Any]]]]: + """从Bioyond系统获取物料数据""" + try: + # 这里应该实现实际的API调用 + # 暂时返回模拟数据 + logger.info("从Bioyond API获取物料数据") + + # 模拟API调用延迟 + await asyncio.sleep(0.1) + + # 返回模拟数据(实际应该从API获取) + return { + "data": [], + "code": 1, + "message": "success", + "timestamp": 1234567890 + } + + except Exception as e: + logger.error(f"Bioyond API调用失败: {e}") + return None + + async def update_materials(self, materials: List[Dict[str, Any]]) -> bool: + """更新Bioyond系统中的物料数据""" + try: + # 这里应该实现实际的API调用 + logger.info(f"更新Bioyond系统中的 {len(materials)} 个物料") + + # 模拟API调用延迟 + await asyncio.sleep(0.1) + + # 模拟成功响应 + return True + + except Exception as e: + logger.error(f"更新Bioyond物料失败: {e}") + return False + + async def get_material_by_id(self, material_id: str) -> Optional[Dict[str, Any]]: + """根据ID获取单个物料""" + try: + # 这里应该实现实际的API调用 + logger.info(f"从Bioyond API获取物料: {material_id}") + + # 模拟API调用延迟 + await asyncio.sleep(0.1) + + # 返回模拟数据 + return { + "id": material_id, + "name": f"material_{material_id}", + "type": "container", + "quantity": 1.0, + "unit": "个" + } + + except Exception as e: + logger.error(f"获取Bioyond物料失败 {material_id}: {e}") + return None diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_workstation copy.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_workstation copy.py new file mode 100644 index 0000000..864a9dd --- /dev/null +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_workstation copy.py @@ -0,0 +1,715 @@ +# -*- coding: utf-8 -*- +from typing import Dict, Any, List, Optional +from datetime import datetime, timezone +import requests +from pathlib import Path +import pandas as pd +import time +from datetime import datetime, timezone, timedelta +import re +import threading +from unilabos.devices.workstation.workstation_base import WorkstationBase +from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService +from unilabos.utils.log import logger +from pylabrobot.resources.deck import Deck + + +def _iso_utc_now_ms() -> str: + # 文档要求:到毫秒 + Z,例如 2025-08-15T05:43:22.814Z + dt = datetime.now(timezone.utc) + return dt.strftime("%Y-%m-%dT%H:%M:%S.") + f"{int(dt.microsecond/1000):03d}Z" + + +class BioyondWorkstation(WorkstationBase): + """ + 集成 Bioyond LIMS 的工作站示例, + 覆盖:入库(2.17/2.18) → 新建实验(2.14) → 启动调度(2.7) → + 运行中推送:物料变更(2.24)、步骤完成(2.21)、订单完成(2.23) → + 查询实验(2.5/2.6) → 3-2-1 转运(2.32) → 样品/废料取出(2.28) + """ + + def __init__( + self, + bioyond_config: Optional[Dict[str, Any]] = None, + station_resource: Optional[Dict[str, Any]] = None, + debug_mode: bool = False, # 增加调试模式开关 + *args, **kwargs, + ): + self.bioyond_config = bioyond_config or { + "base_url": "http://192.168.1.200:44386", + "api_key": "8A819E5C", + "timeout": 30, + "report_token": "CHANGE_ME_TOKEN" + } + + self.http_service_started = False + self.debug_mode = debug_mode + super().__init__(deck=Deck, station_resource=station_resource, *args, **kwargs) + logger.info(f"Bioyond工作站初始化完成 (debug_mode={self.debug_mode})") + + # 实例化并在后台线程启动 HTTP 报送服务 + self.order_status = {} + try: + t = threading.Thread(target=self._start_http_service_bg, daemon=True, name="unilab_http") + t.start() + + except Exception as e: + logger.error(f"unilab-server后台启动报送服务失败: {e}") + + @property + def device_id(self) -> str: + try: + return getattr(self, "_ros_node").device_id # 兼容 ROS 场景 + except Exception: + return "bioyond_workstation" + + def _start_http_service_bg(self, host: str = "192.168.1.104", port: int = 8080) -> None: + logger.info("进入 _start_http_service_bg 函数") + try: + self.service = WorkstationHTTPService(self, host=host, port=port) + logger.info("WorkstationHTTPService 实例化完成") + self.service.start() + self.http_service_started = True + logger.info(f"unilab_HTTP 服务成功启动: {host}:{port}") + + #一直挂着,直到进程退出 + while True: + time.sleep(1) + + except Exception as e: + self.http_service_started = False + logger.error(f"启动unilab_HTTP服务失败: {e}", exc_info=True) + + # -------------------- 基础HTTP封装 -------------------- + def _url(self, path: str) -> str: + return f"{self.bioyond_config['base_url'].rstrip('/')}/{path.lstrip('/')}" + + def _post_lims(self, path: str, data: Optional[Any] = None) -> Dict[str, Any]: + """LIMS API:大多数接口用 {apiKey/requestTime,data} 包装""" + payload = { + "apiKey": self.bioyond_config["api_key"], + "requestTime": _iso_utc_now_ms() + } + if data is not None: + payload["data"] = data + + if self.debug_mode: + # 模拟返回,不发真实请求 + logger.info(f"[DEBUG] POST {path} with payload={payload}") + return {"debug": True, "url": self._url(path), "payload": payload, "status": "ok"} + + try: + r = requests.post( + self._url(path), + json=payload, + timeout=self.bioyond_config.get("timeout", 30), + headers={"Content-Type": "application/json"} + ) + r.raise_for_status() + return r.json() + except Exception as e: + logger.error(f"POST {path} 失败: {e}") + return {"error": str(e)} + + # --- 修正:_post_report / _post_report_raw 同样走 debug_mode --- + def _post_report(self, path: str, data: Dict[str, Any]) -> Dict[str, Any]: + payload = { + "token": self.bioyond_config.get("report_token", ""), + "request_time": _iso_utc_now_ms(), + "data": data + } + if self.debug_mode: + logger.info(f"[DEBUG] POST {path} with payload={payload}") + return {"debug": True, "url": self._url(path), "payload": payload, "status": "ok"} + try: + r = requests.post(self._url(path), json=payload, + timeout=self.bioyond_config.get("timeout", 30), + headers={"Content-Type": "application/json"}) + r.raise_for_status() + return r.json() + except Exception as e: + logger.error(f"POST {path} 失败: {e}") + return {"error": str(e)} + + def _post_report_raw(self, path: str, body: Dict[str, Any]) -> Dict[str, Any]: + if self.debug_mode: + logger.info(f"[DEBUG] POST {path} with body={body}") + return {"debug": True, "url": self._url(path), "payload": body, "status": "ok"} + try: + r = requests.post(self._url(path), json=body, + timeout=self.bioyond_config.get("timeout", 30), + headers={"Content-Type": "application/json"}) + r.raise_for_status() + return r.json() + except Exception as e: + logger.error(f"POST {path} 失败: {e}") + return {"error": str(e)} + + + # -------------------- 单点接口封装 -------------------- + # 2.17 入库物料(单个) + def storage_inbound(self, material_id: str, location_id: str) -> Dict[str, Any]: + return self._post_lims("/api/lims/storage/inbound", { + "materialId": material_id, + "locationId": location_id + }) + + # 2.18 批量入库(多个) + def storage_batch_inbound(self, items: List[Dict[str, str]]) -> Dict[str, Any]: + """ + items = [{"materialId": "...", "locationId": "..."}, ...] + """ + return self._post_lims("/api/lims/storage/batch-inbound", items) + + # 3.30 自动化上料(Excel -> JSON -> POST /api/lims/order/auto-feeding4to3) + def auto_feeding4to3_from_xlsx(self, xlsx_path: str) -> Dict[str, Any]: + """ + 根据固定模板解析 Excel: + - 四号手套箱加样头面 (2-13行, 3-7列) + - 四号手套箱原液瓶面 (15-23行, 3-9列) + - 三号手套箱人工堆栈 (26-40行, 3-7列) + """ + path = Path(xlsx_path) + if not path.exists(): + raise FileNotFoundError(f"未找到 Excel 文件:{path}") + + try: + df = pd.read_excel(path, sheet_name=0, header=None, engine="openpyxl") + except Exception as e: + raise RuntimeError(f"读取 Excel 失败:{e}") + + items: List[Dict[str, Any]] = [] + + # 四号手套箱 - 加样头面(2-13行, 3-7列) + for _, row in df.iloc[1:13, 2:7].iterrows(): + item = { + "sourceWHName": "四号手套箱堆栈", + "posX": int(row[2]), + "posY": int(row[3]), + "posZ": int(row[4]), + "materialName": str(row[5]).strip() if pd.notna(row[5]) else "", + "quantity": float(row[6]) if pd.notna(row[6]) else 0.0, + } + if item["materialName"]: + items.append(item) + + # 四号手套箱 - 原液瓶面(15-23行, 3-9列) + for _, row in df.iloc[14:23, 2:9].iterrows(): + item = { + "sourceWHName": "四号手套箱堆栈", + "posX": int(row[2]), + "posY": int(row[3]), + "posZ": int(row[4]), + "materialName": str(row[5]).strip() if pd.notna(row[5]) else "", + "quantity": float(row[6]) if pd.notna(row[6]) else 0.0, + "materialType": str(row[7]).strip() if pd.notna(row[7]) else "", + "targetWH": str(row[8]).strip() if pd.notna(row[8]) else "", + } + if item["materialName"]: + items.append(item) + + # 三号手套箱人工堆栈(26-40行, 3-7列) + for _, row in df.iloc[25:40, 2:7].iterrows(): + item = { + "sourceWHName": "三号手套箱人工堆栈", + "posX": int(row[2]), + "posY": int(row[3]), + "posZ": int(row[4]), + "materialType": str(row[5]).strip() if pd.notna(row[5]) else "", + "materialId": str(row[6]).strip() if pd.notna(row[6]) else "", + "quantity": 1 # 默认数量1 + } + if item["materialId"] or item["materialType"]: + items.append(item) + + return self._post_lims("/api/lims/order/auto-feeding4to3", items) + + + + def auto_batch_outbound_from_xlsx(self, xlsx_path: str) -> Dict[str, Any]: + """ + 3.31 自动化下料(Excel -> JSON -> POST /api/lims/storage/auto-batch-out-bound) + """ + path = Path(xlsx_path) + if not path.exists(): + raise FileNotFoundError(f"未找到 Excel 文件:{path}") + + try: + df = pd.read_excel(path, sheet_name=0, engine="openpyxl") + except Exception as e: + raise RuntimeError(f"读取 Excel 失败:{e}") + + def pick(names: List[str]) -> Optional[str]: + for n in names: + if n in df.columns: + return n + return None + + c_loc = pick(["locationId", "库位ID", "库位Id", "库位id"]) + c_wh = pick(["warehouseId", "仓库ID", "仓库Id", "仓库id"]) + c_qty = pick(["数量", "quantity"]) + c_x = pick(["x", "X", "posX", "坐标X"]) + c_y = pick(["y", "Y", "posY", "坐标Y"]) + c_z = pick(["z", "Z", "posZ", "坐标Z"]) + + required = [c_loc, c_wh, c_qty, c_x, c_y, c_z] + if any(c is None for c in required): + raise KeyError("Excel 缺少必要列:locationId/warehouseId/数量/x/y/z(支持多别名,至少要能匹配到)。") + + def as_int(v, d=0): + try: + if pd.isna(v): return d + return int(v) + except Exception: + try: + return int(float(v)) + except Exception: + return d + + def as_float(v, d=0.0): + try: + if pd.isna(v): return d + return float(v) + except Exception: + return d + + def as_str(v, d=""): + if v is None or (isinstance(v, float) and pd.isna(v)): return d + s = str(v).strip() + return s if s else d + + items: List[Dict[str, Any]] = [] + for _, row in df.iterrows(): + items.append({ + "locationId": as_str(row[c_loc]), + "warehouseId": as_str(row[c_wh]), + "quantity": as_float(row[c_qty]), + "x": as_int(row[c_x]), + "y": as_int(row[c_y]), + "z": as_int(row[c_z]), + }) + + return self._post_lims("/api/lims/storage/auto-batch-out-bound", items) + + # 2.14 新建实验 + def create_orders(self, xlsx_path: str) -> Dict[str, Any]: + """ + 从 Excel 解析并创建实验(2.14) + 约定: + - batchId = Excel 文件名(不含扩展名) + - 物料列:所有以 "(g)" 结尾(不再读取“总质量(g)”列) + - totalMass 自动计算为所有物料质量之和 + - createTime 缺失或为空时自动填充为当前日期(YYYY/M/D) + """ + path = Path(xlsx_path) + if not path.exists(): + raise FileNotFoundError(f"未找到 Excel 文件:{path}") + + try: + df = pd.read_excel(path, sheet_name=0, engine="openpyxl") + except Exception as e: + raise RuntimeError(f"读取 Excel 失败:{e}") + + # 列名容错:返回可选列名,找不到则返回 None + def _pick(col_names: List[str]) -> Optional[str]: + for c in col_names: + if c in df.columns: + return c + return None + + col_order_name = _pick(["配方ID", "orderName", "订单编号"]) + col_create_time = _pick(["创建日期", "createTime"]) + col_bottle_type = _pick(["配液瓶类型", "bottleType"]) + col_mix_time = _pick(["混匀时间(s)", "mixTime"]) + col_load = _pick(["扣电组装分液体积", "loadSheddingInfo"]) + col_pouch = _pick(["软包组装分液体积", "pouchCellInfo"]) + col_cond = _pick(["电导测试分液体积", "conductivityInfo"]) + col_cond_cnt = _pick(["电导测试分液瓶数", "conductivityBottleCount"]) + + # 物料列:所有以 (g) 结尾 + material_cols = [c for c in df.columns if isinstance(c, str) and c.endswith("(g)")] + if not material_cols: + raise KeyError("未发现任何以“(g)”结尾的物料列,请检查表头。") + + batch_id = path.stem + + def _to_ymd_slash(v) -> str: + # 统一为 "YYYY/M/D";为空或解析失败则用当前日期 + if v is None or (isinstance(v, float) and pd.isna(v)) or str(v).strip() == "": + ts = datetime.now() + else: + try: + ts = pd.to_datetime(v) + except Exception: + ts = datetime.now() + return f"{ts.year}/{ts.month}/{ts.day}" + + def _as_int(val, default=0) -> int: + try: + if pd.isna(val): + return default + return int(val) + except Exception: + return default + + def _as_str(val, default="") -> str: + if val is None or (isinstance(val, float) and pd.isna(val)): + return default + s = str(val).strip() + return s if s else default + + orders: List[Dict[str, Any]] = [] + + for idx, row in df.iterrows(): + mats: List[Dict[str, Any]] = [] + total_mass = 0.0 + + for mcol in material_cols: + val = row.get(mcol, None) + if val is None or (isinstance(val, float) and pd.isna(val)): + continue + try: + mass = float(val) + except Exception: + continue + if mass > 0: + mats.append({"name": mcol.replace("(g)", ""), "mass": mass}) + total_mass += mass + + order_data = { + "batchId": batch_id, + "orderName": _as_str(row[col_order_name], default=f"{batch_id}_order_{idx+1}") if col_order_name else f"{batch_id}_order_{idx+1}", + "createTime": _to_ymd_slash(row[col_create_time]) if col_create_time else _to_ymd_slash(None), + "bottleType": _as_str(row[col_bottle_type], default="配液小瓶") if col_bottle_type else "配液小瓶", + "mixTime": _as_int(row[col_mix_time]) if col_mix_time else 0, + "loadSheddingInfo": _as_int(row[col_load]) if col_load else 0, + "pouchCellInfo": _as_int(row[col_pouch]) if col_pouch else 0, + "conductivityInfo": _as_int(row[col_cond]) if col_cond else 0, + "conductivityBottleCount": _as_int(row[col_cond_cnt]) if col_cond_cnt else 0, + "materialInfos": mats, + "totalMass": round(total_mass, 4) # 自动汇总 + } + orders.append(order_data) + + # print(orders) + + response = self._post_lims("/api/lims/order/orders", orders) + self.order_status[response["data"]["orderCode"]] = "running" + + while True: + time.sleep(5) + if self.order_status.get(response["data"]["orderCode"], None) == "finished": + logger.info(f"配液实验已完成 ,即将执行 3-2-1 转运") + break + logger.info(f"等待配液实验完成") + + self.transfer_3_to_2_to_1() + r321 = self.wait_for_transfer_task() + logger.info(f"3-2-1 转运完成,返回结果") + return r321 + + + # 2.7 启动调度 + def scheduler_start(self) -> Dict[str, Any]: + return self._post_lims("/api/lims/scheduler/start") + # 3.10 停止调度 + def scheduler_stop(self) -> Dict[str, Any]: + """ + 停止调度 (3.10) + 请求体只包含 apiKey 和 requestTime + """ + return self._post_lims("/api/lims/scheduler/stop") + # 2.9 继续调度 + def scheduler_continue(self) -> Dict[str, Any]: + """ + 继续调度 (2.9) + 请求体只包含 apiKey 和 requestTime + """ + return self._post_lims("/api/lims/scheduler/continue") + + + + # 2.24 物料变更推送 + def report_material_change(self, material_obj: Dict[str, Any]) -> Dict[str, Any]: + """ + material_obj 按 2.24 的裸对象格式(包含 id/typeName/locations/detail 等) + """ + return self._post_report_raw("/report/material_change", material_obj) + + # 2.21 步骤完成推送(BS → LIMS) + def report_step_finish(self, + order_code: str, + order_name: str, + step_name: str, + step_id: str, + sample_id: str, + start_time: str, + end_time: str, + execution_status: str = "completed") -> Dict[str, Any]: + data = { + "orderCode": order_code, + "orderName": order_name, + "stepName": step_name, + "stepId": step_id, + "sampleId": sample_id, + "startTime": start_time, + "endTime": end_time, + "executionStatus": execution_status + } + return self._post_report("/report/step_finish", data) + + # 2.23 订单完成推送(BS → LIMS) + def report_order_finish(self, + order_code: str, + order_name: str, + start_time: str, + end_time: str, + status: str = "30", # 30 完成 / -11 异常停止 / -12 人工停止 + workflow_status: str = "Finished", + completion_time: Optional[str] = None, + used_materials: Optional[List[Dict[str, Any]]] = None) -> Dict[str, Any]: + data = { + "orderCode": order_code, + "orderName": order_name, + "startTime": start_time, + "endTime": end_time, + "status": status, + "workflowStatus": workflow_status, + "completionTime": completion_time or end_time, + "usedMaterials": used_materials or [] + } + return self._post_report("/report/order_finish", data) + + # 2.5 批量查询实验报告(用于轮询是否完成) + def order_list(self, + status: Optional[str] = None, + begin_time: Optional[str] = None, + end_time: Optional[str] = None, + filter_text: Optional[str] = None, + skip: int = 0, page: int = 10) -> Dict[str, Any]: + data: Dict[str, Any] = {"skipCount": skip, "pageCount": page} + if status is not None: # 80 成功 / 90 失败 / 100 执行中 + data["status"] = status + if begin_time: + data["timeType"] = "CreationTime" + data["beginTime"] = begin_time + if end_time: + data["endTime"] = end_time + if filter_text: + data["filter"] = filter_text + return self._post_lims("/api/lims/order/order-list", data) + + # 2.6 实验报告查询(根据任务ID拿详情) + def order_report(self, order_id: str) -> Dict[str, Any]: + return self._post_lims("/api/lims/order/order-report", order_id) + + # 2.32 3-2-1 物料转运 + def transfer_3_to_2_to_1(self, + # source_wh_id: Optional[str] = None, + source_wh_id: Optional[str] = '3a19debc-84b4-0359-e2d4-b3beea49348b', + source_x: int = 1, source_y: int = 1, source_z: int = 1) -> Dict[str, Any]: + payload: Dict[str, Any] = { + "sourcePosX": source_x, "sourcePosY": source_y, "sourcePosZ": source_z + } + if source_wh_id: + payload["sourceWHID"] = source_wh_id + return self._post_lims("/api/lims/order/transfer-task3To2To1", payload) + + # 2.28 样品/废料取出 + def take_out(self, + order_id: str, + preintake_ids: Optional[List[str]] = None, + material_ids: Optional[List[str]] = None) -> Dict[str, Any]: + data = { + "orderId": order_id, + "preintakeIds": preintake_ids or [], + "materialIds": material_ids or [] + } + return self._post_lims("/api/lims/order/take-out", data) + + # --------(可选)占位方法:文档未定义的“1号站内部流程 / 1-2转运”-------- + def start_station1_internal_flow(self, **kwargs) -> None: + logger.info("启动1号站内部流程(占位,按现场系统填充具体指令)") + + + # 3.x 1→2 物料转运 + def transfer_1_to_2(self) -> Dict[str, Any]: + """ + 1→2 物料转运 + URL: /api/lims/order/transfer-task1To2 + 只需要 apiKey 和 requestTime + """ + return self._post_lims("/api/lims/order/transfer-task1To2") + + + # -------------------- 整体编排 -------------------- + def run_full_workflow(self, + inbound_items: List[Dict[str, str]], + orders: List[Dict[str, Any]], + poll_filter_code: Optional[str] = None, + poll_timeout_s: int = 600, + poll_interval_s: int = 5, + transfer_source: Optional[Dict[str, Any]] = None, + takeout_order_id: Optional[str] = None) -> None: + """ + 一键串联: + 1) 入库 3-4 个物料 → 2) 新建实验 → 3) 启动调度 + 运行中(如需):4) 物料变更推送 5) 步骤完成推送 6) 订单完成推送 + 完成后:查询实验(2.5/2.6)→ 7) 3-2-1 转运 → 8) 1号站内部流程 + → 9) 1-2 转运 → 10) 样品/废料取出 + """ + # 1. 入库(多于1个就用批量接口 2.18) + if len(inbound_items) == 1: + r = self.storage_inbound(inbound_items[0]["materialId"], inbound_items[0]["locationId"]) + logger.info(f"单个入库结果: {r}") + else: + r = self.storage_batch_inbound(inbound_items) + logger.info(f"批量入库结果: {r}") + + # 2. 新建实验(2.14) + r = self.create_orders(orders) + logger.info(f"新建实验结果: {r}") + + # 3. 启动调度(2.7) + r = self.scheduler_start() + logger.info(f"启动调度结果: {r}") + + # —— 运行中各类推送(2.24 / 2.21 / 2.23),通常由实际任务驱动,这里提供调用方式 —— # + # self.report_material_change({...}) + # self.report_step_finish(order_code="BSO...", order_name="配液分液", step_name="xxx", step_id="...", sample_id="...", + # start_time=_iso_utc_now_ms(), end_time=_iso_utc_now_ms(), execution_status="completed") + # self.report_order_finish(order_code="BSO...", order_name="配液分液", start_time="...", end_time=_iso_utc_now_ms()) + + # 完成后才能转运:用 2.5 批量查询配合 filter=任务编码 轮询到 status=80(成功) + if poll_filter_code: + import time + deadline = time.time() + poll_timeout_s + while time.time() < deadline: + res = self.order_list(status="80", filter_text=poll_filter_code, page=5) + if isinstance(res, dict) and res.get("data", {}).get("items"): + logger.info(f"实验 {poll_filter_code} 已完成:{res['data']['items'][0]}") + break + time.sleep(poll_interval_s) + else: + logger.warning(f"等待实验 {poll_filter_code} 完成超时(未到 status=80)") + + # 7. 启动 3-2-1 转运(2.32) + if transfer_source: + r = self.transfer_3_to_2_to_1( + source_wh_id=transfer_source.get("sourceWHID"), + source_x=transfer_source.get("sourcePosX", 1), + source_y=transfer_source.get("sourcePosY", 1), + source_z=transfer_source.get("sourcePosZ", 1), + ) + logger.info(f"3-2-1 转运结果: {r}") + + # 8. 1号站内部流程(占位) + self.start_station1_internal_flow() + + # 9. 1→2 转运(占位) + self.transfer_1_to_2() + + # 10. 样品/废料取出(2.28) + if takeout_order_id: + r = self.take_out(order_id=takeout_order_id) + logger.info(f"样品/废料取出结果: {r}") + + # 2.5 批量查询实验报告 + def order_list_v2(self, + timeType: str = "string", + beginTime: str = "", + endTime: str = "", + status: str = "", + filter: str = "物料转移任务", + skipCount: int = 0, + pageCount: int = 1, + sorting: str = "") -> Dict[str, Any]: + """ + 批量查询实验报告的详细信息 (2.5) + URL: /api/lims/order/order-list + 参数默认值和接口文档保持一致 + """ + data: Dict[str, Any] = { + "timeType": timeType, + "beginTime": beginTime, + "endTime": endTime, + "status": status, + "filter": filter, + "skipCount": skipCount, + "pageCount": pageCount, + "sorting": sorting + } + return self._post_lims("/api/lims/order/order-list", data) + + + def wait_for_transfer_task(self, timeout: int = 600, interval: int = 3) -> bool: + """ + 轮询查询物料转移任务是否成功完成 (status=80) + - timeout: 最大等待秒数 (默认600秒) + - interval: 轮询间隔秒数 (默认3秒) + 返回 True 表示找到并成功完成,False 表示超时未找到 + """ + now = datetime.now() + beginTime = now.strftime("%Y-%m-%dT%H:%M:%SZ") + endTime = (now + timedelta(minutes=5)).strftime("%Y-%m-%dT%H:%M:%SZ") + print(beginTime, endTime) + + deadline = time.time() + timeout + + while time.time() < deadline: + result = self.order_list_v2( + timeType="string", + beginTime=beginTime, + endTime=endTime, + status="", + filter="物料转移任务", + skipCount=0, + pageCount=1, + sorting="" + ) + print(result) + + items = result.get("data", {}).get("items", []) + for item in items: + name = item.get("name", "") + status = item.get("status") + if name.startswith("物料转移任务") and status == 80: + logger.info(f"硬件转移动作完成: {name}") + return True + + time.sleep(interval) + + logger.warning("超时未找到成功的物料转移任务") + return False + + +# -------------------------------- +if __name__ == "__main__": + ws = BioyondWorkstation() + # ws.scheduler_stop() + ws.scheduler_start() + logger.info("调度启动完成") + + # ws.scheduler_continue() + # 3.30 上料:读取模板 Excel 自动解析并 POST + r1 = ws.auto_feeding4to3_from_xlsx(r"C:\ML\GitHub\Uni-Lab-OS\unilabos\devices\workstation\bioyond_cell\样品导入模板 (8).xlsx") + ws.wait_for_transfer_task() + logger.info("4号箱向3号箱转运物料转移任务已完成") + + # ws.scheduler_start() + # print(r1["payload"]["data"]) # 调试模式下可直接看到要发的 JSON items + + # 新建实验 + res = ws.create_orders("C:/ML/GitHub/Uni-Lab-OS/unilabos/devices/workstation/bioyond_cell/2025092501.xlsx") + # ws.scheduler_start() + # print(res) + + #1号站启动 + ws.transfer_1_to_2() + ws.wait_for_transfer_task() + logger.info("1号站向2号站转移任务完成") + logger.info("全流程结束") + + # 3.31 下料:同理 + # r2 = ws.auto_batch_outbound_from_xlsx(r"C:/path/样品导入模板 (8).xlsx") + # print(r2["payload"]["data"]) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json new file mode 100644 index 0000000..29f6254 --- /dev/null +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json @@ -0,0 +1,14492 @@ +{ + "nodes": [ + { + "id": "bioyond_cell_workstation", + "name": "配液分液工站", + "children": [ + ], + "parent": null, + "type": "device", + "class": "bioyond_cell", + "config": { + "protocol_type": [], + "station_resource": {}, + + "bioyond_config": { + "api_key": "8A819E5C", + "api_host": "http://192.168.1.200:44388", + "debug_mode": false + } + }, + "data": {} + }, + { + "id": "BatteryStation", + "name": "扣电组装工作站", + "children": [ + "coin_cell_deck" + ], + "parent": null, + "type": "device", + "class": "bettery_station_registry", + "position": { + "x": 600, + "y": 400, + "z": 0 + }, + "config": { + "debug_mode": false, + "_comment": "protocol_type接外部工站固定写法字段,一般为空,deck写法也固定", + "protocol_type": [], + "deck": { + "data": { + "_resource_child_name": "coin_cell_deck", + "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" + } + }, + + "address": "192.168.1.20", + "port": 502 + }, + "data": {} + }, + { + "id": "coin_cell_deck", + "name": "coin_cell_deck", + "sample_id": null, + "children": [ + "zi_dan_jia", + "zi_dan_jia2", + "zi_dan_jia3", + "zi_dan_jia4", + "zi_dan_jia5", + "zi_dan_jia6", + "zi_dan_jia7", + "zi_dan_jia8", + "liaopan1", + "liaopan2", + "liaopan3", + "liaopan4", + "liaopan5", + "liaopan6", + "bottle_rack_3x4", + "bottle_rack_6x2", + "bottle_rack_6x2_2", + "tip_box_64", + "waste_tip_box" + ], + "parent": null, + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "CoincellDeck", + "size_x": 1620.0, + "size_y": 1270.0, + "size_z": 500.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "coin_cell_deck", + "barcode": null + }, + "data": {} + }, + { + "id": "zi_dan_jia", + "name": "zi_dan_jia", + "sample_id": null, + "children": [ + "zi_dan_jia_clipmagazinehole_0_0", + "zi_dan_jia_clipmagazinehole_0_1", + "zi_dan_jia_clipmagazinehole_1_0", + "zi_dan_jia_clipmagazinehole_1_1" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1400, + "y": 50, + "z": 0 + }, + "config": { + "type": "ClipMagazine_four", + "size_x": 80, + "size_y": 80, + "size_z": 10, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_four", + "model": null, + "barcode": null, + "ordering": { + "A1": "zi_dan_jia_clipmagazinehole_0_0", + "B1": "zi_dan_jia_clipmagazinehole_0_1", + "A2": "zi_dan_jia_clipmagazinehole_1_0", + "B2": "zi_dan_jia_clipmagazinehole_1_1" + }, + "hole_diameter": 14.0, + "hole_depth": 10.0, + "max_sheets_per_hole": 100 + }, + "data": {} + }, + { + "id": "zi_dan_jia_clipmagazinehole_0_0", + "name": "zi_dan_jia_clipmagazinehole_0_0", + "sample_id": null, + "children": [ + "zi_dan_jia2_jipian_0" + ], + "parent": "zi_dan_jia", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia2_jipian_0", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia_clipmagazinehole_0_0" + } + ] + } + }, + { + "id": "zi_dan_jia2_jipian_0", + "name": "zi_dan_jia2_jipian_0", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia_clipmagazinehole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia_clipmagazinehole_0_1", + "name": "zi_dan_jia_clipmagazinehole_0_1", + "sample_id": null, + "children": [ + "zi_dan_jia2_jipian_1" + ], + "parent": "zi_dan_jia", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia2_jipian_1", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia_clipmagazinehole_0_1" + } + ] + } + }, + { + "id": "zi_dan_jia2_jipian_1", + "name": "zi_dan_jia2_jipian_1", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia_clipmagazinehole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia_clipmagazinehole_1_0", + "name": "zi_dan_jia_clipmagazinehole_1_0", + "sample_id": null, + "children": [ + "zi_dan_jia2_jipian_2" + ], + "parent": "zi_dan_jia", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia2_jipian_2", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia_clipmagazinehole_1_0" + } + ] + } + }, + { + "id": "zi_dan_jia2_jipian_2", + "name": "zi_dan_jia2_jipian_2", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia_clipmagazinehole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia_clipmagazinehole_1_1", + "name": "zi_dan_jia_clipmagazinehole_1_1", + "sample_id": null, + "children": [ + "zi_dan_jia2_jipian_3" + ], + "parent": "zi_dan_jia", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia2_jipian_3", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia_clipmagazinehole_1_1" + } + ] + } + }, + { + "id": "zi_dan_jia2_jipian_3", + "name": "zi_dan_jia2_jipian_3", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia_clipmagazinehole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia2", + "name": "zi_dan_jia2", + "sample_id": null, + "children": [ + "zi_dan_jia2_clipmagazinehole_0_0", + "zi_dan_jia2_clipmagazinehole_0_1", + "zi_dan_jia2_clipmagazinehole_1_0", + "zi_dan_jia2_clipmagazinehole_1_1" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1600, + "y": 200, + "z": 0 + }, + "config": { + "type": "ClipMagazine_four", + "size_x": 80, + "size_y": 80, + "size_z": 10, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_four", + "model": null, + "barcode": null, + "ordering": { + "A1": "zi_dan_jia2_clipmagazinehole_0_0", + "B1": "zi_dan_jia2_clipmagazinehole_0_1", + "A2": "zi_dan_jia2_clipmagazinehole_1_0", + "B2": "zi_dan_jia2_clipmagazinehole_1_1" + }, + "hole_diameter": 14.0, + "hole_depth": 10.0, + "max_sheets_per_hole": 100 + }, + "data": {} + }, + { + "id": "zi_dan_jia2_clipmagazinehole_0_0", + "name": "zi_dan_jia2_clipmagazinehole_0_0", + "sample_id": null, + "children": [ + "zi_dan_jia_jipian_0" + ], + "parent": "zi_dan_jia2", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia_jipian_0", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia2_clipmagazinehole_0_0" + } + ] + } + }, + { + "id": "zi_dan_jia_jipian_0", + "name": "zi_dan_jia_jipian_0", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia2_clipmagazinehole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia2_clipmagazinehole_0_1", + "name": "zi_dan_jia2_clipmagazinehole_0_1", + "sample_id": null, + "children": [ + "zi_dan_jia_jipian_1" + ], + "parent": "zi_dan_jia2", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia_jipian_1", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia2_clipmagazinehole_0_1" + } + ] + } + }, + { + "id": "zi_dan_jia_jipian_1", + "name": "zi_dan_jia_jipian_1", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia2_clipmagazinehole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia2_clipmagazinehole_1_0", + "name": "zi_dan_jia2_clipmagazinehole_1_0", + "sample_id": null, + "children": [ + "zi_dan_jia_jipian_2" + ], + "parent": "zi_dan_jia2", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia_jipian_2", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia2_clipmagazinehole_1_0" + } + ] + } + }, + { + "id": "zi_dan_jia_jipian_2", + "name": "zi_dan_jia_jipian_2", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia2_clipmagazinehole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia2_clipmagazinehole_1_1", + "name": "zi_dan_jia2_clipmagazinehole_1_1", + "sample_id": null, + "children": [ + "zi_dan_jia_jipian_3" + ], + "parent": "zi_dan_jia2", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia_jipian_3", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia2_clipmagazinehole_1_1" + } + ] + } + }, + { + "id": "zi_dan_jia_jipian_3", + "name": "zi_dan_jia_jipian_3", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia2_clipmagazinehole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia3", + "name": "zi_dan_jia3", + "sample_id": null, + "children": [ + "zi_dan_jia3_clipmagazinehole_0_0", + "zi_dan_jia3_clipmagazinehole_0_1", + "zi_dan_jia3_clipmagazinehole_1_0", + "zi_dan_jia3_clipmagazinehole_1_1", + "zi_dan_jia3_clipmagazinehole_2_0", + "zi_dan_jia3_clipmagazinehole_2_1" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1500, + "y": 200, + "z": 0 + }, + "config": { + "type": "ClipMagazine", + "size_x": 80, + "size_y": 80, + "size_z": 10, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine", + "model": null, + "barcode": null, + "ordering": { + "A1": "zi_dan_jia3_clipmagazinehole_0_0", + "B1": "zi_dan_jia3_clipmagazinehole_0_1", + "A2": "zi_dan_jia3_clipmagazinehole_1_0", + "B2": "zi_dan_jia3_clipmagazinehole_1_1", + "A3": "zi_dan_jia3_clipmagazinehole_2_0", + "B3": "zi_dan_jia3_clipmagazinehole_2_1" + }, + "hole_diameter": 14.0, + "hole_depth": 10.0, + "max_sheets_per_hole": 100 + }, + "data": {} + }, + { + "id": "zi_dan_jia3_clipmagazinehole_0_0", + "name": "zi_dan_jia3_clipmagazinehole_0_0", + "sample_id": null, + "children": [ + "zi_dan_jia3_jipian_0" + ], + "parent": "zi_dan_jia3", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia3_jipian_0", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia3_clipmagazinehole_0_0" + } + ] + } + }, + { + "id": "zi_dan_jia3_jipian_0", + "name": "zi_dan_jia3_jipian_0", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia3_clipmagazinehole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia3_clipmagazinehole_0_1", + "name": "zi_dan_jia3_clipmagazinehole_0_1", + "sample_id": null, + "children": [ + "zi_dan_jia3_jipian_1" + ], + "parent": "zi_dan_jia3", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia3_jipian_1", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia3_clipmagazinehole_0_1" + } + ] + } + }, + { + "id": "zi_dan_jia3_jipian_1", + "name": "zi_dan_jia3_jipian_1", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia3_clipmagazinehole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia3_clipmagazinehole_1_0", + "name": "zi_dan_jia3_clipmagazinehole_1_0", + "sample_id": null, + "children": [ + "zi_dan_jia3_jipian_2" + ], + "parent": "zi_dan_jia3", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia3_jipian_2", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia3_clipmagazinehole_1_0" + } + ] + } + }, + { + "id": "zi_dan_jia3_jipian_2", + "name": "zi_dan_jia3_jipian_2", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia3_clipmagazinehole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia3_clipmagazinehole_1_1", + "name": "zi_dan_jia3_clipmagazinehole_1_1", + "sample_id": null, + "children": [ + "zi_dan_jia3_jipian_3" + ], + "parent": "zi_dan_jia3", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia3_jipian_3", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia3_clipmagazinehole_1_1" + } + ] + } + }, + { + "id": "zi_dan_jia3_jipian_3", + "name": "zi_dan_jia3_jipian_3", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia3_clipmagazinehole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia3_clipmagazinehole_2_0", + "name": "zi_dan_jia3_clipmagazinehole_2_0", + "sample_id": null, + "children": [ + "zi_dan_jia3_jipian_4" + ], + "parent": "zi_dan_jia3", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia3_jipian_4", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia3_clipmagazinehole_2_0" + } + ] + } + }, + { + "id": "zi_dan_jia3_jipian_4", + "name": "zi_dan_jia3_jipian_4", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia3_clipmagazinehole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia3_clipmagazinehole_2_1", + "name": "zi_dan_jia3_clipmagazinehole_2_1", + "sample_id": null, + "children": [ + "zi_dan_jia3_jipian_5" + ], + "parent": "zi_dan_jia3", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia3_jipian_5", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia3_clipmagazinehole_2_1" + } + ] + } + }, + { + "id": "zi_dan_jia3_jipian_5", + "name": "zi_dan_jia3_jipian_5", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia3_clipmagazinehole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia4", + "name": "zi_dan_jia4", + "sample_id": null, + "children": [ + "zi_dan_jia4_clipmagazinehole_0_0", + "zi_dan_jia4_clipmagazinehole_0_1", + "zi_dan_jia4_clipmagazinehole_1_0", + "zi_dan_jia4_clipmagazinehole_1_1", + "zi_dan_jia4_clipmagazinehole_2_0", + "zi_dan_jia4_clipmagazinehole_2_1" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1500, + "y": 300, + "z": 0 + }, + "config": { + "type": "ClipMagazine", + "size_x": 80, + "size_y": 80, + "size_z": 10, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine", + "model": null, + "barcode": null, + "ordering": { + "A1": "zi_dan_jia4_clipmagazinehole_0_0", + "B1": "zi_dan_jia4_clipmagazinehole_0_1", + "A2": "zi_dan_jia4_clipmagazinehole_1_0", + "B2": "zi_dan_jia4_clipmagazinehole_1_1", + "A3": "zi_dan_jia4_clipmagazinehole_2_0", + "B3": "zi_dan_jia4_clipmagazinehole_2_1" + }, + "hole_diameter": 14.0, + "hole_depth": 10.0, + "max_sheets_per_hole": 100 + }, + "data": {} + }, + { + "id": "zi_dan_jia4_clipmagazinehole_0_0", + "name": "zi_dan_jia4_clipmagazinehole_0_0", + "sample_id": null, + "children": [ + "zi_dan_jia4_jipian_0" + ], + "parent": "zi_dan_jia4", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia4_jipian_0", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia4_clipmagazinehole_0_0" + } + ] + } + }, + { + "id": "zi_dan_jia4_jipian_0", + "name": "zi_dan_jia4_jipian_0", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia4_clipmagazinehole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia4_clipmagazinehole_0_1", + "name": "zi_dan_jia4_clipmagazinehole_0_1", + "sample_id": null, + "children": [ + "zi_dan_jia4_jipian_1" + ], + "parent": "zi_dan_jia4", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia4_jipian_1", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia4_clipmagazinehole_0_1" + } + ] + } + }, + { + "id": "zi_dan_jia4_jipian_1", + "name": "zi_dan_jia4_jipian_1", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia4_clipmagazinehole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia4_clipmagazinehole_1_0", + "name": "zi_dan_jia4_clipmagazinehole_1_0", + "sample_id": null, + "children": [ + "zi_dan_jia4_jipian_2" + ], + "parent": "zi_dan_jia4", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia4_jipian_2", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia4_clipmagazinehole_1_0" + } + ] + } + }, + { + "id": "zi_dan_jia4_jipian_2", + "name": "zi_dan_jia4_jipian_2", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia4_clipmagazinehole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia4_clipmagazinehole_1_1", + "name": "zi_dan_jia4_clipmagazinehole_1_1", + "sample_id": null, + "children": [ + "zi_dan_jia4_jipian_3" + ], + "parent": "zi_dan_jia4", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia4_jipian_3", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia4_clipmagazinehole_1_1" + } + ] + } + }, + { + "id": "zi_dan_jia4_jipian_3", + "name": "zi_dan_jia4_jipian_3", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia4_clipmagazinehole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia4_clipmagazinehole_2_0", + "name": "zi_dan_jia4_clipmagazinehole_2_0", + "sample_id": null, + "children": [ + "zi_dan_jia4_jipian_4" + ], + "parent": "zi_dan_jia4", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia4_jipian_4", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia4_clipmagazinehole_2_0" + } + ] + } + }, + { + "id": "zi_dan_jia4_jipian_4", + "name": "zi_dan_jia4_jipian_4", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia4_clipmagazinehole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia4_clipmagazinehole_2_1", + "name": "zi_dan_jia4_clipmagazinehole_2_1", + "sample_id": null, + "children": [ + "zi_dan_jia4_jipian_5" + ], + "parent": "zi_dan_jia4", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia4_jipian_5", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia4_clipmagazinehole_2_1" + } + ] + } + }, + { + "id": "zi_dan_jia4_jipian_5", + "name": "zi_dan_jia4_jipian_5", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia4_clipmagazinehole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia5", + "name": "zi_dan_jia5", + "sample_id": null, + "children": [ + "zi_dan_jia5_clipmagazinehole_0_0", + "zi_dan_jia5_clipmagazinehole_0_1", + "zi_dan_jia5_clipmagazinehole_1_0", + "zi_dan_jia5_clipmagazinehole_1_1", + "zi_dan_jia5_clipmagazinehole_2_0", + "zi_dan_jia5_clipmagazinehole_2_1" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1600, + "y": 300, + "z": 0 + }, + "config": { + "type": "ClipMagazine", + "size_x": 80, + "size_y": 80, + "size_z": 10, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine", + "model": null, + "barcode": null, + "ordering": { + "A1": "zi_dan_jia5_clipmagazinehole_0_0", + "B1": "zi_dan_jia5_clipmagazinehole_0_1", + "A2": "zi_dan_jia5_clipmagazinehole_1_0", + "B2": "zi_dan_jia5_clipmagazinehole_1_1", + "A3": "zi_dan_jia5_clipmagazinehole_2_0", + "B3": "zi_dan_jia5_clipmagazinehole_2_1" + }, + "hole_diameter": 14.0, + "hole_depth": 10.0, + "max_sheets_per_hole": 100 + }, + "data": {} + }, + { + "id": "zi_dan_jia5_clipmagazinehole_0_0", + "name": "zi_dan_jia5_clipmagazinehole_0_0", + "sample_id": null, + "children": [ + "zi_dan_jia5_jipian_0" + ], + "parent": "zi_dan_jia5", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia5_jipian_0", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia5_clipmagazinehole_0_0" + } + ] + } + }, + { + "id": "zi_dan_jia5_jipian_0", + "name": "zi_dan_jia5_jipian_0", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia5_clipmagazinehole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia5_clipmagazinehole_0_1", + "name": "zi_dan_jia5_clipmagazinehole_0_1", + "sample_id": null, + "children": [ + "zi_dan_jia5_jipian_1" + ], + "parent": "zi_dan_jia5", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia5_jipian_1", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia5_clipmagazinehole_0_1" + } + ] + } + }, + { + "id": "zi_dan_jia5_jipian_1", + "name": "zi_dan_jia5_jipian_1", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia5_clipmagazinehole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia5_clipmagazinehole_1_0", + "name": "zi_dan_jia5_clipmagazinehole_1_0", + "sample_id": null, + "children": [ + "zi_dan_jia5_jipian_2" + ], + "parent": "zi_dan_jia5", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia5_jipian_2", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia5_clipmagazinehole_1_0" + } + ] + } + }, + { + "id": "zi_dan_jia5_jipian_2", + "name": "zi_dan_jia5_jipian_2", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia5_clipmagazinehole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia5_clipmagazinehole_1_1", + "name": "zi_dan_jia5_clipmagazinehole_1_1", + "sample_id": null, + "children": [ + "zi_dan_jia5_jipian_3" + ], + "parent": "zi_dan_jia5", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia5_jipian_3", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia5_clipmagazinehole_1_1" + } + ] + } + }, + { + "id": "zi_dan_jia5_jipian_3", + "name": "zi_dan_jia5_jipian_3", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia5_clipmagazinehole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia5_clipmagazinehole_2_0", + "name": "zi_dan_jia5_clipmagazinehole_2_0", + "sample_id": null, + "children": [ + "zi_dan_jia5_jipian_4" + ], + "parent": "zi_dan_jia5", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia5_jipian_4", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia5_clipmagazinehole_2_0" + } + ] + } + }, + { + "id": "zi_dan_jia5_jipian_4", + "name": "zi_dan_jia5_jipian_4", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia5_clipmagazinehole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia5_clipmagazinehole_2_1", + "name": "zi_dan_jia5_clipmagazinehole_2_1", + "sample_id": null, + "children": [ + "zi_dan_jia5_jipian_5" + ], + "parent": "zi_dan_jia5", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia5_jipian_5", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia5_clipmagazinehole_2_1" + } + ] + } + }, + { + "id": "zi_dan_jia5_jipian_5", + "name": "zi_dan_jia5_jipian_5", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia5_clipmagazinehole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia6", + "name": "zi_dan_jia6", + "sample_id": null, + "children": [ + "zi_dan_jia6_clipmagazinehole_0_0", + "zi_dan_jia6_clipmagazinehole_0_1", + "zi_dan_jia6_clipmagazinehole_1_0", + "zi_dan_jia6_clipmagazinehole_1_1", + "zi_dan_jia6_clipmagazinehole_2_0", + "zi_dan_jia6_clipmagazinehole_2_1" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1530, + "y": 500, + "z": 0 + }, + "config": { + "type": "ClipMagazine", + "size_x": 80, + "size_y": 80, + "size_z": 10, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine", + "model": null, + "barcode": null, + "ordering": { + "A1": "zi_dan_jia6_clipmagazinehole_0_0", + "B1": "zi_dan_jia6_clipmagazinehole_0_1", + "A2": "zi_dan_jia6_clipmagazinehole_1_0", + "B2": "zi_dan_jia6_clipmagazinehole_1_1", + "A3": "zi_dan_jia6_clipmagazinehole_2_0", + "B3": "zi_dan_jia6_clipmagazinehole_2_1" + }, + "hole_diameter": 14.0, + "hole_depth": 10.0, + "max_sheets_per_hole": 100 + }, + "data": {} + }, + { + "id": "zi_dan_jia6_clipmagazinehole_0_0", + "name": "zi_dan_jia6_clipmagazinehole_0_0", + "sample_id": null, + "children": [ + "zi_dan_jia6_jipian_0" + ], + "parent": "zi_dan_jia6", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia6_jipian_0", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia6_clipmagazinehole_0_0" + } + ] + } + }, + { + "id": "zi_dan_jia6_jipian_0", + "name": "zi_dan_jia6_jipian_0", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia6_clipmagazinehole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia6_clipmagazinehole_0_1", + "name": "zi_dan_jia6_clipmagazinehole_0_1", + "sample_id": null, + "children": [ + "zi_dan_jia6_jipian_1" + ], + "parent": "zi_dan_jia6", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia6_jipian_1", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia6_clipmagazinehole_0_1" + } + ] + } + }, + { + "id": "zi_dan_jia6_jipian_1", + "name": "zi_dan_jia6_jipian_1", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia6_clipmagazinehole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia6_clipmagazinehole_1_0", + "name": "zi_dan_jia6_clipmagazinehole_1_0", + "sample_id": null, + "children": [ + "zi_dan_jia6_jipian_2" + ], + "parent": "zi_dan_jia6", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia6_jipian_2", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia6_clipmagazinehole_1_0" + } + ] + } + }, + { + "id": "zi_dan_jia6_jipian_2", + "name": "zi_dan_jia6_jipian_2", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia6_clipmagazinehole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia6_clipmagazinehole_1_1", + "name": "zi_dan_jia6_clipmagazinehole_1_1", + "sample_id": null, + "children": [ + "zi_dan_jia6_jipian_3" + ], + "parent": "zi_dan_jia6", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia6_jipian_3", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia6_clipmagazinehole_1_1" + } + ] + } + }, + { + "id": "zi_dan_jia6_jipian_3", + "name": "zi_dan_jia6_jipian_3", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia6_clipmagazinehole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia6_clipmagazinehole_2_0", + "name": "zi_dan_jia6_clipmagazinehole_2_0", + "sample_id": null, + "children": [ + "zi_dan_jia6_jipian_4" + ], + "parent": "zi_dan_jia6", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia6_jipian_4", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia6_clipmagazinehole_2_0" + } + ] + } + }, + { + "id": "zi_dan_jia6_jipian_4", + "name": "zi_dan_jia6_jipian_4", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia6_clipmagazinehole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia6_clipmagazinehole_2_1", + "name": "zi_dan_jia6_clipmagazinehole_2_1", + "sample_id": null, + "children": [ + "zi_dan_jia6_jipian_5" + ], + "parent": "zi_dan_jia6", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia6_jipian_5", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia6_clipmagazinehole_2_1" + } + ] + } + }, + { + "id": "zi_dan_jia6_jipian_5", + "name": "zi_dan_jia6_jipian_5", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia6_clipmagazinehole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia7", + "name": "zi_dan_jia7", + "sample_id": null, + "children": [ + "zi_dan_jia7_clipmagazinehole_0_0", + "zi_dan_jia7_clipmagazinehole_0_1", + "zi_dan_jia7_clipmagazinehole_1_0", + "zi_dan_jia7_clipmagazinehole_1_1", + "zi_dan_jia7_clipmagazinehole_2_0", + "zi_dan_jia7_clipmagazinehole_2_1" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1180, + "y": 400, + "z": 0 + }, + "config": { + "type": "ClipMagazine", + "size_x": 80, + "size_y": 80, + "size_z": 10, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine", + "model": null, + "barcode": null, + "ordering": { + "A1": "zi_dan_jia7_clipmagazinehole_0_0", + "B1": "zi_dan_jia7_clipmagazinehole_0_1", + "A2": "zi_dan_jia7_clipmagazinehole_1_0", + "B2": "zi_dan_jia7_clipmagazinehole_1_1", + "A3": "zi_dan_jia7_clipmagazinehole_2_0", + "B3": "zi_dan_jia7_clipmagazinehole_2_1" + }, + "hole_diameter": 14.0, + "hole_depth": 10.0, + "max_sheets_per_hole": 100 + }, + "data": {} + }, + { + "id": "zi_dan_jia7_clipmagazinehole_0_0", + "name": "zi_dan_jia7_clipmagazinehole_0_0", + "sample_id": null, + "children": [ + "zi_dan_jia7_jipian_0" + ], + "parent": "zi_dan_jia7", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia7_jipian_0", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia7_clipmagazinehole_0_0" + } + ] + } + }, + { + "id": "zi_dan_jia7_jipian_0", + "name": "zi_dan_jia7_jipian_0", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia7_clipmagazinehole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia7_clipmagazinehole_0_1", + "name": "zi_dan_jia7_clipmagazinehole_0_1", + "sample_id": null, + "children": [ + "zi_dan_jia7_jipian_1" + ], + "parent": "zi_dan_jia7", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia7_jipian_1", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia7_clipmagazinehole_0_1" + } + ] + } + }, + { + "id": "zi_dan_jia7_jipian_1", + "name": "zi_dan_jia7_jipian_1", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia7_clipmagazinehole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia7_clipmagazinehole_1_0", + "name": "zi_dan_jia7_clipmagazinehole_1_0", + "sample_id": null, + "children": [ + "zi_dan_jia7_jipian_2" + ], + "parent": "zi_dan_jia7", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia7_jipian_2", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia7_clipmagazinehole_1_0" + } + ] + } + }, + { + "id": "zi_dan_jia7_jipian_2", + "name": "zi_dan_jia7_jipian_2", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia7_clipmagazinehole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia7_clipmagazinehole_1_1", + "name": "zi_dan_jia7_clipmagazinehole_1_1", + "sample_id": null, + "children": [ + "zi_dan_jia7_jipian_3" + ], + "parent": "zi_dan_jia7", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia7_jipian_3", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia7_clipmagazinehole_1_1" + } + ] + } + }, + { + "id": "zi_dan_jia7_jipian_3", + "name": "zi_dan_jia7_jipian_3", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia7_clipmagazinehole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia7_clipmagazinehole_2_0", + "name": "zi_dan_jia7_clipmagazinehole_2_0", + "sample_id": null, + "children": [ + "zi_dan_jia7_jipian_4" + ], + "parent": "zi_dan_jia7", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia7_jipian_4", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia7_clipmagazinehole_2_0" + } + ] + } + }, + { + "id": "zi_dan_jia7_jipian_4", + "name": "zi_dan_jia7_jipian_4", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia7_clipmagazinehole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia7_clipmagazinehole_2_1", + "name": "zi_dan_jia7_clipmagazinehole_2_1", + "sample_id": null, + "children": [ + "zi_dan_jia7_jipian_5" + ], + "parent": "zi_dan_jia7", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia7_jipian_5", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia7_clipmagazinehole_2_1" + } + ] + } + }, + { + "id": "zi_dan_jia7_jipian_5", + "name": "zi_dan_jia7_jipian_5", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia7_clipmagazinehole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia8", + "name": "zi_dan_jia8", + "sample_id": null, + "children": [ + "zi_dan_jia8_clipmagazinehole_0_0", + "zi_dan_jia8_clipmagazinehole_0_1", + "zi_dan_jia8_clipmagazinehole_1_0", + "zi_dan_jia8_clipmagazinehole_1_1", + "zi_dan_jia8_clipmagazinehole_2_0", + "zi_dan_jia8_clipmagazinehole_2_1" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1280, + "y": 400, + "z": 0 + }, + "config": { + "type": "ClipMagazine", + "size_x": 80, + "size_y": 80, + "size_z": 10, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine", + "model": null, + "barcode": null, + "ordering": { + "A1": "zi_dan_jia8_clipmagazinehole_0_0", + "B1": "zi_dan_jia8_clipmagazinehole_0_1", + "A2": "zi_dan_jia8_clipmagazinehole_1_0", + "B2": "zi_dan_jia8_clipmagazinehole_1_1", + "A3": "zi_dan_jia8_clipmagazinehole_2_0", + "B3": "zi_dan_jia8_clipmagazinehole_2_1" + }, + "hole_diameter": 14.0, + "hole_depth": 10.0, + "max_sheets_per_hole": 100 + }, + "data": {} + }, + { + "id": "zi_dan_jia8_clipmagazinehole_0_0", + "name": "zi_dan_jia8_clipmagazinehole_0_0", + "sample_id": null, + "children": [ + "zi_dan_jia8_jipian_0" + ], + "parent": "zi_dan_jia8", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia8_jipian_0", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia8_clipmagazinehole_0_0" + } + ] + } + }, + { + "id": "zi_dan_jia8_jipian_0", + "name": "zi_dan_jia8_jipian_0", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia8_clipmagazinehole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia8_clipmagazinehole_0_1", + "name": "zi_dan_jia8_clipmagazinehole_0_1", + "sample_id": null, + "children": [ + "zi_dan_jia8_jipian_1" + ], + "parent": "zi_dan_jia8", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia8_jipian_1", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia8_clipmagazinehole_0_1" + } + ] + } + }, + { + "id": "zi_dan_jia8_jipian_1", + "name": "zi_dan_jia8_jipian_1", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia8_clipmagazinehole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia8_clipmagazinehole_1_0", + "name": "zi_dan_jia8_clipmagazinehole_1_0", + "sample_id": null, + "children": [ + "zi_dan_jia8_jipian_2" + ], + "parent": "zi_dan_jia8", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia8_jipian_2", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia8_clipmagazinehole_1_0" + } + ] + } + }, + { + "id": "zi_dan_jia8_jipian_2", + "name": "zi_dan_jia8_jipian_2", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia8_clipmagazinehole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia8_clipmagazinehole_1_1", + "name": "zi_dan_jia8_clipmagazinehole_1_1", + "sample_id": null, + "children": [ + "zi_dan_jia8_jipian_3" + ], + "parent": "zi_dan_jia8", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia8_jipian_3", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia8_clipmagazinehole_1_1" + } + ] + } + }, + { + "id": "zi_dan_jia8_jipian_3", + "name": "zi_dan_jia8_jipian_3", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia8_clipmagazinehole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia8_clipmagazinehole_2_0", + "name": "zi_dan_jia8_clipmagazinehole_2_0", + "sample_id": null, + "children": [ + "zi_dan_jia8_jipian_4" + ], + "parent": "zi_dan_jia8", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia8_jipian_4", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia8_clipmagazinehole_2_0" + } + ] + } + }, + { + "id": "zi_dan_jia8_jipian_4", + "name": "zi_dan_jia8_jipian_4", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia8_clipmagazinehole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia8_clipmagazinehole_2_1", + "name": "zi_dan_jia8_clipmagazinehole_2_1", + "sample_id": null, + "children": [ + "zi_dan_jia8_jipian_5" + ], + "parent": "zi_dan_jia8", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia8_jipian_5", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia8_clipmagazinehole_2_1" + } + ] + } + }, + { + "id": "zi_dan_jia8_jipian_5", + "name": "zi_dan_jia8_jipian_5", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia8_clipmagazinehole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1", + "name": "liaopan1", + "sample_id": null, + "children": [ + "liaopan1_materialhole_0_0", + "liaopan1_materialhole_0_1", + "liaopan1_materialhole_0_2", + "liaopan1_materialhole_0_3", + "liaopan1_materialhole_1_0", + "liaopan1_materialhole_1_1", + "liaopan1_materialhole_1_2", + "liaopan1_materialhole_1_3", + "liaopan1_materialhole_2_0", + "liaopan1_materialhole_2_1", + "liaopan1_materialhole_2_2", + "liaopan1_materialhole_2_3", + "liaopan1_materialhole_3_0", + "liaopan1_materialhole_3_1", + "liaopan1_materialhole_3_2", + "liaopan1_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1010, + "y": 50, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120, + "size_y": 100, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan1_materialhole_0_0", + "B1": "liaopan1_materialhole_0_1", + "C1": "liaopan1_materialhole_0_2", + "D1": "liaopan1_materialhole_0_3", + "A2": "liaopan1_materialhole_1_0", + "B2": "liaopan1_materialhole_1_1", + "C2": "liaopan1_materialhole_1_2", + "D2": "liaopan1_materialhole_1_3", + "A3": "liaopan1_materialhole_2_0", + "B3": "liaopan1_materialhole_2_1", + "C3": "liaopan1_materialhole_2_2", + "D3": "liaopan1_materialhole_2_3", + "A4": "liaopan1_materialhole_3_0", + "B4": "liaopan1_materialhole_3_1", + "C4": "liaopan1_materialhole_3_2", + "D4": "liaopan1_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan1_materialhole_0_0", + "name": "liaopan1_materialhole_0_0", + "sample_id": null, + "children": [ + "liaopan1_jipian_0" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_0", + "name": "liaopan1_jipian_0", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_1", + "name": "liaopan1_materialhole_0_1", + "sample_id": null, + "children": [ + "liaopan1_jipian_1" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_1", + "name": "liaopan1_jipian_1", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_2", + "name": "liaopan1_materialhole_0_2", + "sample_id": null, + "children": [ + "liaopan1_jipian_2" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_2", + "name": "liaopan1_jipian_2", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_3", + "name": "liaopan1_materialhole_0_3", + "sample_id": null, + "children": [ + "liaopan1_jipian_3" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_3", + "name": "liaopan1_jipian_3", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_0", + "name": "liaopan1_materialhole_1_0", + "sample_id": null, + "children": [ + "liaopan1_jipian_4" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_4", + "name": "liaopan1_jipian_4", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_1", + "name": "liaopan1_materialhole_1_1", + "sample_id": null, + "children": [ + "liaopan1_jipian_5" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_5", + "name": "liaopan1_jipian_5", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_2", + "name": "liaopan1_materialhole_1_2", + "sample_id": null, + "children": [ + "liaopan1_jipian_6" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_6", + "name": "liaopan1_jipian_6", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_3", + "name": "liaopan1_materialhole_1_3", + "sample_id": null, + "children": [ + "liaopan1_jipian_7" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_7", + "name": "liaopan1_jipian_7", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_0", + "name": "liaopan1_materialhole_2_0", + "sample_id": null, + "children": [ + "liaopan1_jipian_8" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_8", + "name": "liaopan1_jipian_8", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_1", + "name": "liaopan1_materialhole_2_1", + "sample_id": null, + "children": [ + "liaopan1_jipian_9" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_9", + "name": "liaopan1_jipian_9", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_2", + "name": "liaopan1_materialhole_2_2", + "sample_id": null, + "children": [ + "liaopan1_jipian_10" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_10", + "name": "liaopan1_jipian_10", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_3", + "name": "liaopan1_materialhole_2_3", + "sample_id": null, + "children": [ + "liaopan1_jipian_11" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_11", + "name": "liaopan1_jipian_11", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_0", + "name": "liaopan1_materialhole_3_0", + "sample_id": null, + "children": [ + "liaopan1_jipian_12" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_12", + "name": "liaopan1_jipian_12", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_1", + "name": "liaopan1_materialhole_3_1", + "sample_id": null, + "children": [ + "liaopan1_jipian_13" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_13", + "name": "liaopan1_jipian_13", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_2", + "name": "liaopan1_materialhole_3_2", + "sample_id": null, + "children": [ + "liaopan1_jipian_14" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_14", + "name": "liaopan1_jipian_14", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_3", + "name": "liaopan1_materialhole_3_3", + "sample_id": null, + "children": [ + "liaopan1_jipian_15" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_15", + "name": "liaopan1_jipian_15", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan2", + "name": "liaopan2", + "sample_id": null, + "children": [ + "liaopan2_materialhole_0_0", + "liaopan2_materialhole_0_1", + "liaopan2_materialhole_0_2", + "liaopan2_materialhole_0_3", + "liaopan2_materialhole_1_0", + "liaopan2_materialhole_1_1", + "liaopan2_materialhole_1_2", + "liaopan2_materialhole_1_3", + "liaopan2_materialhole_2_0", + "liaopan2_materialhole_2_1", + "liaopan2_materialhole_2_2", + "liaopan2_materialhole_2_3", + "liaopan2_materialhole_3_0", + "liaopan2_materialhole_3_1", + "liaopan2_materialhole_3_2", + "liaopan2_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1130, + "y": 50, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120, + "size_y": 100, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan2_materialhole_0_0", + "B1": "liaopan2_materialhole_0_1", + "C1": "liaopan2_materialhole_0_2", + "D1": "liaopan2_materialhole_0_3", + "A2": "liaopan2_materialhole_1_0", + "B2": "liaopan2_materialhole_1_1", + "C2": "liaopan2_materialhole_1_2", + "D2": "liaopan2_materialhole_1_3", + "A3": "liaopan2_materialhole_2_0", + "B3": "liaopan2_materialhole_2_1", + "C3": "liaopan2_materialhole_2_2", + "D3": "liaopan2_materialhole_2_3", + "A4": "liaopan2_materialhole_3_0", + "B4": "liaopan2_materialhole_3_1", + "C4": "liaopan2_materialhole_3_2", + "D4": "liaopan2_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan2_materialhole_0_0", + "name": "liaopan2_materialhole_0_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_1", + "name": "liaopan2_materialhole_0_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_2", + "name": "liaopan2_materialhole_0_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_3", + "name": "liaopan2_materialhole_0_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_0", + "name": "liaopan2_materialhole_1_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_1", + "name": "liaopan2_materialhole_1_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_2", + "name": "liaopan2_materialhole_1_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_3", + "name": "liaopan2_materialhole_1_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_0", + "name": "liaopan2_materialhole_2_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_1", + "name": "liaopan2_materialhole_2_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_2", + "name": "liaopan2_materialhole_2_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_3", + "name": "liaopan2_materialhole_2_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_0", + "name": "liaopan2_materialhole_3_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_1", + "name": "liaopan2_materialhole_3_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_2", + "name": "liaopan2_materialhole_3_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_3", + "name": "liaopan2_materialhole_3_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3", + "name": "liaopan3", + "sample_id": null, + "children": [ + "liaopan3_materialhole_0_0", + "liaopan3_materialhole_0_1", + "liaopan3_materialhole_0_2", + "liaopan3_materialhole_0_3", + "liaopan3_materialhole_1_0", + "liaopan3_materialhole_1_1", + "liaopan3_materialhole_1_2", + "liaopan3_materialhole_1_3", + "liaopan3_materialhole_2_0", + "liaopan3_materialhole_2_1", + "liaopan3_materialhole_2_2", + "liaopan3_materialhole_2_3", + "liaopan3_materialhole_3_0", + "liaopan3_materialhole_3_1", + "liaopan3_materialhole_3_2", + "liaopan3_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1250, + "y": 50, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120, + "size_y": 100, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan3_materialhole_0_0", + "B1": "liaopan3_materialhole_0_1", + "C1": "liaopan3_materialhole_0_2", + "D1": "liaopan3_materialhole_0_3", + "A2": "liaopan3_materialhole_1_0", + "B2": "liaopan3_materialhole_1_1", + "C2": "liaopan3_materialhole_1_2", + "D2": "liaopan3_materialhole_1_3", + "A3": "liaopan3_materialhole_2_0", + "B3": "liaopan3_materialhole_2_1", + "C3": "liaopan3_materialhole_2_2", + "D3": "liaopan3_materialhole_2_3", + "A4": "liaopan3_materialhole_3_0", + "B4": "liaopan3_materialhole_3_1", + "C4": "liaopan3_materialhole_3_2", + "D4": "liaopan3_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan3_materialhole_0_0", + "name": "liaopan3_materialhole_0_0", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_0_1", + "name": "liaopan3_materialhole_0_1", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_0_2", + "name": "liaopan3_materialhole_0_2", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_0_3", + "name": "liaopan3_materialhole_0_3", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_1_0", + "name": "liaopan3_materialhole_1_0", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_1_1", + "name": "liaopan3_materialhole_1_1", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_1_2", + "name": "liaopan3_materialhole_1_2", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_1_3", + "name": "liaopan3_materialhole_1_3", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_2_0", + "name": "liaopan3_materialhole_2_0", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_2_1", + "name": "liaopan3_materialhole_2_1", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_2_2", + "name": "liaopan3_materialhole_2_2", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_2_3", + "name": "liaopan3_materialhole_2_3", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_3_0", + "name": "liaopan3_materialhole_3_0", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_3_1", + "name": "liaopan3_materialhole_3_1", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_3_2", + "name": "liaopan3_materialhole_3_2", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_3_3", + "name": "liaopan3_materialhole_3_3", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4", + "name": "liaopan4", + "sample_id": null, + "children": [ + "liaopan4_materialhole_0_0", + "liaopan4_materialhole_0_1", + "liaopan4_materialhole_0_2", + "liaopan4_materialhole_0_3", + "liaopan4_materialhole_1_0", + "liaopan4_materialhole_1_1", + "liaopan4_materialhole_1_2", + "liaopan4_materialhole_1_3", + "liaopan4_materialhole_2_0", + "liaopan4_materialhole_2_1", + "liaopan4_materialhole_2_2", + "liaopan4_materialhole_2_3", + "liaopan4_materialhole_3_0", + "liaopan4_materialhole_3_1", + "liaopan4_materialhole_3_2", + "liaopan4_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1010, + "y": 150, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120, + "size_y": 100, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan4_materialhole_0_0", + "B1": "liaopan4_materialhole_0_1", + "C1": "liaopan4_materialhole_0_2", + "D1": "liaopan4_materialhole_0_3", + "A2": "liaopan4_materialhole_1_0", + "B2": "liaopan4_materialhole_1_1", + "C2": "liaopan4_materialhole_1_2", + "D2": "liaopan4_materialhole_1_3", + "A3": "liaopan4_materialhole_2_0", + "B3": "liaopan4_materialhole_2_1", + "C3": "liaopan4_materialhole_2_2", + "D3": "liaopan4_materialhole_2_3", + "A4": "liaopan4_materialhole_3_0", + "B4": "liaopan4_materialhole_3_1", + "C4": "liaopan4_materialhole_3_2", + "D4": "liaopan4_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan4_materialhole_0_0", + "name": "liaopan4_materialhole_0_0", + "sample_id": null, + "children": [ + "liaopan4_jipian_0" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_0", + "name": "liaopan4_jipian_0", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_0_1", + "name": "liaopan4_materialhole_0_1", + "sample_id": null, + "children": [ + "liaopan4_jipian_1" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_1", + "name": "liaopan4_jipian_1", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_0_2", + "name": "liaopan4_materialhole_0_2", + "sample_id": null, + "children": [ + "liaopan4_jipian_2" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_2", + "name": "liaopan4_jipian_2", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_0_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_0_3", + "name": "liaopan4_materialhole_0_3", + "sample_id": null, + "children": [ + "liaopan4_jipian_3" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_3", + "name": "liaopan4_jipian_3", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_0_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_1_0", + "name": "liaopan4_materialhole_1_0", + "sample_id": null, + "children": [ + "liaopan4_jipian_4" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_4", + "name": "liaopan4_jipian_4", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_1_1", + "name": "liaopan4_materialhole_1_1", + "sample_id": null, + "children": [ + "liaopan4_jipian_5" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_5", + "name": "liaopan4_jipian_5", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_1_2", + "name": "liaopan4_materialhole_1_2", + "sample_id": null, + "children": [ + "liaopan4_jipian_6" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_6", + "name": "liaopan4_jipian_6", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_1_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_1_3", + "name": "liaopan4_materialhole_1_3", + "sample_id": null, + "children": [ + "liaopan4_jipian_7" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_7", + "name": "liaopan4_jipian_7", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_1_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_2_0", + "name": "liaopan4_materialhole_2_0", + "sample_id": null, + "children": [ + "liaopan4_jipian_8" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_8", + "name": "liaopan4_jipian_8", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_2_1", + "name": "liaopan4_materialhole_2_1", + "sample_id": null, + "children": [ + "liaopan4_jipian_9" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_9", + "name": "liaopan4_jipian_9", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_2_2", + "name": "liaopan4_materialhole_2_2", + "sample_id": null, + "children": [ + "liaopan4_jipian_10" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_10", + "name": "liaopan4_jipian_10", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_2_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_2_3", + "name": "liaopan4_materialhole_2_3", + "sample_id": null, + "children": [ + "liaopan4_jipian_11" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_11", + "name": "liaopan4_jipian_11", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_2_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_3_0", + "name": "liaopan4_materialhole_3_0", + "sample_id": null, + "children": [ + "liaopan4_jipian_12" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_12", + "name": "liaopan4_jipian_12", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_3_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_3_1", + "name": "liaopan4_materialhole_3_1", + "sample_id": null, + "children": [ + "liaopan4_jipian_13" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_13", + "name": "liaopan4_jipian_13", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_3_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_3_2", + "name": "liaopan4_materialhole_3_2", + "sample_id": null, + "children": [ + "liaopan4_jipian_14" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_14", + "name": "liaopan4_jipian_14", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_3_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_3_3", + "name": "liaopan4_materialhole_3_3", + "sample_id": null, + "children": [ + "liaopan4_jipian_15" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_15", + "name": "liaopan4_jipian_15", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_3_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan5", + "name": "liaopan5", + "sample_id": null, + "children": [ + "liaopan5_materialhole_0_0", + "liaopan5_materialhole_0_1", + "liaopan5_materialhole_0_2", + "liaopan5_materialhole_0_3", + "liaopan5_materialhole_1_0", + "liaopan5_materialhole_1_1", + "liaopan5_materialhole_1_2", + "liaopan5_materialhole_1_3", + "liaopan5_materialhole_2_0", + "liaopan5_materialhole_2_1", + "liaopan5_materialhole_2_2", + "liaopan5_materialhole_2_3", + "liaopan5_materialhole_3_0", + "liaopan5_materialhole_3_1", + "liaopan5_materialhole_3_2", + "liaopan5_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1130, + "y": 150, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120, + "size_y": 100, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan5_materialhole_0_0", + "B1": "liaopan5_materialhole_0_1", + "C1": "liaopan5_materialhole_0_2", + "D1": "liaopan5_materialhole_0_3", + "A2": "liaopan5_materialhole_1_0", + "B2": "liaopan5_materialhole_1_1", + "C2": "liaopan5_materialhole_1_2", + "D2": "liaopan5_materialhole_1_3", + "A3": "liaopan5_materialhole_2_0", + "B3": "liaopan5_materialhole_2_1", + "C3": "liaopan5_materialhole_2_2", + "D3": "liaopan5_materialhole_2_3", + "A4": "liaopan5_materialhole_3_0", + "B4": "liaopan5_materialhole_3_1", + "C4": "liaopan5_materialhole_3_2", + "D4": "liaopan5_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan5_materialhole_0_0", + "name": "liaopan5_materialhole_0_0", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_0_1", + "name": "liaopan5_materialhole_0_1", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_0_2", + "name": "liaopan5_materialhole_0_2", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_0_3", + "name": "liaopan5_materialhole_0_3", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_1_0", + "name": "liaopan5_materialhole_1_0", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_1_1", + "name": "liaopan5_materialhole_1_1", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_1_2", + "name": "liaopan5_materialhole_1_2", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_1_3", + "name": "liaopan5_materialhole_1_3", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_2_0", + "name": "liaopan5_materialhole_2_0", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_2_1", + "name": "liaopan5_materialhole_2_1", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_2_2", + "name": "liaopan5_materialhole_2_2", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_2_3", + "name": "liaopan5_materialhole_2_3", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_3_0", + "name": "liaopan5_materialhole_3_0", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_3_1", + "name": "liaopan5_materialhole_3_1", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_3_2", + "name": "liaopan5_materialhole_3_2", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_3_3", + "name": "liaopan5_materialhole_3_3", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6", + "name": "liaopan6", + "sample_id": null, + "children": [ + "liaopan6_materialhole_0_0", + "liaopan6_materialhole_0_1", + "liaopan6_materialhole_0_2", + "liaopan6_materialhole_0_3", + "liaopan6_materialhole_1_0", + "liaopan6_materialhole_1_1", + "liaopan6_materialhole_1_2", + "liaopan6_materialhole_1_3", + "liaopan6_materialhole_2_0", + "liaopan6_materialhole_2_1", + "liaopan6_materialhole_2_2", + "liaopan6_materialhole_2_3", + "liaopan6_materialhole_3_0", + "liaopan6_materialhole_3_1", + "liaopan6_materialhole_3_2", + "liaopan6_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1250, + "y": 150, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120, + "size_y": 100, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan6_materialhole_0_0", + "B1": "liaopan6_materialhole_0_1", + "C1": "liaopan6_materialhole_0_2", + "D1": "liaopan6_materialhole_0_3", + "A2": "liaopan6_materialhole_1_0", + "B2": "liaopan6_materialhole_1_1", + "C2": "liaopan6_materialhole_1_2", + "D2": "liaopan6_materialhole_1_3", + "A3": "liaopan6_materialhole_2_0", + "B3": "liaopan6_materialhole_2_1", + "C3": "liaopan6_materialhole_2_2", + "D3": "liaopan6_materialhole_2_3", + "A4": "liaopan6_materialhole_3_0", + "B4": "liaopan6_materialhole_3_1", + "C4": "liaopan6_materialhole_3_2", + "D4": "liaopan6_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan6_materialhole_0_0", + "name": "liaopan6_materialhole_0_0", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_0_1", + "name": "liaopan6_materialhole_0_1", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_0_2", + "name": "liaopan6_materialhole_0_2", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_0_3", + "name": "liaopan6_materialhole_0_3", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_1_0", + "name": "liaopan6_materialhole_1_0", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_1_1", + "name": "liaopan6_materialhole_1_1", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_1_2", + "name": "liaopan6_materialhole_1_2", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_1_3", + "name": "liaopan6_materialhole_1_3", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_2_0", + "name": "liaopan6_materialhole_2_0", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_2_1", + "name": "liaopan6_materialhole_2_1", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_2_2", + "name": "liaopan6_materialhole_2_2", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_2_3", + "name": "liaopan6_materialhole_2_3", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_3_0", + "name": "liaopan6_materialhole_3_0", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_3_1", + "name": "liaopan6_materialhole_3_1", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_3_2", + "name": "liaopan6_materialhole_3_2", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_3_3", + "name": "liaopan6_materialhole_3_3", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "bottle_rack_3x4", + "name": "bottle_rack_3x4", + "sample_id": null, + "children": [ + "sheet_3x4_0", + "sheet_3x4_1", + "sheet_3x4_2", + "sheet_3x4_3", + "sheet_3x4_4", + "sheet_3x4_5", + "sheet_3x4_6", + "sheet_3x4_7", + "sheet_3x4_8", + "sheet_3x4_9", + "sheet_3x4_10", + "sheet_3x4_11" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 100, + "y": 200, + "z": 0 + }, + "config": { + "type": "BottleRack", + "size_x": 210.0, + "size_y": 140.0, + "size_z": 100.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "bottle_rack", + "model": null, + "barcode": null, + "num_items_x": 3, + "num_items_y": 4, + "position_spacing": 35.0, + "orientation": "vertical", + "padding_x": 20.0, + "padding_y": 20.0 + }, + "data": { + "bottle_diameter": 30.0, + "bottle_height": 100.0, + "position_spacing": 35.0, + "name_to_index": {} + } + }, + { + "id": "sheet_3x4_0", + "name": "sheet_3x4_0", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 20.0, + "y": 20.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_1", + "name": "sheet_3x4_1", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 20.0, + "y": 55.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_2", + "name": "sheet_3x4_2", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 20.0, + "y": 90.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_3", + "name": "sheet_3x4_3", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 55.0, + "y": 20.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_4", + "name": "sheet_3x4_4", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 55.0, + "y": 55.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_5", + "name": "sheet_3x4_5", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 55.0, + "y": 90.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_6", + "name": "sheet_3x4_6", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 90.0, + "y": 20.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_7", + "name": "sheet_3x4_7", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 90.0, + "y": 55.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_8", + "name": "sheet_3x4_8", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 90.0, + "y": 90.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_9", + "name": "sheet_3x4_9", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 125.0, + "y": 20.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_10", + "name": "sheet_3x4_10", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 125.0, + "y": 55.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_11", + "name": "sheet_3x4_11", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 125.0, + "y": 90.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "bottle_rack_6x2", + "name": "bottle_rack_6x2", + "sample_id": null, + "children": [ + "sheet_6x2_0", + "sheet_6x2_1", + "sheet_6x2_2", + "sheet_6x2_3", + "sheet_6x2_4", + "sheet_6x2_5", + "sheet_6x2_6", + "sheet_6x2_7", + "sheet_6x2_8", + "sheet_6x2_9", + "sheet_6x2_10", + "sheet_6x2_11" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 300, + "y": 300, + "z": 0 + }, + "config": { + "type": "BottleRack", + "size_x": 120.0, + "size_y": 250.0, + "size_z": 100.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "bottle_rack", + "model": null, + "barcode": null, + "num_items_x": 6, + "num_items_y": 2, + "position_spacing": 35.0, + "orientation": "vertical", + "padding_x": 20.0, + "padding_y": 20.0 + }, + "data": { + "bottle_diameter": 30.0, + "bottle_height": 100.0, + "position_spacing": 35.0, + "name_to_index": {} + } + }, + { + "id": "sheet_6x2_0", + "name": "sheet_6x2_0", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 20.0, + "y": 20.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_1", + "name": "sheet_6x2_1", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 20.0, + "y": 55.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_2", + "name": "sheet_6x2_2", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 20.0, + "y": 90.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_3", + "name": "sheet_6x2_3", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 20.0, + "y": 125.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_4", + "name": "sheet_6x2_4", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 20.0, + "y": 160.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_5", + "name": "sheet_6x2_5", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 20.0, + "y": 195.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_6", + "name": "sheet_6x2_6", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 55.0, + "y": 20.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_7", + "name": "sheet_6x2_7", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 55.0, + "y": 55.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_8", + "name": "sheet_6x2_8", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 55.0, + "y": 90.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_9", + "name": "sheet_6x2_9", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 55.0, + "y": 125.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_10", + "name": "sheet_6x2_10", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 55.0, + "y": 160.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_11", + "name": "sheet_6x2_11", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 55.0, + "y": 195.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "bottle_rack_6x2_2", + "name": "bottle_rack_6x2_2", + "sample_id": null, + "children": [], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 430, + "y": 300, + "z": 0 + }, + "config": { + "type": "BottleRack", + "size_x": 120.0, + "size_y": 250.0, + "size_z": 100.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "bottle_rack", + "model": null, + "barcode": null, + "num_items_x": 6, + "num_items_y": 2, + "position_spacing": 35.0, + "orientation": "vertical", + "padding_x": 20.0, + "padding_y": 20.0 + }, + "data": { + "bottle_diameter": 30.0, + "bottle_height": 100.0, + "position_spacing": 35.0, + "name_to_index": {} + } + }, + { + "id": "tip_box_64", + "name": "tip_box_64", + "sample_id": null, + "children": [ + "tip_box_64_tipspot_0_0", + "tip_box_64_tipspot_0_1", + "tip_box_64_tipspot_0_2", + "tip_box_64_tipspot_0_3", + "tip_box_64_tipspot_0_4", + "tip_box_64_tipspot_0_5", + "tip_box_64_tipspot_0_6", + "tip_box_64_tipspot_0_7", + "tip_box_64_tipspot_1_0", + "tip_box_64_tipspot_1_1", + "tip_box_64_tipspot_1_2", + "tip_box_64_tipspot_1_3", + "tip_box_64_tipspot_1_4", + "tip_box_64_tipspot_1_5", + "tip_box_64_tipspot_1_6", + "tip_box_64_tipspot_1_7", + "tip_box_64_tipspot_2_0", + "tip_box_64_tipspot_2_1", + "tip_box_64_tipspot_2_2", + "tip_box_64_tipspot_2_3", + "tip_box_64_tipspot_2_4", + "tip_box_64_tipspot_2_5", + "tip_box_64_tipspot_2_6", + "tip_box_64_tipspot_2_7", + "tip_box_64_tipspot_3_0", + "tip_box_64_tipspot_3_1", + "tip_box_64_tipspot_3_2", + "tip_box_64_tipspot_3_3", + "tip_box_64_tipspot_3_4", + "tip_box_64_tipspot_3_5", + "tip_box_64_tipspot_3_6", + "tip_box_64_tipspot_3_7", + "tip_box_64_tipspot_4_0", + "tip_box_64_tipspot_4_1", + "tip_box_64_tipspot_4_2", + "tip_box_64_tipspot_4_3", + "tip_box_64_tipspot_4_4", + "tip_box_64_tipspot_4_5", + "tip_box_64_tipspot_4_6", + "tip_box_64_tipspot_4_7", + "tip_box_64_tipspot_5_0", + "tip_box_64_tipspot_5_1", + "tip_box_64_tipspot_5_2", + "tip_box_64_tipspot_5_3", + "tip_box_64_tipspot_5_4", + "tip_box_64_tipspot_5_5", + "tip_box_64_tipspot_5_6", + "tip_box_64_tipspot_5_7", + "tip_box_64_tipspot_6_0", + "tip_box_64_tipspot_6_1", + "tip_box_64_tipspot_6_2", + "tip_box_64_tipspot_6_3", + "tip_box_64_tipspot_6_4", + "tip_box_64_tipspot_6_5", + "tip_box_64_tipspot_6_6", + "tip_box_64_tipspot_6_7", + "tip_box_64_tipspot_7_0", + "tip_box_64_tipspot_7_1", + "tip_box_64_tipspot_7_2", + "tip_box_64_tipspot_7_3", + "tip_box_64_tipspot_7_4", + "tip_box_64_tipspot_7_5", + "tip_box_64_tipspot_7_6", + "tip_box_64_tipspot_7_7" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 300, + "y": 100, + "z": 0 + }, + "config": { + "type": "TipBox64", + "size_x": 127.8, + "size_y": 85.5, + "size_z": 60.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_box_64", + "model": null, + "barcode": null, + "ordering": { + "A1": "tip_box_64_tipspot_0_0", + "B1": "tip_box_64_tipspot_0_1", + "C1": "tip_box_64_tipspot_0_2", + "D1": "tip_box_64_tipspot_0_3", + "E1": "tip_box_64_tipspot_0_4", + "F1": "tip_box_64_tipspot_0_5", + "G1": "tip_box_64_tipspot_0_6", + "H1": "tip_box_64_tipspot_0_7", + "A2": "tip_box_64_tipspot_1_0", + "B2": "tip_box_64_tipspot_1_1", + "C2": "tip_box_64_tipspot_1_2", + "D2": "tip_box_64_tipspot_1_3", + "E2": "tip_box_64_tipspot_1_4", + "F2": "tip_box_64_tipspot_1_5", + "G2": "tip_box_64_tipspot_1_6", + "H2": "tip_box_64_tipspot_1_7", + "A3": "tip_box_64_tipspot_2_0", + "B3": "tip_box_64_tipspot_2_1", + "C3": "tip_box_64_tipspot_2_2", + "D3": "tip_box_64_tipspot_2_3", + "E3": "tip_box_64_tipspot_2_4", + "F3": "tip_box_64_tipspot_2_5", + "G3": "tip_box_64_tipspot_2_6", + "H3": "tip_box_64_tipspot_2_7", + "A4": "tip_box_64_tipspot_3_0", + "B4": "tip_box_64_tipspot_3_1", + "C4": "tip_box_64_tipspot_3_2", + "D4": "tip_box_64_tipspot_3_3", + "E4": "tip_box_64_tipspot_3_4", + "F4": "tip_box_64_tipspot_3_5", + "G4": "tip_box_64_tipspot_3_6", + "H4": "tip_box_64_tipspot_3_7", + "A5": "tip_box_64_tipspot_4_0", + "B5": "tip_box_64_tipspot_4_1", + "C5": "tip_box_64_tipspot_4_2", + "D5": "tip_box_64_tipspot_4_3", + "E5": "tip_box_64_tipspot_4_4", + "F5": "tip_box_64_tipspot_4_5", + "G5": "tip_box_64_tipspot_4_6", + "H5": "tip_box_64_tipspot_4_7", + "A6": "tip_box_64_tipspot_5_0", + "B6": "tip_box_64_tipspot_5_1", + "C6": "tip_box_64_tipspot_5_2", + "D6": "tip_box_64_tipspot_5_3", + "E6": "tip_box_64_tipspot_5_4", + "F6": "tip_box_64_tipspot_5_5", + "G6": "tip_box_64_tipspot_5_6", + "H6": "tip_box_64_tipspot_5_7", + "A7": "tip_box_64_tipspot_6_0", + "B7": "tip_box_64_tipspot_6_1", + "C7": "tip_box_64_tipspot_6_2", + "D7": "tip_box_64_tipspot_6_3", + "E7": "tip_box_64_tipspot_6_4", + "F7": "tip_box_64_tipspot_6_5", + "G7": "tip_box_64_tipspot_6_6", + "H7": "tip_box_64_tipspot_6_7", + "A8": "tip_box_64_tipspot_7_0", + "B8": "tip_box_64_tipspot_7_1", + "C8": "tip_box_64_tipspot_7_2", + "D8": "tip_box_64_tipspot_7_3", + "E8": "tip_box_64_tipspot_7_4", + "F8": "tip_box_64_tipspot_7_5", + "G8": "tip_box_64_tipspot_7_6", + "H8": "tip_box_64_tipspot_7_7" + }, + "num_items_x": 8, + "num_items_y": 8, + "dx": 8.0, + "dy": 8.0, + "item_dx": 9.0, + "item_dy": 9.0 + }, + "data": {} + }, + { + "id": "tip_box_64_tipspot_0_0", + "name": "tip_box_64_tipspot_0_0", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 8.0, + "y": 71.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_0_1", + "name": "tip_box_64_tipspot_0_1", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 8.0, + "y": 62.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_0_2", + "name": "tip_box_64_tipspot_0_2", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 8.0, + "y": 53.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_0_3", + "name": "tip_box_64_tipspot_0_3", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 8.0, + "y": 44.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_0_4", + "name": "tip_box_64_tipspot_0_4", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 8.0, + "y": 35.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_0_5", + "name": "tip_box_64_tipspot_0_5", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 8.0, + "y": 26.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_0_6", + "name": "tip_box_64_tipspot_0_6", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 8.0, + "y": 17.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_0_7", + "name": "tip_box_64_tipspot_0_7", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 8.0, + "y": 8.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_1_0", + "name": "tip_box_64_tipspot_1_0", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 17.0, + "y": 71.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_1_1", + "name": "tip_box_64_tipspot_1_1", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 17.0, + "y": 62.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_1_2", + "name": "tip_box_64_tipspot_1_2", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 17.0, + "y": 53.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_1_3", + "name": "tip_box_64_tipspot_1_3", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 17.0, + "y": 44.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_1_4", + "name": "tip_box_64_tipspot_1_4", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 17.0, + "y": 35.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_1_5", + "name": "tip_box_64_tipspot_1_5", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 17.0, + "y": 26.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_1_6", + "name": "tip_box_64_tipspot_1_6", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 17.0, + "y": 17.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_1_7", + "name": "tip_box_64_tipspot_1_7", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 17.0, + "y": 8.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_2_0", + "name": "tip_box_64_tipspot_2_0", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 26.0, + "y": 71.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_2_1", + "name": "tip_box_64_tipspot_2_1", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 26.0, + "y": 62.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_2_2", + "name": "tip_box_64_tipspot_2_2", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 26.0, + "y": 53.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_2_3", + "name": "tip_box_64_tipspot_2_3", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 26.0, + "y": 44.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_2_4", + "name": "tip_box_64_tipspot_2_4", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 26.0, + "y": 35.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_2_5", + "name": "tip_box_64_tipspot_2_5", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 26.0, + "y": 26.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_2_6", + "name": "tip_box_64_tipspot_2_6", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 26.0, + "y": 17.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_2_7", + "name": "tip_box_64_tipspot_2_7", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 26.0, + "y": 8.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_3_0", + "name": "tip_box_64_tipspot_3_0", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 35.0, + "y": 71.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_3_1", + "name": "tip_box_64_tipspot_3_1", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 35.0, + "y": 62.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_3_2", + "name": "tip_box_64_tipspot_3_2", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 35.0, + "y": 53.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_3_3", + "name": "tip_box_64_tipspot_3_3", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 35.0, + "y": 44.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_3_4", + "name": "tip_box_64_tipspot_3_4", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 35.0, + "y": 35.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_3_5", + "name": "tip_box_64_tipspot_3_5", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 35.0, + "y": 26.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_3_6", + "name": "tip_box_64_tipspot_3_6", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 35.0, + "y": 17.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_3_7", + "name": "tip_box_64_tipspot_3_7", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 35.0, + "y": 8.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_4_0", + "name": "tip_box_64_tipspot_4_0", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 44.0, + "y": 71.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_4_1", + "name": "tip_box_64_tipspot_4_1", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 44.0, + "y": 62.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_4_2", + "name": "tip_box_64_tipspot_4_2", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 44.0, + "y": 53.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_4_3", + "name": "tip_box_64_tipspot_4_3", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 44.0, + "y": 44.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_4_4", + "name": "tip_box_64_tipspot_4_4", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 44.0, + "y": 35.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_4_5", + "name": "tip_box_64_tipspot_4_5", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 44.0, + "y": 26.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_4_6", + "name": "tip_box_64_tipspot_4_6", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 44.0, + "y": 17.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_4_7", + "name": "tip_box_64_tipspot_4_7", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 44.0, + "y": 8.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_5_0", + "name": "tip_box_64_tipspot_5_0", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 53.0, + "y": 71.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_5_1", + "name": "tip_box_64_tipspot_5_1", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 53.0, + "y": 62.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_5_2", + "name": "tip_box_64_tipspot_5_2", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 53.0, + "y": 53.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_5_3", + "name": "tip_box_64_tipspot_5_3", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 53.0, + "y": 44.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_5_4", + "name": "tip_box_64_tipspot_5_4", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 53.0, + "y": 35.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_5_5", + "name": "tip_box_64_tipspot_5_5", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 53.0, + "y": 26.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_5_6", + "name": "tip_box_64_tipspot_5_6", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 53.0, + "y": 17.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_5_7", + "name": "tip_box_64_tipspot_5_7", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 53.0, + "y": 8.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_6_0", + "name": "tip_box_64_tipspot_6_0", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 62.0, + "y": 71.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_6_1", + "name": "tip_box_64_tipspot_6_1", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 62.0, + "y": 62.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_6_2", + "name": "tip_box_64_tipspot_6_2", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 62.0, + "y": 53.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_6_3", + "name": "tip_box_64_tipspot_6_3", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 62.0, + "y": 44.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_6_4", + "name": "tip_box_64_tipspot_6_4", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 62.0, + "y": 35.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_6_5", + "name": "tip_box_64_tipspot_6_5", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 62.0, + "y": 26.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_6_6", + "name": "tip_box_64_tipspot_6_6", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 62.0, + "y": 17.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_6_7", + "name": "tip_box_64_tipspot_6_7", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 62.0, + "y": 8.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_7_0", + "name": "tip_box_64_tipspot_7_0", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 71.0, + "y": 71.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_7_1", + "name": "tip_box_64_tipspot_7_1", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 71.0, + "y": 62.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_7_2", + "name": "tip_box_64_tipspot_7_2", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 71.0, + "y": 53.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_7_3", + "name": "tip_box_64_tipspot_7_3", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 71.0, + "y": 44.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_7_4", + "name": "tip_box_64_tipspot_7_4", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 71.0, + "y": 35.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_7_5", + "name": "tip_box_64_tipspot_7_5", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 71.0, + "y": 26.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_7_6", + "name": "tip_box_64_tipspot_7_6", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 71.0, + "y": 17.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_7_7", + "name": "tip_box_64_tipspot_7_7", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 71.0, + "y": 8.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "waste_tip_box", + "name": "waste_tip_box", + "sample_id": null, + "children": [], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 300, + "y": 200, + "z": 0 + }, + "config": { + "type": "WasteTipBox", + "size_x": 127.8, + "size_y": 85.5, + "size_z": 60.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "waste_tip_box", + "model": null, + "barcode": null, + "max_volume": "Infinity", + "material_z_thickness": 0, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + } + } + ], + "links": [] +} \ No newline at end of file diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/样品导入模板.xlsx b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/样品导入模板.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..a89f1bc27fa7eee453958723c88bd563c122cbe7 GIT binary patch literal 22007 zcmeHP|8FeURd;AWD5xqCAOR9$v=aPM+3Vf4{cK}9vUk_-y}akddUjruAXM(o%p-~OAQnwY@9UxmVb zxBBVOcBAT^AMSK~e`jic;7(!5(nD}%?Al@E_}+4*HZ@%_Y|r#9$LlRu9@}1@oUa&B zEIdoNzGp924(zD%(8|XjSh``mA_k$T?*x$nTD)kv(vRa{v09DHzC94plpok0P`Z9N z5HZ|_y=oxL9nrI^_37!kDj?80Dl0^%an)L`gjcOv#n?Uwpyrcx``xZ%+AZH44s0(j zs6w_s>f3e5I=`BOvNRI0^b>K6$n1|!)Th`g=J|BuDcxJV39eBeTl zX4X*m<4iDfq19twY}m1|L@YFlK~j?9s1j#e*(AExqP0`QpXeZJx}mWvT<~4Tv;4hndmsGS6;TX?<;t}DhaOzbgxt79HYc|(|0QyX zfVESXb%wo8R%va0X5msR(=Qp^c$BZ*Za3StOrK;BrU@-oSn@h&*5_+$sm>gex*XNr zoSvPYq5L`hjS3kepbR5Gc{nGyc^>A9KJI^ma0ihp*5e`am9*_H6-KLiTxJpPx?HH5Yh8=3Q7t*Z1NwUmJ*9 zez*<{$cQo8GhztBU15S3H^rb6Isz*Yi?+y7RFN5t6k%OPWQI-v5%=Lhz=+WLK79AD zK79B4#)t2|@!r#KzW4N7?|tD5?>+t3N*58lBD`K+@PpU>@ZVo~%J{eM-}&IhKPu*j z(55RoeEqq*fBQl)Ba9%;c=Xa6$8WxI^v7R$|2r=hu&;)qlV{&{26kjTV(%F@`~kF0 z8ipdbJK@&jVpd+7CwD-VZxz8ga97S-Jp9_iZlFo39;Iwi4vvu_iao38L; zn1XnAY#`0wvF(D^J>fXSt{aXS`jOv_jVByqO*n-n*>*a_k+jeiX8`?jP}FAVYGRv> zo5q^&7PY!%?@~?}5JFB?)Nsy**tYEwGhPi(LuW5(-DIA&=0~Z?e3cn&Lnl*I5`kJ=JW_8?I1`Qh6S)L+dlE% ztpm3!Y#C)Bq?1TJaJ+G0+jSsrs~SEE+slwXR8~V!r>?Cgp~;{~ zYRj68CYI0TVDhYKP?%X0W;O=Qhrr5n@M;UIhRK+4ZFq^5J_D!Sf>uqhFSv`6R;z~e zg=MQQBE>mt)@0wME;Q0UY@YIwvsp8<8W`rzNBL8bBz(;7;+TE^g#`(_9m7wj^ zZ4wFnVQhz6eQVF?xWn*$h#aoXj>t6*|@Ve1xNfvH6`!H|IeC&=>Xm!G%V}^6JG9y!oZQT zT=B!W??V&{`i{9CLf)6L1H1*^&;l8Z4+|;sH?(*0SB*KPLk??r5R7j)JtK4=oy7gn zwl^UxN$)#BSuIIDN~N19k{*?V8wCn>$KJKwZH$3)7=tTDA6#8VlOzmCKJI_?J{dus z9?ntng4MBpQCC#Lxf+W9% zg`}!;LO4wV&gEJau|`^4pH0C*FOIet3bXl40Plee{2$~$Cp2AKx-llyZT|+uRs)tF zl>H3w>Pd`h;6WH5)l~=PJ*rSS6RJ|CO(v&kwKQ2@@GK~+ml+$8ou{{`CX4sSn=G*u7;n_^V1+Xj@aVNA<1Zu~<>}I8*>dn>|9St5Pu@Q0v__ePmGIBH|FjkguP!Kd&u(A|=Rt@@^Tv|1K*AETZ zhsi@Su97Q`CM*>sFRQz-aN!=PVkxH_Qx&KIir%1VkU#L&0pmdi07nrPtSmtmf~mf@ z4mX(G@ET3HT=8IYi)@18JAoVW)#Gy?WDUIL)xQt4AS zs6Vew>Aq*R#(p=cOay^@@W^n`v0(uj_x&V&D{F;%a*ZKP$v`DLA09zrSenWdcZQA& z)2xDKfKF>)8%C!1Sae)lPDry_z$lhVX4@HYxuUN(Y|9xA7Ba3)XV+ILZMmYa71xaH zdmR*55c-!7X8!#6S0*O#@0V2&D%@vu{&co-~3LdovR z;ma={e)X-RXP-NK^&7{pfA{dtmyYh-UaIb*(-?|ZBwd-Nr22} zKwxid&LWo>5K84hD3$vNrIr9$$bdl7$^n^YKq!?1p;Yc8lv)C$hL0O+{K91($^wJJ z$gJ5dM%L9SKPe@X{;6fzVGS<|+ zs5m3HnY30;;pkXfV;I9bG9tYyFWbq)^nMYNzSW`^Z4c0 zVXkubhc6ty^Fqc+`(J04T3G^+s+;4QgM1Twxqw zB&y;nXL$MVj-J;OA8Hky=4vz>G2l%`)^06fw-}j8w@b+sjm~v7nv&QUY%=l)nz7Pt zCau9zG&{mjZS=Znz{G{g^@?lP-$JA zbgMR#)?{iMo%iZA@3FYjWaJSvRGN|7Ou7tB7KK-F`$2Bl zWSL-L7GyldkFteKJPQ^w@hr%gj-UO@qnEx}l(#MybJB-#+J8Di*J*@i-q{>aZjC3m zOUV?Cj?i@)V|9m1?`$$M1KVO`Cf#OaElttr2wkT!mU(BBk+nV*p&6M;w;5SWQ#3k4 z*J+I9`GS#I<6DfZt4qc`Rj$bdO+!a$xXh0%t}p2< zroE)IbUbdbkja|gWb_d@RGiti&7?J2(pj(;kiIN}%X~ntvHF^f&d4oBX0~lJGKZr+ z_g2daP7gkuv8{F(`XMYhQ8T%@FqhCOW_hWtnQPxBDY)<&l*3bxuJF z9RB@0I9+Dnq%r5u%(y!mTqW(B8m@giJMMl4i7fIuN`J29Md^JsTdgt(P8V@wc(J8Hxd z1I$q~I4Yiv;FC>;nwajY?rav+{^{$#_u!L|Kk)Ctyno>czqc%628*afSFO^?q6wch zoiPff6OGZhqa4jLGnK(Gbmgf|mRYrvQ*5fWsah4bZQ+4Xi<0Q^sJwur9m~rn%Fk4C5wYRr7wbzg^U=~-aKE82XZW9%gj)x~#nYgi!9vSo(|9tDI zU;5dJ3H-w&gMPaS`{v+anopz4M!$1!T_JUxKAV#Ru}pM%<6|B)qh-n)ytY zSy@W*^FR=(~!Mm;_t1T!8?XMuaL-A%z#nb?~I4Qn_8vni@ot_CCzGrsR;H z0s#>oObrAatTzXk3&z(l zfO(SpNvWo|37tW}Oaxz@!*OU>UMvydhlm7{lECs}iJsFfl)&<0342g1u|J8FVwt^C z8Ke};+zN`@ax1XQ8Y4+X?H?l(mly!b%aBP@Q8i;^0*hNMKTMEXYz5da31gddXa<>zd|R3i7U{M1dZhaX|6mHY?P z2YR>@a|QgP7p>sG45>gnB^Tve)wSs7e?y9911*0*CfCB>X=*LsdPr)?_ZAoC?^d+% zRx^W;9$U(V`I`bQ{P!*L-hd18r#&rr>GJ5aAD8BHc`f~ot2ya(FmiD|1J&ZjwZi%6 V=Cz;x8Dx3@{`(v_MRyDD{u^YnfQ0}6 literal 0 HcmV?d00001 diff --git a/unilabos/devices/workstation/coin_cell_assembly/__init__.py b/unilabos/devices/workstation/coin_cell_assembly/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py b/unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py deleted file mode 100644 index f663a21..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py +++ /dev/null @@ -1,1289 +0,0 @@ -""" -纽扣电池组装工作站物料类定义 -Button Battery Assembly Station Resource Classes -""" - -from __future__ import annotations - -from collections import OrderedDict -from typing import Any, Dict, List, Optional, TypedDict, Union, cast - -from pylabrobot.resources.coordinate import Coordinate -from pylabrobot.resources.container import Container -from pylabrobot.resources.deck import Deck -from pylabrobot.resources.itemized_resource import ItemizedResource -from pylabrobot.resources.resource import Resource -from pylabrobot.resources.resource_stack import ResourceStack -from pylabrobot.resources.tip_rack import TipRack, TipSpot -from pylabrobot.resources.trash import Trash -from pylabrobot.resources.utils import create_ordered_items_2d - - -class ElectrodeSheetState(TypedDict): - diameter: float # 直径 (mm) - thickness: float # 厚度 (mm) - mass: float # 质量 (g) - material_type: str # 材料类型(正极、负极、隔膜、弹片、垫片、铝箔等) - info: Optional[str] # 附加信息 - -class ElectrodeSheet(Resource): - """极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料""" - - def __init__( - self, - name: str = "极片", - size_x=10, - size_y=10, - size_z=10, - category: str = "electrode_sheet", - model: Optional[str] = None, - ): - """初始化极片 - - Args: - name: 极片名称 - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - category=category, - model=model, - ) - self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState( - diameter=14, - thickness=0.1, - mass=0.5, - material_type="copper", - info=None - ) - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - #序列化 - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - -# TODO: 这个应该只能放一个极片 -class MaterialHoleState(TypedDict): - diameter: int - depth: int - max_sheets: int - info: Optional[str] # 附加信息 - -class MaterialHole(Resource): - """料板洞位类""" - children: List[ElectrodeSheet] = [] - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - category: str = "material_hole", - **kwargs - ): - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - category=category, - ) - self._unilabos_state: MaterialHoleState = MaterialHoleState( - diameter=20, - depth=10, - max_sheets=1, - info=None - ) - - def get_all_sheet_info(self): - info_list = [] - for sheet in self.children: - info_list.append(sheet._unilabos_state["info"]) - return info_list - - #这个函数函数好像没用,一般不会集中赋值质量 - def set_all_sheet_mass(self): - for sheet in self.children: - sheet._unilabos_state["mass"] = 0.5 # 示例:设置质量为0.5g - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - #移动极片前先取出对象 - def get_sheet_with_name(self, name: str) -> Optional[ElectrodeSheet]: - for sheet in self.children: - if sheet.name == name: - return sheet - return None - - def has_electrode_sheet(self) -> bool: - """检查洞位是否有极片""" - return len(self.children) > 0 - - def assign_child_resource( - self, - resource: ElectrodeSheet, - location: Optional[Coordinate], - reassign: bool = True, - ): - """放置极片""" - # TODO: 这里要改,diameter找不到,加入._unilabos_state后应该没问题 - if resource._unilabos_state["diameter"] > self._unilabos_state["diameter"]: - raise ValueError(f"极片直径 {resource._unilabos_state['diameter']} 超过洞位直径 {self._unilabos_state['diameter']}") - if len(self.children) >= self._unilabos_state["max_sheets"]: - raise ValueError(f"洞位已满,无法放置更多极片") - super().assign_child_resource(resource, location, reassign) - - # 根据children的编号取物料对象。 - def get_electrode_sheet_info(self, index: int) -> ElectrodeSheet: - return self.children[index] - - - -class MaterialPlateState(TypedDict): - hole_spacing_x: float - hole_spacing_y: float - hole_diameter: float - info: Optional[str] # 附加信息 - - - -class MaterialPlate(ItemizedResource[MaterialHole]): - """料板类 - 4x4个洞位,每个洞位放1个极片""" - - children: List[MaterialHole] - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - ordered_items: Optional[Dict[str, MaterialHole]] = None, - ordering: Optional[OrderedDict[str, str]] = None, - category: str = "material_plate", - model: Optional[str] = None, - fill: bool = False - ): - """初始化料板 - - Args: - name: 料板名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - hole_diameter: 洞直径 (mm) - hole_depth: 洞深度 (mm) - hole_spacing_x: X方向洞位间距 (mm) - hole_spacing_y: Y方向洞位间距 (mm) - number: 编号 - category: 类别 - model: 型号 - """ - self._unilabos_state: MaterialPlateState = MaterialPlateState( - hole_spacing_x=24.0, - hole_spacing_y=24.0, - hole_diameter=20.0, - info="", - ) - # 创建4x4的洞位 - # TODO: 这里要改,对应不同形状 - holes = create_ordered_items_2d( - klass=MaterialHole, - num_items_x=4, - num_items_y=4, - dx=(size_x - 4 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中 - dy=(size_y - 4 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中 - dz=size_z, - item_dx=self._unilabos_state["hole_spacing_x"], - item_dy=self._unilabos_state["hole_spacing_y"], - size_x = 16, - size_y = 16, - size_z = 16, - ) - if fill: - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=holes, - category=category, - model=model, - ) - else: - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=ordered_items, - ordering=ordering, - category=category, - model=model, - ) - - def update_locations(self): - # TODO:调多次相加 - holes = create_ordered_items_2d( - klass=MaterialHole, - num_items_x=4, - num_items_y=4, - dx=(self._size_x - 3 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中 - dy=(self._size_y - 3 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中 - dz=self._size_z, - item_dx=self._unilabos_state["hole_spacing_x"], - item_dy=self._unilabos_state["hole_spacing_y"], - size_x = 1, - size_y = 1, - size_z = 1, - ) - for item, original_item in zip(holes.items(), self.children): - original_item.location = item[1].location - - -class PlateSlot(ResourceStack): - """板槽位类 - 1个槽上能堆放8个板,移板只能操作最上方的板""" - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - max_plates: int = 8, - category: str = "plate_slot", - model: Optional[str] = None - ): - """初始化板槽位 - - Args: - name: 槽位名称 - max_plates: 最大板数量 - category: 类别 - """ - super().__init__( - name=name, - direction="z", # Z方向堆叠 - resources=[], - ) - self.max_plates = max_plates - self.category = category - - def can_add_plate(self) -> bool: - """检查是否可以添加板""" - return len(self.children) < self.max_plates - - def add_plate(self, plate: MaterialPlate) -> None: - """添加料板""" - if not self.can_add_plate(): - raise ValueError(f"槽位 {self.name} 已满,无法添加更多板") - self.assign_child_resource(plate) - - def get_top_plate(self) -> MaterialPlate: - """获取最上方的板""" - if len(self.children) == 0: - raise ValueError(f"槽位 {self.name} 为空") - return cast(MaterialPlate, self.get_top_item()) - - def take_top_plate(self) -> MaterialPlate: - """取出最上方的板""" - top_plate = self.get_top_plate() - self.unassign_child_resource(top_plate) - return top_plate - - def can_access_for_picking(self) -> bool: - """检查是否可以进行取料操作(只有最上方的板能进行取料操作)""" - return len(self.children) > 0 - - def serialize(self) -> dict: - return { - **super().serialize(), - "max_plates": self.max_plates, - } - - -class ClipMagazineHole(Container): - """子弹夹洞位类""" - children: List[ElectrodeSheet] = [] - def __init__( - self, - name: str, - diameter: float, - depth: float, - category: str = "clip_magazine_hole", - ): - """初始化子弹夹洞位 - - Args: - name: 洞位名称 - diameter: 洞直径 (mm) - depth: 洞深度 (mm) - category: 类别 - """ - super().__init__( - name=name, - size_x=diameter, - size_y=diameter, - size_z=depth, - category=category, - ) - self.diameter = diameter - self.depth = depth - - def can_add_sheet(self, sheet: ElectrodeSheet) -> bool: - """检查是否可以添加极片 - - 根据洞的深度和极片的厚度来判断是否可以添加极片 - """ - # 检查极片直径是否适合洞的直径 - if sheet._unilabos_state["diameter"] > self.diameter: - return False - - # 计算当前已添加极片的总厚度 - current_thickness = sum(s._unilabos_state["thickness"] for s in self.children) - - # 检查添加新极片后总厚度是否超过洞的深度 - if current_thickness + sheet._unilabos_state["thickness"] > self.depth: - return False - - return True - - - def assign_child_resource( - self, - resource: ElectrodeSheet, - location: Optional[Coordinate] = None, - reassign: bool = True, - ): - """放置极片到洞位中 - - Args: - resource: 要放置的极片 - location: 极片在洞位中的位置(对于洞位,通常为None) - reassign: 是否允许重新分配 - """ - # 检查是否可以添加极片 - if not self.can_add_sheet(resource): - raise ValueError(f"无法向洞位 {self.name} 添加极片:直径或厚度不匹配") - - # 调用父类方法实际执行分配 - super().assign_child_resource(resource, location, reassign) - - def unassign_child_resource(self, resource: ElectrodeSheet): - """从洞位中移除极片 - - Args: - resource: 要移除的极片 - """ - if resource not in self.children: - raise ValueError(f"极片 {resource.name} 不在洞位 {self.name} 中") - - # 调用父类方法实际执行移除 - super().unassign_child_resource(resource) - - - - def serialize_state(self) -> Dict[str, Any]: - return { - "sheet_count": len(self.children), - "sheets": [sheet.serialize() for sheet in self.children], - } -class ClipMagazine_four(ItemizedResource[ClipMagazineHole]): - """子弹夹类 - 有4个洞位,每个洞位放多个极片""" - children: List[ClipMagazineHole] - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - hole_diameter: float = 14.0, - hole_depth: float = 10.0, - hole_spacing: float = 25.0, - max_sheets_per_hole: int = 100, - category: str = "clip_magazine_four", - model: Optional[str] = None, - ): - """初始化子弹夹 - - Args: - name: 子弹夹名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - hole_diameter: 洞直径 (mm) - hole_depth: 洞深度 (mm) - hole_spacing: 洞位间距 (mm) - max_sheets_per_hole: 每个洞位最大极片数量 - category: 类别 - model: 型号 - """ - # 创建4个洞位,排成2x2布局 - holes = create_ordered_items_2d( - klass=ClipMagazineHole, - num_items_x=2, - num_items_y=2, - dx=(size_x - 2 * hole_spacing) / 2, # 居中 - dy=(size_y - hole_spacing) / 2, # 居中 - dz=size_z - 0, - item_dx=hole_spacing, - item_dy=hole_spacing, - diameter=hole_diameter, - depth=hole_depth, - ) - - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=holes, - category=category, - model=model, - ) - - # 保存洞位的直径和深度 - self.hole_diameter = hole_diameter - self.hole_depth = hole_depth - self.max_sheets_per_hole = max_sheets_per_hole - - def serialize(self) -> dict: - return { - **super().serialize(), - "hole_diameter": self.hole_diameter, - "hole_depth": self.hole_depth, - "max_sheets_per_hole": self.max_sheets_per_hole, - } -# TODO: 这个要改 -class ClipMagazine(ItemizedResource[ClipMagazineHole]): - """子弹夹类 - 有6个洞位,每个洞位放多个极片""" - children: List[ClipMagazineHole] - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - hole_diameter: float = 14.0, - hole_depth: float = 10.0, - hole_spacing: float = 25.0, - max_sheets_per_hole: int = 100, - category: str = "clip_magazine", - model: Optional[str] = None, - ): - """初始化子弹夹 - - Args: - name: 子弹夹名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - hole_diameter: 洞直径 (mm) - hole_depth: 洞深度 (mm) - hole_spacing: 洞位间距 (mm) - max_sheets_per_hole: 每个洞位最大极片数量 - category: 类别 - model: 型号 - """ - # 创建6个洞位,排成2x3布局 - holes = create_ordered_items_2d( - klass=ClipMagazineHole, - num_items_x=3, - num_items_y=2, - dx=(size_x - 2 * hole_spacing) / 2, # 居中 - dy=(size_y - hole_spacing) / 2, # 居中 - dz=size_z - 0, - item_dx=hole_spacing, - item_dy=hole_spacing, - diameter=hole_diameter, - depth=hole_depth, - ) - - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=holes, - category=category, - model=model, - ) - - # 保存洞位的直径和深度 - self.hole_diameter = hole_diameter - self.hole_depth = hole_depth - self.max_sheets_per_hole = max_sheets_per_hole - - def serialize(self) -> dict: - return { - **super().serialize(), - "hole_diameter": self.hole_diameter, - "hole_depth": self.hole_depth, - "max_sheets_per_hole": self.max_sheets_per_hole, - } -#是一种类型注解,不用self -class BatteryState(TypedDict): - """电池状态字典""" - diameter: float - height: float - - electrolyte_name: str - electrolyte_volume: float - -class Battery(Resource): - """电池类 - 可容纳极片""" - children: List[ElectrodeSheet] = [] - - def __init__( - self, - name: str, - category: str = "battery", - ): - """初始化电池 - - Args: - name: 电池名称 - diameter: 直径 (mm) - height: 高度 (mm) - max_volume: 最大容量 (μL) - barcode: 二维码编号 - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=1, - size_y=1, - size_z=1, - category=category, - ) - self._unilabos_state: BatteryState = BatteryState() - - def add_electrolyte_with_bottle(self, bottle: Bottle) -> bool: - to_add_name = bottle._unilabos_state["electrolyte_name"] - if bottle.aspirate_electrolyte(10): - if self.add_electrolyte(to_add_name, 10): - pass - else: - bottle._unilabos_state["electrolyte_volume"] += 10 - - def set_electrolyte(self, name: str, volume: float) -> None: - """设置电解液信息""" - self._unilabos_state["electrolyte_name"] = name - self._unilabos_state["electrolyte_volume"] = volume - #这个应该没用,不会有加了后再加的事情 - def add_electrolyte(self, name: str, volume: float) -> bool: - """添加电解液信息""" - if name != self._unilabos_state["electrolyte_name"]: - return False - self._unilabos_state["electrolyte_volume"] += volume - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - -# 电解液作为属性放进去 - -class BatteryPressSlotState(TypedDict): - """电池状态字典""" - diameter: float =20.0 - depth: float = 4.0 - -class BatteryPressSlot(Resource): - """电池压制槽类 - 设备,可容纳一个电池""" - children: List[Battery] = [] - - def __init__( - self, - name: str = "BatteryPressSlot", - category: str = "battery_press_slot", - ): - """初始化电池压制槽 - - Args: - name: 压制槽名称 - diameter: 直径 (mm) - depth: 深度 (mm) - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=10, - size_y=12, - size_z=13, - category=category, - ) - self._unilabos_state: BatteryPressSlotState = BatteryPressSlotState() - - def has_battery(self) -> bool: - """检查是否有电池""" - return len(self.children) > 0 - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - - def assign_child_resource( - self, - resource: Battery, - location: Optional[Coordinate], - reassign: bool = True, - ): - """放置极片""" - if self.has_battery(): - raise ValueError(f"槽位已含有一个电池,无法再放置其他电池") - super().assign_child_resource(resource, location, reassign) - - # 根据children的编号取物料对象。 - def get_battery_info(self, index: int) -> Battery: - return self.children[0] - -class TipBox64State(TypedDict): - """电池状态字典""" - tip_diameter: float = 5.0 - tip_length: float = 50.0 - with_tips: bool = True - -class TipBox64(TipRack): - """64孔枪头盒类""" - - children: List[TipSpot] = [] - def __init__( - self, - name: str, - size_x: float = 127.8, - size_y: float = 85.5, - size_z: float = 60.0, - category: str = "tip_box_64", - model: Optional[str] = None, - ): - """初始化64孔枪头盒 - - Args: - name: 枪头盒名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - tip_diameter: 枪头直径 (mm) - tip_length: 枪头长度 (mm) - category: 类别 - model: 型号 - with_tips: 是否带枪头 - """ - from pylabrobot.resources.tip import Tip - - # 创建8x8=64个枪头位 - def make_tip(): - return Tip( - has_filter=False, - total_tip_length=20.0, - maximal_volume=1000, # 1mL - fitting_depth=8.0, - ) - - tip_spots = create_ordered_items_2d( - klass=TipSpot, - num_items_x=8, - num_items_y=8, - dx=8.0, - dy=8.0, - dz=0.0, - item_dx=9.0, - item_dy=9.0, - size_x=10, - size_y=10, - size_z=0.0, - make_tip=make_tip, - ) - self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate() - # 记录网格参数用于前端渲染 - self._grid_params = { - "num_items_x": 8, - "num_items_y": 8, - "dx": 8.0, - "dy": 8.0, - "item_dx": 9.0, - "item_dy": 9.0, - } - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=tip_spots, - category=category, - model=model, - with_tips=True, - ) - - def serialize(self) -> dict: - return { - **super().serialize(), - **self._grid_params, - } - - - -class WasteTipBoxstate(TypedDict): - """"废枪头盒状态字典""" - max_tips: int = 100 - tip_count: int = 0 - -#枪头不是一次性的(同一溶液则反复使用),根据寄存器判断 -class WasteTipBox(Trash): - """废枪头盒类 - 100个枪头容量""" - - def __init__( - self, - name: str, - size_x: float = 127.8, - size_y: float = 85.5, - size_z: float = 60.0, - category: str = "waste_tip_box", - model: Optional[str] = None, - ): - """初始化废枪头盒 - - Args: - name: 废枪头盒名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - max_tips: 最大枪头容量 - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - category=category, - model=model, - ) - self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate() - - def add_tip(self) -> None: - """添加废枪头""" - if self._unilabos_state["tip_count"] >= self._unilabos_state["max_tips"]: - raise ValueError(f"废枪头盒 {self.name} 已满") - self._unilabos_state["tip_count"] += 1 - - def get_tip_count(self) -> int: - """获取枪头数量""" - return self._unilabos_state["tip_count"] - - def empty(self) -> None: - """清空废枪头盒""" - self._unilabos_state["tip_count"] = 0 - - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - - -class BottleRackState(TypedDict): - """ bottle_diameter: 瓶子直径 (mm) - bottle_height: 瓶子高度 (mm) - position_spacing: 位置间距 (mm)""" - bottle_diameter: float - bottle_height: float - name_to_index: dict - - -class BottleRackState(TypedDict): - """ bottle_diameter: 瓶子直径 (mm) - bottle_height: 瓶子高度 (mm) - position_spacing: 位置间距 (mm)""" - bottle_diameter: float - bottle_height: float - position_spacing: float - name_to_index: dict - - -class BottleRack(Resource): - """瓶架类 - 12个待配位置+12个已配位置""" - children: List[Resource] = [] - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - category: str = "bottle_rack", - model: Optional[str] = None, - num_items_x: int = 3, - num_items_y: int = 4, - position_spacing: float = 35.0, - orientation: str = "horizontal", - padding_x: float = 20.0, - padding_y: float = 20.0, - ): - """初始化瓶架 - - Args: - name: 瓶架名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - category=category, - model=model, - ) - # 初始化状态 - self._unilabos_state: BottleRackState = BottleRackState( - bottle_diameter=30.0, - bottle_height=100.0, - position_spacing=position_spacing, - name_to_index={}, - ) - # 基于网格生成瓶位坐标映射(居中摆放) - # 使用内边距,避免点跑到容器外(前端渲染不按mm等比缩放时更稳妥) - origin_x = padding_x - origin_y = padding_y - self.index_to_pos = {} - for j in range(num_items_y): - for i in range(num_items_x): - idx = j * num_items_x + i - if orientation == "vertical": - # 纵向:沿 y 方向优先排列 - self.index_to_pos[idx] = Coordinate( - x=origin_x + j * position_spacing, - y=origin_y + i * position_spacing, - z=0, - ) - else: - # 横向(默认):沿 x 方向优先排列 - self.index_to_pos[idx] = Coordinate( - x=origin_x + i * position_spacing, - y=origin_y + j * position_spacing, - z=0, - ) - self.name_to_index = {} - self.name_to_pos = {} - self.num_items_x = num_items_x - self.num_items_y = num_items_y - self.orientation = orientation - self.padding_x = padding_x - self.padding_y = padding_y - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update( - self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - - # TODO: 这里有些问题要重新写一下 - def assign_child_resource_old(self, resource: Resource, location=Coordinate.zero(), reassign=True): - capacity = self.num_items_x * self.num_items_y - assert len(self.children) < capacity, "瓶架已满,无法添加更多瓶子" - index = len(self.children) - location = self.index_to_pos.get(index, Coordinate.zero()) - self.name_to_pos[resource.name] = location - self.name_to_index[resource.name] = index - return super().assign_child_resource(resource, location, reassign) - - def assign_child_resource(self, resource: Resource, index: int): - capacity = self.num_items_x * self.num_items_y - assert 0 <= index < capacity, "无效的瓶子索引" - self.name_to_index[resource.name] = index - location = self.index_to_pos[index] - return super().assign_child_resource(resource, location) - - def unassign_child_resource(self, resource: Bottle): - super().unassign_child_resource(resource) - self.index_to_pos.pop(self.name_to_index.pop(resource.name, None), None) - - def serialize(self) -> dict: - return { - **super().serialize(), - "num_items_x": self.num_items_x, - "num_items_y": self.num_items_y, - "position_spacing": self._unilabos_state.get("position_spacing", 35.0), - "orientation": self.orientation, - "padding_x": self.padding_x, - "padding_y": self.padding_y, - } - - -class BottleState(TypedDict): - diameter: float - height: float - electrolyte_name: str - electrolyte_volume: float - max_volume: float - -class Bottle(Resource): - """瓶子类 - 容纳电解液""" - - def __init__( - self, - name: str, - category: str = "bottle", - ): - """初始化瓶子 - - Args: - name: 瓶子名称 - diameter: 直径 (mm) - height: 高度 (mm) - max_volume: 最大体积 (μL) - barcode: 二维码 - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=1, - size_y=1, - size_z=1, - category=category, - ) - self._unilabos_state: BottleState = BottleState() - - def aspirate_electrolyte(self, volume: float) -> bool: - current_volume = self._unilabos_state["electrolyte_volume"] - assert current_volume > volume, f"Cannot aspirate {volume}μL, only {current_volume}μL available." - self._unilabos_state["electrolyte_volume"] -= volume - return True - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - -class CoincellDeck(Deck): - """纽扣电池组装工作站台面类""" - - def __init__( - self, - name: str = "coin_cell_deck", - size_x: float = 1620.0, # 3.66m - size_y: float = 1270.0, # 1.23m - size_z: float = 500.0, - origin: Coordinate = Coordinate(0, 0, 0), - category: str = "coin_cell_deck", - ): - """初始化纽扣电池组装工作站台面 - - Args: - name: 台面名称 - size_x: 长度 (mm) - 3.66m - size_y: 宽度 (mm) - 1.23m - size_z: 高度 (mm) - origin: 原点坐标 - category: 类别 - """ - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - origin=origin, - category=category, - ) - -#if __name__ == "__main__": -# # 转移极片的测试代码 -# deck = CoincellDeck("coin_cell_deck") -# ban_cao_wei = PlateSlot("ban_cao_wei", max_plates=8) -# deck.assign_child_resource(ban_cao_wei, Coordinate(x=0, y=0, z=0)) -# -# plate_1 = MaterialPlate("plate_1", 1,1,1, fill=True) -# for i, hole in enumerate(plate_1.children): -# sheet = ElectrodeSheet(f"hole_{i}_sheet_1") -# sheet._unilabos_state = { -# "diameter": 14, -# "info": "NMC", -# "mass": 5.0, -# "material_type": "positive_electrode", -# "thickness": 0.1 -# } -# hole._unilabos_state = { -# "depth": 1.0, -# "diameter": 14, -# "info": "", -# "max_sheets": 1 -# } -# hole.assign_child_resource(sheet, Coordinate.zero()) -# plate_1._unilabos_state = { -# "hole_spacing_x": 20.0, -# "hole_spacing_y": 20.0, -# "hole_diameter": 5, -# "info": "这是第一块料板" -# } -# plate_1.update_locations() -# ban_cao_wei.assign_child_resource(plate_1, Coordinate.zero()) -# # zi_dan_jia = ClipMagazine("zi_dan_jia", 1, 1, 1) -# # deck.assign_child_resource(ban_cao_wei, Coordinate(x=200, y=200, z=0)) -# -# from unilabos.resources.graphio import * -# A = tree_to_list([resource_plr_to_ulab(deck)]) -# with open("test.json", "w") as f: -# json.dump(A, f) -# -# -#def get_plate_with_14mm_hole(name=""): -# plate = MaterialPlate(name=name) -# for i in range(4): -# for j in range(4): -# hole = MaterialHole(f"{i+1}x{j+1}") -# hole._unilabos_state["diameter"] = 14 -# hole._unilabos_state["max_sheets"] = 1 -# plate.assign_child_resource(hole) -# return plate - -def create_a_liaopan(): - liaopan = MaterialPlate(name="liaopan", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) - for i in range(16): - jipian = ElectrodeSheet(name=f"jipian_{i}", size_x= 12, size_y=12, size_z=0.1) - liaopan1.children[i].assign_child_resource(jipian, location=None) - return liaopan - -def create_a_coin_cell_deck(): - deck = Deck(size_x=1200, - size_y=800, - size_z=900) - - #liaopan = TipBox64(name="liaopan") - - #创建一个4*4的物料板 - liaopan1 = MaterialPlate(name="liaopan1", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) - #把物料板放到桌子上 - deck.assign_child_resource(liaopan1, Coordinate(x=0, y=0, z=0)) - #创建一个极片 - for i in range(16): - jipian = ElectrodeSheet(name=f"jipian_{i}", size_x= 12, size_y=12, size_z=0.1) - liaopan1.children[i].assign_child_resource(jipian, location=None) - #创建一个4*4的物料板 - liaopan2 = MaterialPlate(name="liaopan2", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) - #把物料板放到桌子上 - deck.assign_child_resource(liaopan2, Coordinate(x=500, y=0, z=0)) - - #创建一个4*4的物料板 - liaopan3 = MaterialPlate(name="liaopan3", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) - #把物料板放到桌子上 - deck.assign_child_resource(liaopan3, Coordinate(x=1000, y=0, z=0)) - - print(deck) - - return deck - - -import json - -if __name__ == "__main__": - electrode1 = BatteryPressSlot() - #print(electrode1.get_size_x()) - #print(electrode1.get_size_y()) - #print(electrode1.get_size_z()) - #jipian = ElectrodeSheet() - #jipian._unilabos_state["diameter"] = 18 - #print(jipian.serialize()) - #print(jipian.serialize_state()) - - deck = CoincellDeck() - """======================================子弹夹============================================""" - zip_dan_jia = ClipMagazine_four("zi_dan_jia", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia, Coordinate(x=1400, y=50, z=0)) - zip_dan_jia2 = ClipMagazine_four("zi_dan_jia2", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia2, Coordinate(x=1600, y=200, z=0)) - zip_dan_jia3 = ClipMagazine("zi_dan_jia3", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia3, Coordinate(x=1500, y=200, z=0)) - zip_dan_jia4 = ClipMagazine("zi_dan_jia4", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia4, Coordinate(x=1500, y=300, z=0)) - zip_dan_jia5 = ClipMagazine("zi_dan_jia5", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia5, Coordinate(x=1600, y=300, z=0)) - zip_dan_jia6 = ClipMagazine("zi_dan_jia6", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia6, Coordinate(x=1530, y=500, z=0)) - zip_dan_jia7 = ClipMagazine("zi_dan_jia7", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia7, Coordinate(x=1180, y=400, z=0)) - zip_dan_jia8 = ClipMagazine("zi_dan_jia8", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia8, Coordinate(x=1280, y=400, z=0)) - for i in range(4): - jipian = ElectrodeSheet(name=f"zi_dan_jia_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia2.children[i].assign_child_resource(jipian, location=None) - for i in range(4): - jipian2 = ElectrodeSheet(name=f"zi_dan_jia2_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia.children[i].assign_child_resource(jipian2, location=None) - for i in range(6): - jipian3 = ElectrodeSheet(name=f"zi_dan_jia3_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia3.children[i].assign_child_resource(jipian3, location=None) - for i in range(6): - jipian4 = ElectrodeSheet(name=f"zi_dan_jia4_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia4.children[i].assign_child_resource(jipian4, location=None) - for i in range(6): - jipian5 = ElectrodeSheet(name=f"zi_dan_jia5_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia5.children[i].assign_child_resource(jipian5, location=None) - for i in range(6): - jipian6 = ElectrodeSheet(name=f"zi_dan_jia6_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia6.children[i].assign_child_resource(jipian6, location=None) - for i in range(6): - jipian7 = ElectrodeSheet(name=f"zi_dan_jia7_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia7.children[i].assign_child_resource(jipian7, location=None) - for i in range(6): - jipian8 = ElectrodeSheet(name=f"zi_dan_jia8_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia8.children[i].assign_child_resource(jipian8, location=None) - """======================================子弹夹============================================""" - #liaopan = TipBox64(name="liaopan") - """======================================物料板============================================""" - #创建一个4*4的物料板 - liaopan1 = MaterialPlate(name="liaopan1", size_x=120, size_y=100, size_z=10.0, fill=True) - deck.assign_child_resource(liaopan1, Coordinate(x=1010, y=50, z=0)) - for i in range(16): - jipian_1 = ElectrodeSheet(name=f"{liaopan1.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - liaopan1.children[i].assign_child_resource(jipian_1, location=None) - - liaopan2 = MaterialPlate(name="liaopan2", size_x=120, size_y=100, size_z=10.0, fill=True) - deck.assign_child_resource(liaopan2, Coordinate(x=1130, y=50, z=0)) - - liaopan3 = MaterialPlate(name="liaopan3", size_x=120, size_y=100, size_z=10.0, fill=True) - deck.assign_child_resource(liaopan3, Coordinate(x=1250, y=50, z=0)) - - liaopan4 = MaterialPlate(name="liaopan4", size_x=120, size_y=100, size_z=10.0, fill=True) - deck.assign_child_resource(liaopan4, Coordinate(x=1010, y=150, z=0)) - for i in range(16): - jipian_4 = ElectrodeSheet(name=f"{liaopan4.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - liaopan4.children[i].assign_child_resource(jipian_4, location=None) - liaopan5 = MaterialPlate(name="liaopan5", size_x=120, size_y=100, size_z=10.0, fill=True) - deck.assign_child_resource(liaopan5, Coordinate(x=1130, y=150, z=0)) - liaopan6 = MaterialPlate(name="liaopan6", size_x=120, size_y=100, size_z=10.0, fill=True) - deck.assign_child_resource(liaopan6, Coordinate(x=1250, y=150, z=0)) - #liaopan.children[3].assign_child_resource(jipian, location=None) - """======================================物料板============================================""" - """======================================瓶架,移液枪============================================""" - # 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒 - bottle_rack_3x4 = BottleRack( - name="bottle_rack_3x4", - size_x=210.0, - size_y=140.0, - size_z=100.0, - num_items_x=3, - num_items_y=4, - position_spacing=35.0, - orientation="vertical", - ) - deck.assign_child_resource(bottle_rack_3x4, Coordinate(x=100, y=200, z=0)) - - bottle_rack_6x2 = BottleRack( - name="bottle_rack_6x2", - size_x=120.0, - size_y=250.0, - size_z=100.0, - num_items_x=6, - num_items_y=2, - position_spacing=35.0, - orientation="vertical", - ) - deck.assign_child_resource(bottle_rack_6x2, Coordinate(x=300, y=300, z=0)) - - bottle_rack_6x2_2 = BottleRack( - name="bottle_rack_6x2_2", - size_x=120.0, - size_y=250.0, - size_z=100.0, - num_items_x=6, - num_items_y=2, - position_spacing=35.0, - orientation="vertical", - ) - deck.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=430, y=300, z=0)) - - - # 将 ElectrodeSheet 放满 3x4 与 6x2 的所有孔位 - for idx in range(bottle_rack_3x4.num_items_x * bottle_rack_3x4.num_items_y): - sheet = ElectrodeSheet(name=f"sheet_3x4_{idx}", size_x=12, size_y=12, size_z=0.1) - bottle_rack_3x4.assign_child_resource(sheet, index=idx) - - for idx in range(bottle_rack_6x2.num_items_x * bottle_rack_6x2.num_items_y): - sheet = ElectrodeSheet(name=f"sheet_6x2_{idx}", size_x=12, size_y=12, size_z=0.1) - bottle_rack_6x2.assign_child_resource(sheet, index=idx) - - tip_box = TipBox64(name="tip_box_64") - deck.assign_child_resource(tip_box, Coordinate(x=300, y=100, z=0)) - - waste_tip_box = WasteTipBox(name="waste_tip_box") - deck.assign_child_resource(waste_tip_box, Coordinate(x=300, y=200, z=0)) - """======================================瓶架,移液枪============================================""" - print(deck) - - - from unilabos.resources.graphio import convert_resources_from_type - from unilabos.config.config import BasicConfig - BasicConfig.ak = "56bbed5b-6e30-438c-b06d-f69eaa63bb45" - BasicConfig.sk = "238222fe-0bf7-4350-a426-e5ced8011dcf" - from unilabos.app.web.client import http_client - - resources = convert_resources_from_type([deck], [Resource]) - - # 检查序列化后的资源 - - json.dump({"nodes": resources, "links": []}, open("button_battery_decks_unilab.json", "w"), indent=2) - - - #print(resources) - http_client.remote_addr = "https://uni-lab.test.bohrium.com/api/v1" - - http_client.resource_add(resources) \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py deleted file mode 100644 index 4758bdd..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ /dev/null @@ -1,1142 +0,0 @@ -import csv -import json -import os -import threading -import time -from datetime import datetime -from typing import Any, Dict, Optional -from pylabrobot.resources import Resource as PLRResource -from unilabos_msgs.msg import Resource -from unilabos.device_comms.modbus_plc.client import ModbusTcpClient -from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import MaterialHole, MaterialPlate -from unilabos.devices.workstation.workstation_base import WorkstationBase -from unilabos.device_comms.modbus_plc.client import TCPClient, ModbusNode, PLCWorkflow, ModbusWorkflow, WorkflowAction, BaseClient -from unilabos.device_comms.modbus_plc.modbus import DeviceType, Base as ModbusNodeBase, DataType, WorderOrder -from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import * -from unilabos.ros.nodes.base_device_node import ROS2DeviceNode, BaseROS2DeviceNode -from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode - -#构建物料系统 - -class CoinCellAssemblyWorkstation(WorkstationBase): - def __init__( - self, - deck: CoincellDeck, - address: str = "192.168.1.20", - port: str = "502", - debug_mode: bool = True, - *args, - **kwargs, - ): - super().__init__( - #桌子 - deck=deck, - *args, - **kwargs, - ) - self.debug_mode = debug_mode - self.deck = deck - """ 连接初始化 """ - modbus_client = TCPClient(addr=address, port=port) - print("modbus_client", modbus_client) - if not debug_mode: - modbus_client.client.connect() - count = 100 - while count >0: - count -=1 - if modbus_client.client.is_socket_open(): - break - time.sleep(2) - if not modbus_client.client.is_socket_open(): - raise ValueError('modbus tcp connection failed') - else: - print("测试模式,跳过连接") - - """ 工站的配置 """ - self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_a.csv')) - self.client = modbus_client.register_node_list(self.nodes) - self.success = False - self.allow_data_read = False #允许读取函数运行标志位 - self.csv_export_thread = None - self.csv_export_running = False - self.csv_export_file = None - #创建一个物料台面,包含两个极片板 - #self.deck = create_a_coin_cell_deck() - - #self._ros_node.update_resource(self.deck) - - #ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ - # "resources": [self.deck] - #}) - - - def post_init(self, ros_node: ROS2WorkstationNode): - self._ros_node = ros_node - #self.deck = create_a_coin_cell_deck() - ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ - "resources": [self.deck] - }) - - # 批量操作在这里写 - async def change_hole_sheet_to_2(self, hole: MaterialHole): - hole._unilabos_state["max_sheets"] = 2 - return await self._ros_node.update_resource(hole) - - - async def fill_plate(self): - plate_1: MaterialPlate = self.deck.children[0].children[0] - #plate_1 - return await self._ros_node.update_resource(plate_1) - - #def run_assembly(self, wf_name: str, resource: PLRResource, params: str = "\{\}"): - # """启动工作流""" - # self.current_workflow_status = WorkflowStatus.RUNNING - # logger.info(f"工作站 {self.device_id} 启动工作流: {wf_name}") -# - # # TODO: 实现工作流逻辑 -# - # anode_sheet = self.deck.get_resource("anode_sheet") - - """ Action逻辑代码 """ - def _sys_start_cmd(self, cmd=None): - """设备启动命令 (可读写)""" - if cmd is not None: # 写入模式 - self.success = False - node = self.client.use_node('COIL_SYS_START_CMD') - ret = node.write(cmd) - print(ret) - self.success = True - return self.success - else: # 读取模式 - cmd_feedback, read_err = self.client.use_node('COIL_SYS_START_CMD').read(1) - return cmd_feedback[0] - - def _sys_stop_cmd(self, cmd=None): - """设备停止命令 (可读写)""" - if cmd is not None: # 写入模式 - self.success = False - node = self.client.use_node('COIL_SYS_STOP_CMD') - node.write(cmd) - self.success = True - return self.success - else: # 读取模式 - cmd_feedback, read_err = self.client.use_node('COIL_SYS_STOP_CMD').read(1) - return cmd_feedback[0] - - def _sys_reset_cmd(self, cmd=None): - """设备复位命令 (可读写)""" - if cmd is not None: - self.success = False - self.client.use_node('COIL_SYS_RESET_CMD').write(cmd) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('COIL_SYS_RESET_CMD').read(1) - return cmd_feedback[0] - - def _sys_hand_cmd(self, cmd=None): - """手动模式命令 (可读写)""" - if cmd is not None: - self.success = False - self.client.use_node('COIL_SYS_HAND_CMD').write(cmd) - self.success = True - print("步骤0") - return self.success - else: - cmd_feedback, read_err = self.client.use_node('COIL_SYS_HAND_CMD').read(1) - return cmd_feedback[0] - - def _sys_auto_cmd(self, cmd=None): - """自动模式命令 (可读写)""" - if cmd is not None: - self.success = False - self.client.use_node('COIL_SYS_AUTO_CMD').write(cmd) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('COIL_SYS_AUTO_CMD').read(1) - return cmd_feedback[0] - - def _sys_init_cmd(self, cmd=None): - """初始化命令 (可读写)""" - if cmd is not None: - self.success = False - self.client.use_node('COIL_SYS_INIT_CMD').write(cmd) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('COIL_SYS_INIT_CMD').read(1) - return cmd_feedback[0] - - def _unilab_send_msg_succ_cmd(self, cmd=None): - """UNILAB发送配方完毕 (可读写)""" - if cmd is not None: - self.success = False - self.client.use_node('COIL_UNILAB_SEND_MSG_SUCC_CMD').write(cmd) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('COIL_UNILAB_SEND_MSG_SUCC_CMD').read(1) - return cmd_feedback[0] - - def _unilab_rec_msg_succ_cmd(self, cmd=None): - """UNILAB接收测试电池数据完毕 (可读写)""" - if cmd is not None: - self.success = False - self.client.use_node('COIL_UNILAB_REC_MSG_SUCC_CMD').write(cmd) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('COIL_UNILAB_REC_MSG_SUCC_CMD').read(1) - return cmd_feedback - - - # ====================== 命令类指令(REG_x_) ====================== - def _unilab_send_msg_electrolyte_num(self, num=None): - """UNILAB写电解液使用瓶数(可读写)""" - if num is not None: - self.success = False - ret = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').write(num) - print(ret) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').read(1) - return cmd_feedback[0] - - def _unilab_send_msg_electrolyte_use_num(self, use_num=None): - """UNILAB写单次电解液使用瓶数(可读写)""" - if use_num is not None: - self.success = False - self.client.use_node('REG_MSG_ELECTROLYTE_USE_NUM').write(use_num) - self.success = True - return self.success - else: - return False - - def _unilab_send_msg_assembly_type(self, num=None): - """UNILAB写组装参数""" - if num is not None: - self.success = False - self.client.use_node('REG_MSG_ASSEMBLY_TYPE').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('REG_MSG_ASSEMBLY_TYPE').read(1) - return cmd_feedback[0] - - def _unilab_send_msg_electrolyte_vol(self, vol=None): - """UNILAB写电解液吸取量参数""" - if vol is not None: - self.success = False - self.client.use_node('REG_MSG_ELECTROLYTE_VOLUME').write(vol, data_type=DataType.FLOAT32, word_order=WorderOrder.LITTLE) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_VOLUME').read(2, word_order=WorderOrder.LITTLE) - return cmd_feedback[0] - - def _unilab_send_msg_assembly_pressure(self, vol=None): - """UNILAB写电池压制力""" - if vol is not None: - self.success = False - self.client.use_node('REG_MSG_ASSEMBLY_PRESSURE').write(vol, data_type=DataType.FLOAT32, word_order=WorderOrder.LITTLE) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('REG_MSG_ASSEMBLY_PRESSURE').read(2, word_order=WorderOrder.LITTLE) - return cmd_feedback[0] - - # ==================== 0905新增内容(COIL_x_STATUS) ==================== - def _unilab_send_electrolyte_bottle_num(self, num=None): - """UNILAB发送电解液瓶数完毕""" - if num is not None: - self.success = False - self.client.use_node('UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM').read(1) - return cmd_feedback[0] - - def _unilab_rece_electrolyte_bottle_num(self, num=None): - """设备请求接受电解液瓶数""" - if num is not None: - self.success = False - self.client.use_node('UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM').read(1) - return cmd_feedback[0] - - def _reg_msg_electrolyte_num(self, num=None): - """电解液已使用瓶数""" - if num is not None: - self.success = False - self.client.use_node('REG_MSG_ELECTROLYTE_NUM').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').read(1) - return cmd_feedback[0] - - def _reg_data_electrolyte_use_num(self, num=None): - """单瓶电解液完成组装数""" - if num is not None: - self.success = False - self.client.use_node('REG_DATA_ELECTROLYTE_USE_NUM').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_USE_NUM').read(1) - return cmd_feedback[0] - - def _unilab_send_finished_cmd(self, num=None): - """Unilab发送已知一组组装完成信号""" - if num is not None: - self.success = False - self.client.use_node('UNILAB_SEND_FINISHED_CMD').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('UNILAB_SEND_FINISHED_CMD').read(1) - return cmd_feedback[0] - - def _unilab_rece_finished_cmd(self, num=None): - """Unilab接收已知一组组装完成信号""" - if num is not None: - self.success = False - self.client.use_node('UNILAB_RECE_FINISHED_CMD').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('UNILAB_RECE_FINISHED_CMD').read(1) - return cmd_feedback[0] - - - - # ==================== 状态类属性(COIL_x_STATUS) ==================== - def _sys_start_status(self) -> bool: - """设备启动中( BOOL)""" - status, read_err = self.client.use_node('COIL_SYS_START_STATUS').read(1) - return status[0] - - def _sys_stop_status(self) -> bool: - """设备停止中( BOOL)""" - status, read_err = self.client.use_node('COIL_SYS_STOP_STATUS').read(1) - return status[0] - - def _sys_reset_status(self) -> bool: - """设备复位中( BOOL)""" - status, read_err = self.client.use_node('COIL_SYS_RESET_STATUS').read(1) - return status[0] - - def _sys_init_status(self) -> bool: - """设备初始化完成( BOOL)""" - status, read_err = self.client.use_node('COIL_SYS_INIT_STATUS').read(1) - return status[0] - - # 查找资源 - def modify_deck_name(self, resource_name: str): - # figure_res = self._ros_node.resource_tracker.figure_resource({"name": resource_name}) - # print(f"!!! figure_res: {type(figure_res)}") - self.deck.children[1] - return - - @property - def sys_status(self) -> str: - if self.debug_mode: - return "设备调试模式" - if self._sys_start_status(): - return "设备启动中" - elif self._sys_stop_status(): - return "设备停止中" - elif self._sys_reset_status(): - return "设备复位中" - elif self._sys_init_status(): - return "设备初始化中" - else: - return "未知状态" - - def _sys_hand_status(self) -> bool: - """设备手动模式( BOOL)""" - status, read_err = self.client.use_node('COIL_SYS_HAND_STATUS').read(1) - return status[0] - - def _sys_auto_status(self) -> bool: - """设备自动模式( BOOL)""" - status, read_err = self.client.use_node('COIL_SYS_AUTO_STATUS').read(1) - return status[0] - - @property - def sys_mode(self) -> str: - if self.debug_mode: - return "设备调试模式" - if self._sys_hand_status(): - return "设备手动模式" - elif self._sys_auto_status(): - return "设备自动模式" - else: - return "未知模式" - - @property - def request_rec_msg_status(self) -> bool: - """设备请求接受配方( BOOL)""" - if self.debug_mode: - return True - status, read_err = self.client.use_node('COIL_REQUEST_REC_MSG_STATUS').read(1) - return status[0] - - @property - def request_send_msg_status(self) -> bool: - """设备请求发送测试数据( BOOL)""" - if self.debug_mode: - return True - status, read_err = self.client.use_node('COIL_REQUEST_SEND_MSG_STATUS').read(1) - return status[0] - - # ======================= 其他属性(特殊功能) ======================== - ''' - @property - def warning_1(self) -> bool: - status, read_err = self.client.use_node('COIL_WARNING_1').read(1) - return status[0] - ''' - # ===================== 生产数据区 ====================== - - @property - def data_assembly_coin_cell_num(self) -> int: - """已完成电池数量 (INT16)""" - if self.debug_mode: - return 0 - num, read_err = self.client.use_node('REG_DATA_ASSEMBLY_COIN_CELL_NUM').read(1) - return num - - @property - def data_assembly_time(self) -> float: - """单颗电池组装时间 (秒, REAL/FLOAT32)""" - if self.debug_mode: - return 0 - time, read_err = self.client.use_node('REG_DATA_ASSEMBLY_PER_TIME').read(2, word_order=WorderOrder.LITTLE) - return time - - @property - def data_open_circuit_voltage(self) -> float: - """开路电压值 (FLOAT32)""" - if self.debug_mode: - return 0 - vol, read_err = self.client.use_node('REG_DATA_OPEN_CIRCUIT_VOLTAGE').read(2, word_order=WorderOrder.LITTLE) - return vol - - @property - def data_axis_x_pos(self) -> float: - """分液X轴当前位置 (FLOAT32)""" - if self.debug_mode: - return 0 - pos, read_err = self.client.use_node('REG_DATA_AXIS_X_POS').read(2, word_order=WorderOrder.LITTLE) - return pos - - @property - def data_axis_y_pos(self) -> float: - """分液Y轴当前位置 (FLOAT32)""" - if self.debug_mode: - return 0 - pos, read_err = self.client.use_node('REG_DATA_AXIS_Y_POS').read(2, word_order=WorderOrder.LITTLE) - return pos - - @property - def data_axis_z_pos(self) -> float: - """分液Z轴当前位置 (FLOAT32)""" - if self.debug_mode: - return 0 - pos, read_err = self.client.use_node('REG_DATA_AXIS_Z_POS').read(2, word_order=WorderOrder.LITTLE) - return pos - - @property - def data_pole_weight(self) -> float: - """当前电池正极片称重数据 (FLOAT32)""" - if self.debug_mode: - return 0 - weight, read_err = self.client.use_node('REG_DATA_POLE_WEIGHT').read(2, word_order=WorderOrder.LITTLE) - return weight - - @property - def data_assembly_pressure(self) -> int: - """当前电池压制力 (INT16)""" - if self.debug_mode: - return 0 - pressure, read_err = self.client.use_node('REG_DATA_ASSEMBLY_PRESSURE').read(1) - return pressure - - @property - def data_electrolyte_volume(self) -> int: - """当前电解液加注量 (INT16)""" - if self.debug_mode: - return 0 - vol, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_VOLUME').read(1) - return vol - - @property - def data_coin_num(self) -> int: - """当前电池数量 (INT16)""" - if self.debug_mode: - return 0 - num, read_err = self.client.use_node('REG_DATA_COIN_NUM').read(1) - return num - - @property - def data_coin_cell_code(self) -> str: - """电池二维码序列号 (STRING)""" - try: - # 尝试不同的字节序读取 - code_little, read_err = self.client.use_node('REG_DATA_COIN_CELL_CODE').read(10, word_order=WorderOrder.LITTLE) - print(code_little) - clean_code = code_little[-8:][::-1] - return clean_code - except Exception as e: - print(f"读取电池二维码失败: {e}") - return "N/A" - - - @property - def data_electrolyte_code(self) -> str: - try: - # 尝试不同的字节序读取 - code_little, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_CODE').read(10, word_order=WorderOrder.LITTLE) - print(code_little) - clean_code = code_little[-8:][::-1] - return clean_code - except Exception as e: - print(f"读取电解液二维码失败: {e}") - return "N/A" - - # ===================== 环境监控区 ====================== - @property - def data_glove_box_pressure(self) -> float: - """手套箱压力 (bar, FLOAT32)""" - if self.debug_mode: - return 0 - status, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_PRESSURE').read(2, word_order=WorderOrder.LITTLE) - return status - - @property - def data_glove_box_o2_content(self) -> float: - """手套箱氧含量 (ppm, FLOAT32)""" - if self.debug_mode: - return 0 - value, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_O2_CONTENT').read(2, word_order=WorderOrder.LITTLE) - return value - - @property - def data_glove_box_water_content(self) -> float: - """手套箱水含量 (ppm, FLOAT32)""" - if self.debug_mode: - return 0 - value, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_WATER_CONTENT').read(2, word_order=WorderOrder.LITTLE) - return value - -# @property -# def data_stack_vision_code(self) -> int: -# """物料堆叠复检图片编码 (INT16)""" -# if self.debug_mode: -# return 0 -# code, read_err = self.client.use_node('REG_DATA_STACK_VISON_CODE').read(1) -# #code, _ = self.client.use_node('REG_DATA_STACK_VISON_CODE').read(1).type -# print(f"读取物料堆叠复检图片编码", {code}, "error", type(code)) -# #print(code.type) -# # print(read_err) -# return int(code) - - def func_pack_device_init(self): - #切换手动模式 - print("切换手动模式") - self._sys_hand_cmd(True) - time.sleep(1) - while (self._sys_hand_status()) == False: - print("waiting for hand_cmd") - time.sleep(1) - #设备初始化 - self._sys_init_cmd(True) - time.sleep(1) - #sys_init_status为bool值,不加括号 - while (self._sys_init_status())== False: - print("waiting for init_cmd") - time.sleep(1) - #手动按钮置回False - self._sys_hand_cmd(False) - time.sleep(1) - while (self._sys_hand_cmd()) == True: - print("waiting for hand_cmd to False") - time.sleep(1) - #初始化命令置回False - self._sys_init_cmd(False) - time.sleep(1) - while (self._sys_init_cmd()) == True: - print("waiting for init_cmd to False") - time.sleep(1) - - def func_pack_device_auto(self): - #切换自动 - print("切换自动模式") - self._sys_auto_cmd(True) - time.sleep(1) - while (self._sys_auto_status()) == False: - print("waiting for auto_status") - time.sleep(1) - #自动按钮置False - self._sys_auto_cmd(False) - time.sleep(1) - while (self._sys_auto_cmd()) == True: - print("waiting for auto_cmd") - time.sleep(1) - - def func_pack_device_start(self): - #切换自动 - print("启动") - self._sys_start_cmd(True) - time.sleep(1) - while (self._sys_start_status()) == False: - print("waiting for start_status") - time.sleep(1) - #自动按钮置False - self._sys_start_cmd(False) - time.sleep(1) - while (self._sys_start_cmd()) == True: - print("waiting for start_cmd") - time.sleep(1) - - def func_pack_send_bottle_num(self, bottle_num: int): - #发送电解液平台数 - print("启动") - while (self._unilab_rece_electrolyte_bottle_num()) == False: - print("waiting for rece_electrolyte_bottle_num to True") - # self.client.use_node('8520').write(True) - time.sleep(1) - #发送电解液瓶数为2 - self._reg_msg_electrolyte_num(bottle_num) - time.sleep(1) - #完成信号置True - self._unilab_send_electrolyte_bottle_num(True) - time.sleep(1) - #检测到依华已接收 - while (self._unilab_rece_electrolyte_bottle_num()) == True: - print("waiting for rece_electrolyte_bottle_num to False") - time.sleep(1) - #完成信号置False - self._unilab_send_electrolyte_bottle_num(False) - time.sleep(1) - #自动按钮置False - - - # 下发参数 - #def func_pack_send_msg_cmd(self, elec_num: int, elec_use_num: int, elec_vol: float, assembly_type: int, assembly_pressure: int) -> bool: - # """UNILAB写参数""" - # while (self.request_rec_msg_status) == False: - # print("wait for res_msg") - # time.sleep(1) - # self.success = False - # self._unilab_send_msg_electrolyte_num(elec_num) - # time.sleep(1) - # self._unilab_send_msg_electrolyte_use_num(elec_use_num) - # time.sleep(1) - # self._unilab_send_msg_electrolyte_vol(elec_vol) - # time.sleep(1) - # self._unilab_send_msg_assembly_type(assembly_type) - # time.sleep(1) - # self._unilab_send_msg_assembly_pressure(assembly_pressure) - # time.sleep(1) - # self._unilab_send_msg_succ_cmd(True) - # time.sleep(1) - # self._unilab_send_msg_succ_cmd(False) - # #将允许读取标志位置True - # self.allow_data_read = True - # self.success = True - # return self.success - - def func_pack_send_msg_cmd(self, elec_use_num) -> bool: - """UNILAB写参数""" - while (self.request_rec_msg_status) == False: - print("wait for request_rec_msg_status to True") - time.sleep(1) - self.success = False - #self._unilab_send_msg_electrolyte_num(elec_num) - time.sleep(1) - self._unilab_send_msg_electrolyte_use_num(elec_use_num) - time.sleep(1) - self._unilab_send_msg_succ_cmd(True) - time.sleep(1) - while (self.request_rec_msg_status) == True: - print("wait for request_rec_msg_status to False") - time.sleep(1) - self._unilab_send_msg_succ_cmd(False) - #将允许读取标志位置True - self.allow_data_read = True - self.success = True - return self.success - - def func_pack_get_msg_cmd(self, file_path: str="D:\\coin_cell_data") -> bool: - """UNILAB读参数""" - while self.request_send_msg_status == False: - print("waiting for send_read_msg_status to True") - time.sleep(1) - data_open_circuit_voltage = self.data_open_circuit_voltage - data_pole_weight = self.data_pole_weight - data_assembly_time = self.data_assembly_time - data_assembly_pressure = self.data_assembly_pressure - data_electrolyte_volume = self.data_electrolyte_volume - data_coin_num = self.data_coin_num - data_electrolyte_code = self.data_electrolyte_code - data_coin_cell_code = self.data_coin_cell_code - print("data_open_circuit_voltage", data_open_circuit_voltage) - print("data_pole_weight", data_pole_weight) - print("data_assembly_time", data_assembly_time) - print("data_assembly_pressure", data_assembly_pressure) - print("data_electrolyte_volume", data_electrolyte_volume) - print("data_coin_num", data_coin_num) - print("data_electrolyte_code", data_electrolyte_code) - print("data_coin_cell_code", data_coin_cell_code) - #接收完信息后,读取完毕标志位置True - self._unilab_rec_msg_succ_cmd(True) - time.sleep(1) - #等待允许读取标志位置False - while self.request_send_msg_status == True: - print("waiting for send_msg_status to False") - time.sleep(1) - self._unilab_rec_msg_succ_cmd(False) - time.sleep(1) - #将允许读取标志位置True - time_date = datetime.now().strftime("%Y%m%d") - #秒级时间戳用于标记每一行电池数据 - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - #生成输出文件的变量 - self.csv_export_file = os.path.join(file_path, f"date_{time_date}.csv") - #将数据存入csv文件 - if not os.path.exists(self.csv_export_file): - #创建一个表头 - with open(self.csv_export_file, 'w', newline='', encoding='utf-8') as csvfile: - writer = csv.writer(csvfile) - writer.writerow([ - 'Time', 'open_circuit_voltage', 'pole_weight', - 'assembly_time', 'assembly_pressure', 'electrolyte_volume', - 'coin_num', 'electrolyte_code', 'coin_cell_code' - ]) - #立刻写入磁盘 - csvfile.flush() - #开始追加电池信息 - with open(self.csv_export_file, 'a', newline='', encoding='utf-8') as csvfile: - writer = csv.writer(csvfile) - writer.writerow([ - timestamp, data_open_circuit_voltage, data_pole_weight, - data_assembly_time, data_assembly_pressure, data_electrolyte_volume, - data_coin_num, data_electrolyte_code, data_coin_cell_code - ]) - #立刻写入磁盘 - csvfile.flush() - self.success = True - return self.success - - - - def func_pack_send_finished_cmd(self) -> bool: - """UNILAB写参数""" - while (self._unilab_rece_finished_cmd()) == False: - print("wait for rece_finished_cmd to True") - time.sleep(1) - self.success = False - self._unilab_send_finished_cmd(True) - time.sleep(1) - while (self._unilab_rece_finished_cmd()) == True: - print("wait for rece_finished_cmd to False") - time.sleep(1) - self._unilab_send_finished_cmd(False) - #将允许读取标志位置True - self.success = True - return self.success - - - - def func_allpack_cmd(self, elec_num, elec_use_num, file_path: str="D:\\coin_cell_data") -> bool: - summary_csv_file = os.path.join(file_path, "duandian.csv") - # 如果断点文件存在,先读取之前的进度 - if os.path.exists(summary_csv_file): - read_status_flag = True - with open(summary_csv_file, 'r', newline='', encoding='utf-8') as csvfile: - reader = csv.reader(csvfile) - header = next(reader) # 跳过标题行 - data_row = next(reader) # 读取数据行 - if len(data_row) >= 2: - elec_num_r = int(data_row[0]) - elec_use_num_r = int(data_row[1]) - elec_num_N = int(data_row[2]) - elec_use_num_N = int(data_row[3]) - coin_num_N = int(data_row[4]) - if elec_num_r == elec_num and elec_use_num_r == elec_use_num: - print("断点文件与当前任务匹配,继续") - else: - print("断点文件中elec_num、elec_use_num与当前任务不匹配,请检查任务下发参数或修改断点文件") - return False - print(f"从断点文件读取进度: elec_num_N={elec_num_N}, elec_use_num_N={elec_use_num_N}, coin_num_N={coin_num_N}") - - else: - read_status_flag = False - print("未找到断点文件,从头开始") - elec_num_N = 0 - elec_use_num_N = 0 - coin_num_N = 0 - - print(f"剩余电解液瓶数: {elec_num}, 已组装电池数: {elec_use_num}") - - - #如果是第一次运行,则进行初始化、切换自动、启动, 如果是断点重启则跳过。 - if read_status_flag == False: - #初始化 - self.func_pack_device_init() - #切换自动 - self.func_pack_device_auto() - #启动,小车收回 - self.func_pack_device_start() - #发送电解液瓶数量,启动搬运,多搬运没事 - self.func_pack_send_bottle_num(elec_num) - last_i = elec_num_N - last_j = elec_use_num_N - for i in range(last_i, elec_num): - print(f"开始第{last_i+i+1}瓶电解液的组装") - #第一个循环从上次断点继续,后续循环从0开始 - j_start = last_j if i == last_i else 0 - self.func_pack_send_msg_cmd(elec_use_num-j_start) - - for j in range(j_start, elec_use_num): - print(f"开始第{last_i+i+1}瓶电解液的第{j+j_start+1}个电池组装") - #读取电池组装数据并存入csv - self.func_pack_get_msg_cmd(file_path) - time.sleep(1) - - #这里定义物料系统 - # TODO:读完再将电池数加一还是进入循环就将电池数加一需要考虑 - liaopan1 = self.deck.get_resource("liaopan1") - liaopan4 = self.deck.get_resource("liaopan4") - jipian1 = liaopan1.children[coin_num_N].children[0] - jipian4 = liaopan4.children[coin_num_N].children[0] - #print(jipian1) - #从料盘上去物料解绑后放到另一盘上 - jipian1.parent.unassign_child_resource(jipian1) - jipian4.parent.unassign_child_resource(jipian4) - - #print(jipian2.parent) - battery = Battery(name = f"battery_{coin_num_N}") - battery.assign_child_resource(jipian1, location=None) - battery.assign_child_resource(jipian4, location=None) - - zidanjia6 = self.deck.get_resource("zi_dan_jia6") - - zidanjia6.children[0].assign_child_resource(battery, location=None) - - - # 生成断点文件 - # 生成包含elec_num_N、coin_num_N、timestamp的CSV文件 - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - with open(summary_csv_file, 'w', newline='', encoding='utf-8') as csvfile: - writer = csv.writer(csvfile) - writer.writerow(['elec_num','elec_use_num', 'elec_num_N', 'elec_use_num_N', 'coin_num_N', 'timestamp']) - writer.writerow([elec_num, elec_use_num, elec_num_N, elec_use_num_N, coin_num_N, timestamp]) - csvfile.flush() - coin_num_N += 1 - elec_use_num_N += 1 - elec_num_N += 1 - elec_use_num_N = 0 - - #循环正常结束,则删除断点文件 - os.remove(summary_csv_file) - #全部完成后等待依华发送完成信号 - self.func_pack_send_finished_cmd() - - - def func_pack_device_stop(self) -> bool: - """打包指令:设备停止""" - for i in range(3): - time.sleep(2) - print(f"输出{i}") - #print("_sys_hand_cmd", self._sys_hand_cmd()) - #time.sleep(1) - #print("_sys_hand_status", self._sys_hand_status()) - #time.sleep(1) - #print("_sys_init_cmd", self._sys_init_cmd()) - #time.sleep(1) - #print("_sys_init_status", self._sys_init_status()) - #time.sleep(1) - #print("_sys_auto_status", self._sys_auto_status()) - #time.sleep(1) - #print("data_axis_y_pos", self.data_axis_y_pos) - #time.sleep(1) - #self.success = False - #with open('action_device_stop.json', 'r', encoding='utf-8') as f: - # action_json = json.load(f) - #self.client.execute_procedure_from_json(action_json) - #self.success = True - #return self.success - - def fun_wuliao_test(self) -> bool: - #找到data_init中构建的2个物料盘 - #liaopan1 = self.deck.get_resource("liaopan1") - #liaopan4 = self.deck.get_resource("liaopan4") - #for coin_num_N in range(16): - # liaopan1 = self.deck.get_resource("liaopan1") - # liaopan4 = self.deck.get_resource("liaopan4") - # jipian1 = liaopan1.children[coin_num_N].children[0] - # jipian4 = liaopan4.children[coin_num_N].children[0] - # #print(jipian1) - # #从料盘上去物料解绑后放到另一盘上 - # jipian1.parent.unassign_child_resource(jipian1) - # jipian4.parent.unassign_child_resource(jipian4) - # - # #print(jipian2.parent) - # battery = Battery(name = f"battery_{coin_num_N}") - # battery.assign_child_resource(jipian1, location=None) - # battery.assign_child_resource(jipian4, location=None) - # - # zidanjia6 = self.deck.get_resource("zi_dan_jia6") - # zidanjia6.children[0].assign_child_resource(battery, location=None) - # ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ - # "resources": [self.deck] - # }) - # time.sleep(2) - for i in range(20): - print(f"输出{i}") - time.sleep(2) - - - # 数据读取与输出 - def func_read_data_and_output(self, file_path: str="D:\\coin_cell_data"): - # 检查CSV导出是否正在运行,已运行则跳出,防止同时启动两个while循环 - if self.csv_export_running: - return False, "读取已在运行中" - - #若不存在该目录则创建 - if not os.path.exists(file_path): - os.makedirs(file_path) - print(f"创建目录: {file_path}") - - # 只要允许读取标志位为true,就持续运行该函数,直到触发停止条件 - while self.allow_data_read: - - #函数运行标志位,确保只同时启动一个导出函数 - self.csv_export_running = True - - #等待接收结果标志位置True - while self.request_send_msg_status == False: - print("waiting for send_msg_status to True") - time.sleep(1) - #日期时间戳用于按天存放csv文件 - time_date = datetime.now().strftime("%Y%m%d") - #秒级时间戳用于标记每一行电池数据 - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - #生成输出文件的变量 - self.csv_export_file = os.path.join(file_path, f"date_{time_date}.csv") - - #接收信息 - data_open_circuit_voltage = self.data_open_circuit_voltage - data_pole_weight = self.data_pole_weight - data_assembly_time = self.data_assembly_time - data_assembly_pressure = self.data_assembly_pressure - data_electrolyte_volume = self.data_electrolyte_volume - data_coin_num = self.data_coin_num - data_electrolyte_code = self.data_electrolyte_code - data_coin_cell_code = self.data_coin_cell_code - # 电解液瓶位置 - elec_bottle_site = 2 - # 极片夹取位置(应当通过寄存器读光标) - Pos_elec_site = 0 - Al_elec_site = 0 - Gasket_site = 0 - - #接收完信息后,读取完毕标志位置True - self._unilab_rec_msg_succ_cmd()# = True - #等待允许读取标志位置False - while self.request_send_msg_status == True: - print("waiting for send_msg_status to False") - time.sleep(1) - self._unilab_rec_msg_succ_cmd()# = False - - #此处操作物料信息(如果中途报错停止,如何) - #报错怎么办(加个判断标志位,如果发生错误,则根据停止位置扣除物料) - #根据物料光标判断取哪个物料(人工摆盘,电解液瓶,移液枪头都有光标位置,寄存器读即可) - - #物料读取操作写在这里 - #在这里进行物料调取 - #转移物料瓶,elec_bottle_site对应第几瓶电解液(从依华寄存器读取) - # transfer_bottle(deck, elec_bottle_site) - # #找到电解液瓶的对象 - # electrolyte_rack = deck.get_resource("electrolyte_rack") - # pending_positions = electrolyte_rack.get_pending_positions()[elec_bottle_site] - # # TODO: 瓶子取液体操作需要加入 -# -# - # #找到压制工站对应的对象 - # battery_press_slot = deck.get_resource("battery_press_1") - # #创建一个新电池 - # test_battery = Battery( - # name=f"test_battery_{data_coin_num}", - # diameter=20.0, # 与压制槽直径匹配 - # height=3.0, # 电池高度 - # max_volume=100.0, # 100μL容量 - # barcode=data_coin_cell_code, # 电池条码 - # ) - # if battery_press_slot.has_battery(): - # return False, "压制工站已有电池,无法放置新电池" - # #在压制位放置电池 - # battery_press_slot.place_battery(test_battery) - # #从第一个子弹夹中取料 - # clip_magazine_1_hole = self.deck.get_resource("clip_magazine_1").get_item(Pos_elec_site) - # clip_magazine_2_hole = self.deck.get_resource("clip_magazine_2").get_item(Al_elec_site) - # clip_magazine_3_hole = self.deck.get_resource("clip_magazine_3").get_item(Gasket_site) - # - # if clip_magazine_1_hole.get_sheet_count() > 0: # 检查洞位是否有极片 - # electrode_sheet_1 = clip_magazine_1_hole.take_sheet() # 从洞位取出极片 - # test_battery.add_electrode_sheet(electrode_sheet_1) # 添加到电池中 - # print(f"已将极片 {electrode_sheet_1.name} 从子弹夹转移到电池") - # else: - # print("子弹夹洞位0没有极片") -# - # if clip_magazine_2_hole.get_sheet_count() > 0: # 检查洞位是否有极片 - # electrode_sheet_2 = clip_magazine_2_hole.take_sheet() # 从洞位取出极片 - # test_battery.add_electrode_sheet(electrode_sheet_2) # 添加到电池中 - # print(f"已将极片 {electrode_sheet_2.name} 从子弹夹转移到电池") - # else: - # print("子弹夹洞位0没有极片") -# - # if clip_magazine_3_hole.get_sheet_count() > 0: # 检查洞位是否有极片 - # electrode_sheet_3 = clip_magazine_3_hole.take_sheet() # 从洞位取出极片 - # test_battery.add_electrode_sheet(electrode_sheet_3) # 添加到电池中 - # print(f"已将极片 {electrode_sheet_3.name} 从子弹夹转移到电池") - # else: - # print("子弹夹洞位0没有极片") - # - # #把电解液从瓶中取到电池夹子中 - # battery_site = deck.get_resource("battery_press_1") - # clip_magazine_battery = deck.get_resource("clip_magazine_battery") - # if battery_site.has_battery(): - # battery = battery_site.take_battery() #从压制槽取出电池 - # clip_magazine_battery.add_battery(battery) #从压制槽取出电池 -# -# -# -# - # # 保存配置到文件 - # self.deck.save("button_battery_station_layout.json", indent=2) - # print("\n台面配置已保存到: button_battery_station_layout.json") - # - # # 保存状态到文件 - # self.deck.save_state_to_file("button_battery_station_state.json", indent=2) - # print("台面状态已保存到: button_battery_station_state.json") - - - - - - - #将数据写入csv中 - #如当前目录下无同名文件则新建一个csv用于存放数据 - if not os.path.exists(self.csv_export_file): - #创建一个表头 - with open(self.csv_export_file, 'w', newline='', encoding='utf-8') as csvfile: - writer = csv.writer(csvfile) - writer.writerow([ - 'Time', 'open_circuit_voltage', 'pole_weight', - 'assembly_time', 'assembly_pressure', 'electrolyte_volume', - 'coin_num', 'electrolyte_code', 'coin_cell_code' - ]) - #立刻写入磁盘 - csvfile.flush() - #开始追加电池信息 - with open(self.csv_export_file, 'a', newline='', encoding='utf-8') as csvfile: - writer = csv.writer(csvfile) - writer.writerow([ - timestamp, data_open_circuit_voltage, data_pole_weight, - data_assembly_time, data_assembly_pressure, data_electrolyte_volume, - data_coin_num, data_electrolyte_code, data_coin_cell_code - ]) - #立刻写入磁盘 - csvfile.flush() - - # 只要不在自动模式运行中,就将允许标志位置False - if self.sys_auto_status == False or self.sys_start_status == False: - self.allow_data_read = False - self.csv_export_running = False - time.sleep(1) - - def func_stop_read_data(self): - """停止CSV导出""" - if not self.csv_export_running: - return False, "read data未在运行" - - self.csv_export_running = False - self.allow_data_read = False - - if self.csv_export_thread and self.csv_export_thread.is_alive(): - self.csv_export_thread.join(timeout=5) - - def func_get_csv_export_status(self): - """获取CSV导出状态""" - return { - 'allow_read': self.allow_data_read, - 'running': self.csv_export_running, - 'thread_alive': self.csv_export_thread.is_alive() if self.csv_export_thread else False - } - - - ''' - # ===================== 物料管理区 ====================== - @property - def data_material_inventory(self) -> int: - """主物料库存 (数量, INT16)""" - inventory, read_err = self.client.use_node('REG_DATA_MATERIAL_INVENTORY').read(1) - return inventory - - @property - def data_tips_inventory(self) -> int: - """移液枪头库存 (数量, INT16)""" - inventory, read_err = self.client.register_node_list(self.nodes).use_node('REG_DATA_TIPS_INVENTORY').read(1) - return inventory - - ''' - - -if __name__ == "__main__": - from pylabrobot.resources import Resource - Coin_Cell = CoinCellAssemblyWorkstation(Resource("1", 1, 1, 1), debug_mode=True) - #Coin_Cell.func_pack_device_init() - #Coin_Cell.func_pack_device_auto() - #Coin_Cell.func_pack_device_start() - #Coin_Cell.func_pack_send_bottle_num(2) - #Coin_Cell.func_pack_send_msg_cmd(2) - #Coin_Cell.func_pack_get_msg_cmd() - #Coin_Cell.func_pack_get_msg_cmd() - #Coin_Cell.func_pack_send_finished_cmd() -# - #Coin_Cell.func_allpack_cmd(3, 2) - #print(Coin_Cell.data_stack_vision_code) - #print("success") - #创建一个物料台面 - - #deck = create_a_coin_cell_deck() - - ##在台面上找到料盘和极片 - #liaopan1 = deck.get_resource("liaopan1") - #liaopan2 = deck.get_resource("liaopan2") - #jipian1 = liaopan1.children[1].children[0] -# - ##print(jipian1) - ##把物料解绑后放到另一盘上 - #jipian1.parent.unassign_child_resource(jipian1) - #liaopan2.children[1].assign_child_resource(jipian1, location=None) - ##print(jipian2.parent) - from unilabos.resources.graphio import resource_ulab_to_plr, convert_resources_to_type - - with open("./button_battery_decks_unilab.json", "r", encoding="utf-8") as f: - bioyond_resources_unilab = json.load(f) - print(f"成功读取 JSON 文件,包含 {len(bioyond_resources_unilab)} 个资源") - ulab_resources = convert_resources_to_type(bioyond_resources_unilab, List[PLRResource]) - print(f"转换结果类型: {type(ulab_resources)}") - print(ulab_resources) - diff --git a/unilabos/registry/devices/bioyond.yaml b/unilabos/registry/devices/bioyond.yaml index 9c64795..fe6f090 100644 --- a/unilabos/registry/devices/bioyond.yaml +++ b/unilabos/registry/devices/bioyond.yaml @@ -242,8 +242,11 @@ workstation.bioyond_dispensing_station: properties: config: type: string + deck: + type: string required: - config + - deck type: object data: properties: {} diff --git a/unilabos/registry/devices/bioyond_cell.yaml b/unilabos/registry/devices/bioyond_cell.yaml new file mode 100644 index 0000000..c50a1a8 --- /dev/null +++ b/unilabos/registry/devices/bioyond_cell.yaml @@ -0,0 +1,834 @@ +bioyond_cell: + category: + - bioyond_cell + class: + action_value_mappings: + auto-auto_batch_outbound_from_xlsx: + feedback: {} + goal: {} + goal_default: + xlsx_path: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + xlsx_path: + type: string + required: + - xlsx_path + type: object + result: {} + required: + - goal + title: auto_batch_outbound_from_xlsx参数 + type: object + type: UniLabJsonCommand + auto-auto_feeding4to3: + feedback: {} + goal: {} + goal_default: + WH3_x1_y1_z3_1_materialId: '' + WH3_x1_y1_z3_1_materialType: '' + WH3_x1_y1_z3_1_quantity: 0 + WH3_x1_y2_z3_4_materialId: '' + WH3_x1_y2_z3_4_materialType: '' + WH3_x1_y2_z3_4_quantity: 0 + WH3_x1_y3_z3_7_materialId: '' + WH3_x1_y3_z3_7_materialType: '' + WH3_x1_y3_z3_7_quantity: 0 + WH3_x1_y4_z3_10_materialId: '' + WH3_x1_y4_z3_10_materialType: '' + WH3_x1_y4_z3_10_quantity: 0 + WH3_x1_y5_z3_13_materialId: '' + WH3_x1_y5_z3_13_materialType: '' + WH3_x1_y5_z3_13_quantity: 0 + WH3_x2_y1_z3_2_materialId: '' + WH3_x2_y1_z3_2_materialType: '' + WH3_x2_y1_z3_2_quantity: 0 + WH3_x2_y2_z3_5_materialId: '' + WH3_x2_y2_z3_5_materialType: '' + WH3_x2_y2_z3_5_quantity: 0 + WH3_x2_y3_z3_8_materialId: '' + WH3_x2_y3_z3_8_materialType: '' + WH3_x2_y3_z3_8_quantity: 0 + WH3_x2_y4_z3_11_materialId: '' + WH3_x2_y4_z3_11_materialType: '' + WH3_x2_y4_z3_11_quantity: 0 + WH3_x2_y5_z3_14_materialId: '' + WH3_x2_y5_z3_14_materialType: '' + WH3_x2_y5_z3_14_quantity: 0 + WH3_x3_y1_z3_3_materialId: '' + WH3_x3_y1_z3_3_materialType: '' + WH3_x3_y1_z3_3_quantity: 0 + WH3_x3_y2_z3_6_materialId: '' + WH3_x3_y2_z3_6_materialType: '' + WH3_x3_y2_z3_6_quantity: 0 + WH3_x3_y3_z3_9_materialId: '' + WH3_x3_y3_z3_9_materialType: '' + WH3_x3_y3_z3_9_quantity: 0 + WH3_x3_y4_z3_12_materialId: '' + WH3_x3_y4_z3_12_materialType: '' + WH3_x3_y4_z3_12_quantity: 0 + WH3_x3_y5_z3_15_materialId: '' + WH3_x3_y5_z3_15_materialType: '' + WH3_x3_y5_z3_15_quantity: 0 + WH4_x1_y1_z1_1_materialName: '' + WH4_x1_y1_z1_1_quantity: 0.0 + WH4_x1_y1_z2_1_materialName: '' + WH4_x1_y1_z2_1_materialType: '' + WH4_x1_y1_z2_1_quantity: 0.0 + WH4_x1_y1_z2_1_targetWH: '' + WH4_x1_y2_z1_6_materialName: '' + WH4_x1_y2_z1_6_quantity: 0.0 + WH4_x1_y2_z2_4_materialName: '' + WH4_x1_y2_z2_4_materialType: '' + WH4_x1_y2_z2_4_quantity: 0.0 + WH4_x1_y2_z2_4_targetWH: '' + WH4_x1_y3_z1_11_materialName: '' + WH4_x1_y3_z1_11_quantity: 0.0 + WH4_x1_y3_z2_7_materialName: '' + WH4_x1_y3_z2_7_materialType: '' + WH4_x1_y3_z2_7_quantity: 0.0 + WH4_x1_y3_z2_7_targetWH: '' + WH4_x2_y1_z1_2_materialName: '' + WH4_x2_y1_z1_2_quantity: 0.0 + WH4_x2_y1_z2_2_materialName: '' + WH4_x2_y1_z2_2_materialType: '' + WH4_x2_y1_z2_2_quantity: 0.0 + WH4_x2_y1_z2_2_targetWH: '' + WH4_x2_y2_z1_7_materialName: '' + WH4_x2_y2_z1_7_quantity: 0.0 + WH4_x2_y2_z2_5_materialName: '' + WH4_x2_y2_z2_5_materialType: '' + WH4_x2_y2_z2_5_quantity: 0.0 + WH4_x2_y2_z2_5_targetWH: '' + WH4_x2_y3_z1_12_materialName: '' + WH4_x2_y3_z1_12_quantity: 0.0 + WH4_x2_y3_z2_8_materialName: '' + WH4_x2_y3_z2_8_materialType: '' + WH4_x2_y3_z2_8_quantity: 0.0 + WH4_x2_y3_z2_8_targetWH: '' + WH4_x3_y1_z1_3_materialName: '' + WH4_x3_y1_z1_3_quantity: 0.0 + WH4_x3_y1_z2_3_materialName: '' + WH4_x3_y1_z2_3_materialType: '' + WH4_x3_y1_z2_3_quantity: 0.0 + WH4_x3_y1_z2_3_targetWH: '' + WH4_x3_y2_z1_8_materialName: '' + WH4_x3_y2_z1_8_quantity: 0.0 + WH4_x3_y2_z2_6_materialName: '' + WH4_x3_y2_z2_6_materialType: '' + WH4_x3_y2_z2_6_quantity: 0.0 + WH4_x3_y2_z2_6_targetWH: '' + WH4_x3_y3_z2_9_materialName: '' + WH4_x3_y3_z2_9_materialType: '' + WH4_x3_y3_z2_9_quantity: 0.0 + WH4_x3_y3_z2_9_targetWH: '' + WH4_x4_y1_z1_4_materialName: '' + WH4_x4_y1_z1_4_quantity: 0.0 + WH4_x4_y2_z1_9_materialName: '' + WH4_x4_y2_z1_9_quantity: 0.0 + WH4_x5_y1_z1_5_materialName: '' + WH4_x5_y1_z1_5_quantity: 0.0 + WH4_x5_y2_z1_10_materialName: '' + WH4_x5_y2_z1_10_quantity: 0.0 + xlsx_path: unilabos/devices/workstation/bioyond_cell/样品导入模板.xlsx + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + WH3_x1_y1_z3_1_materialId: + default: '' + type: string + WH3_x1_y1_z3_1_materialType: + default: '' + type: string + WH3_x1_y1_z3_1_quantity: + default: 0 + type: number + WH3_x1_y2_z3_4_materialId: + default: '' + type: string + WH3_x1_y2_z3_4_materialType: + default: '' + type: string + WH3_x1_y2_z3_4_quantity: + default: 0 + type: number + WH3_x1_y3_z3_7_materialId: + default: '' + type: string + WH3_x1_y3_z3_7_materialType: + default: '' + type: string + WH3_x1_y3_z3_7_quantity: + default: 0 + type: number + WH3_x1_y4_z3_10_materialId: + default: '' + type: string + WH3_x1_y4_z3_10_materialType: + default: '' + type: string + WH3_x1_y4_z3_10_quantity: + default: 0 + type: number + WH3_x1_y5_z3_13_materialId: + default: '' + type: string + WH3_x1_y5_z3_13_materialType: + default: '' + type: string + WH3_x1_y5_z3_13_quantity: + default: 0 + type: number + WH3_x2_y1_z3_2_materialId: + default: '' + type: string + WH3_x2_y1_z3_2_materialType: + default: '' + type: string + WH3_x2_y1_z3_2_quantity: + default: 0 + type: number + WH3_x2_y2_z3_5_materialId: + default: '' + type: string + WH3_x2_y2_z3_5_materialType: + default: '' + type: string + WH3_x2_y2_z3_5_quantity: + default: 0 + type: number + WH3_x2_y3_z3_8_materialId: + default: '' + type: string + WH3_x2_y3_z3_8_materialType: + default: '' + type: string + WH3_x2_y3_z3_8_quantity: + default: 0 + type: number + WH3_x2_y4_z3_11_materialId: + default: '' + type: string + WH3_x2_y4_z3_11_materialType: + default: '' + type: string + WH3_x2_y4_z3_11_quantity: + default: 0 + type: number + WH3_x2_y5_z3_14_materialId: + default: '' + type: string + WH3_x2_y5_z3_14_materialType: + default: '' + type: string + WH3_x2_y5_z3_14_quantity: + default: 0 + type: number + WH3_x3_y1_z3_3_materialId: + default: '' + type: string + WH3_x3_y1_z3_3_materialType: + default: '' + type: string + WH3_x3_y1_z3_3_quantity: + default: 0 + type: number + WH3_x3_y2_z3_6_materialId: + default: '' + type: string + WH3_x3_y2_z3_6_materialType: + default: '' + type: string + WH3_x3_y2_z3_6_quantity: + default: 0 + type: number + WH3_x3_y3_z3_9_materialId: + default: '' + type: string + WH3_x3_y3_z3_9_materialType: + default: '' + type: string + WH3_x3_y3_z3_9_quantity: + default: 0 + type: number + WH3_x3_y4_z3_12_materialId: + default: '' + type: string + WH3_x3_y4_z3_12_materialType: + default: '' + type: string + WH3_x3_y4_z3_12_quantity: + default: 0 + type: number + WH3_x3_y5_z3_15_materialId: + default: '' + type: string + WH3_x3_y5_z3_15_materialType: + default: '' + type: string + WH3_x3_y5_z3_15_quantity: + default: 0 + type: number + WH4_x1_y1_z1_1_materialName: + default: '' + type: string + WH4_x1_y1_z1_1_quantity: + default: 0.0 + type: number + WH4_x1_y1_z2_1_materialName: + default: '' + type: string + WH4_x1_y1_z2_1_materialType: + default: '' + type: string + WH4_x1_y1_z2_1_quantity: + default: 0.0 + type: number + WH4_x1_y1_z2_1_targetWH: + default: '' + type: string + WH4_x1_y2_z1_6_materialName: + default: '' + type: string + WH4_x1_y2_z1_6_quantity: + default: 0.0 + type: number + WH4_x1_y2_z2_4_materialName: + default: '' + type: string + WH4_x1_y2_z2_4_materialType: + default: '' + type: string + WH4_x1_y2_z2_4_quantity: + default: 0.0 + type: number + WH4_x1_y2_z2_4_targetWH: + default: '' + type: string + WH4_x1_y3_z1_11_materialName: + default: '' + type: string + WH4_x1_y3_z1_11_quantity: + default: 0.0 + type: number + WH4_x1_y3_z2_7_materialName: + default: '' + type: string + WH4_x1_y3_z2_7_materialType: + default: '' + type: string + WH4_x1_y3_z2_7_quantity: + default: 0.0 + type: number + WH4_x1_y3_z2_7_targetWH: + default: '' + type: string + WH4_x2_y1_z1_2_materialName: + default: '' + type: string + WH4_x2_y1_z1_2_quantity: + default: 0.0 + type: number + WH4_x2_y1_z2_2_materialName: + default: '' + type: string + WH4_x2_y1_z2_2_materialType: + default: '' + type: string + WH4_x2_y1_z2_2_quantity: + default: 0.0 + type: number + WH4_x2_y1_z2_2_targetWH: + default: '' + type: string + WH4_x2_y2_z1_7_materialName: + default: '' + type: string + WH4_x2_y2_z1_7_quantity: + default: 0.0 + type: number + WH4_x2_y2_z2_5_materialName: + default: '' + type: string + WH4_x2_y2_z2_5_materialType: + default: '' + type: string + WH4_x2_y2_z2_5_quantity: + default: 0.0 + type: number + WH4_x2_y2_z2_5_targetWH: + default: '' + type: string + WH4_x2_y3_z1_12_materialName: + default: '' + type: string + WH4_x2_y3_z1_12_quantity: + default: 0.0 + type: number + WH4_x2_y3_z2_8_materialName: + default: '' + type: string + WH4_x2_y3_z2_8_materialType: + default: '' + type: string + WH4_x2_y3_z2_8_quantity: + default: 0.0 + type: number + WH4_x2_y3_z2_8_targetWH: + default: '' + type: string + WH4_x3_y1_z1_3_materialName: + default: '' + type: string + WH4_x3_y1_z1_3_quantity: + default: 0.0 + type: number + WH4_x3_y1_z2_3_materialName: + default: '' + type: string + WH4_x3_y1_z2_3_materialType: + default: '' + type: string + WH4_x3_y1_z2_3_quantity: + default: 0.0 + type: number + WH4_x3_y1_z2_3_targetWH: + default: '' + type: string + WH4_x3_y2_z1_8_materialName: + default: '' + type: string + WH4_x3_y2_z1_8_quantity: + default: 0.0 + type: number + WH4_x3_y2_z2_6_materialName: + default: '' + type: string + WH4_x3_y2_z2_6_materialType: + default: '' + type: string + WH4_x3_y2_z2_6_quantity: + default: 0.0 + type: number + WH4_x3_y2_z2_6_targetWH: + default: '' + type: string + WH4_x3_y3_z2_9_materialName: + default: '' + type: string + WH4_x3_y3_z2_9_materialType: + default: '' + type: string + WH4_x3_y3_z2_9_quantity: + default: 0.0 + type: number + WH4_x3_y3_z2_9_targetWH: + default: '' + type: string + WH4_x4_y1_z1_4_materialName: + default: '' + type: string + WH4_x4_y1_z1_4_quantity: + default: 0.0 + type: number + WH4_x4_y2_z1_9_materialName: + default: '' + type: string + WH4_x4_y2_z1_9_quantity: + default: 0.0 + type: number + WH4_x5_y1_z1_5_materialName: + default: '' + type: string + WH4_x5_y1_z1_5_quantity: + default: 0.0 + type: number + WH4_x5_y2_z1_10_materialName: + default: '' + type: string + WH4_x5_y2_z1_10_quantity: + default: 0.0 + type: number + xlsx_path: + default: unilabos/devices/workstation/bioyond_cell/样品导入模板.xlsx + type: string + required: [] + type: object + result: {} + required: + - goal + title: auto_feeding4to3参数 + type: object + type: UniLabJsonCommand + auto-auto_feeding4to3_from_xlsx: + feedback: {} + goal: {} + goal_default: + xlsx_path: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + xlsx_path: + type: string + required: + - xlsx_path + type: object + result: {} + required: + - goal + title: auto_feeding4to3_from_xlsx参数 + type: object + type: UniLabJsonCommand + auto-create_orders: + feedback: {} + goal: {} + goal_default: + xlsx_path: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + xlsx_path: + type: string + required: + - xlsx_path + type: object + result: {} + required: + - goal + title: create_orders参数 + type: object + type: UniLabJsonCommand + auto-order_list_v2: + feedback: {} + goal: {} + goal_default: + beginTime: '' + endTime: '' + filter: '' + pageCount: 1 + skipCount: 0 + sorting: '' + status: '' + timeType: '' + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + beginTime: + default: '' + type: string + endTime: + default: '' + type: string + filter: + default: '' + type: string + pageCount: + default: 1 + type: integer + skipCount: + default: 0 + type: integer + sorting: + default: '' + type: string + status: + default: '' + type: string + timeType: + default: '' + type: string + required: [] + type: object + result: {} + required: + - goal + title: order_list_v2参数 + type: object + type: UniLabJsonCommand + auto-report_material_change: + feedback: {} + goal: {} + goal_default: + material_obj: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + material_obj: + type: object + required: + - material_obj + type: object + result: {} + required: + - goal + title: report_material_change参数 + type: object + type: UniLabJsonCommand + auto-scheduler_continue: + feedback: {} + goal: {} + goal_default: {} + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: scheduler_continue参数 + type: object + type: UniLabJsonCommand + auto-scheduler_start: + feedback: {} + goal: {} + goal_default: {} + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: scheduler_start参数 + type: object + type: UniLabJsonCommand + auto-scheduler_stop: + feedback: {} + goal: {} + goal_default: {} + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: scheduler_stop参数 + type: object + type: UniLabJsonCommand + auto-storage_batch_inbound: + feedback: {} + goal: {} + goal_default: + items: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + items: + items: + type: object + type: array + required: + - items + type: object + result: {} + required: + - goal + title: storage_batch_inbound参数 + type: object + type: UniLabJsonCommand + auto-storage_inbound: + feedback: {} + goal: {} + goal_default: + location_id: null + material_id: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + location_id: + type: string + material_id: + type: string + required: + - material_id + - location_id + type: object + result: {} + required: + - goal + title: storage_inbound参数 + type: object + type: UniLabJsonCommand + auto-transfer_1_to_2: + feedback: {} + goal: {} + goal_default: {} + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: transfer_1_to_2参数 + type: object + type: UniLabJsonCommand + auto-transfer_3_to_2_to_1: + feedback: {} + goal: {} + goal_default: + source_wh_id: 3a19debc-84b4-0359-e2d4-b3beea49348b + source_x: 1 + source_y: 1 + source_z: 1 + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + source_wh_id: + default: 3a19debc-84b4-0359-e2d4-b3beea49348b + type: string + source_x: + default: 1 + type: integer + source_y: + default: 1 + type: integer + source_z: + default: 1 + type: integer + required: [] + type: object + result: {} + required: + - goal + title: transfer_3_to_2_to_1参数 + type: object + type: UniLabJsonCommand + auto-wait_for_transfer_task: + feedback: {} + goal: {} + goal_default: + filter_text: null + interval: 5 + timeout: 3000 + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + filter_text: + type: string + interval: + default: 5 + type: integer + timeout: + default: 3000 + type: integer + required: [] + type: object + result: {} + required: + - goal + title: wait_for_transfer_task参数 + type: object + type: UniLabJsonCommand + module: unilabos.devices.workstation.bioyond_studio.bioyond_cell.bioyond_cell_workstation:BioyondCellWorkstation + status_types: {} + type: python + config_info: [] + description: '' + handles: [] + icon: '' + init_param_schema: + config: + properties: + bioyond_config: + type: string + station_resource: + type: string + required: [] + type: object + data: + properties: {} + required: [] + type: object + registry_type: device + version: 1.0.0 diff --git a/unilabos/registry/devices/characterization_chromatic.yaml b/unilabos/registry/devices/characterization_chromatic.yaml index f3059b5..37446bc 100644 --- a/unilabos/registry/devices/characterization_chromatic.yaml +++ b/unilabos/registry/devices/characterization_chromatic.yaml @@ -1,231 +1,3 @@ -hplc.agilent: - category: - - characterization_chromatic - class: - action_value_mappings: - auto-check_status: - feedback: {} - goal: {} - goal_default: {} - handles: {} - placeholder_keys: {} - result: {} - schema: - description: 检查安捷伦HPLC设备状态的函数。用于监控设备的运行状态、连接状态、错误信息等关键指标。该函数定期查询设备状态,确保系统稳定运行,及时发现和报告设备异常。适用于自动化流程中的设备监控、故障诊断、系统维护等场景。 - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: check_status参数 - type: object - type: UniLabJsonCommand - auto-extract_data_from_txt: - feedback: {} - goal: {} - goal_default: - file_path: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: 从文本文件中提取分析数据的函数。用于解析安捷伦HPLC生成的结果文件,提取峰面积、保留时间、浓度等关键分析数据。支持多种文件格式的自动识别和数据结构化处理,为后续数据分析和报告生成提供标准化的数据格式。适用于批量数据处理、结果验证、质量控制等分析工作流程。 - properties: - feedback: {} - goal: - properties: - file_path: - type: string - required: - - file_path - type: object - result: {} - required: - - goal - title: extract_data_from_txt参数 - type: object - type: UniLabJsonCommand - auto-start_sequence: - feedback: {} - goal: {} - goal_default: - params: null - resource: null - wf_name: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: 启动安捷伦HPLC分析序列的函数。用于执行预定义的分析方法序列,包括样品进样、色谱分离、检测等完整的分析流程。支持参数配置、资源分配、工作流程管理等功能,实现全自动的样品分析。适用于批量样品处理、标准化分析、质量检测等需要连续自动分析的应用场景。 - properties: - feedback: {} - goal: - properties: - params: - type: string - resource: - type: object - wf_name: - type: string - required: - - wf_name - type: object - result: {} - required: - - goal - title: start_sequence参数 - type: object - type: UniLabJsonCommand - auto-try_close_sub_device: - feedback: {} - goal: {} - goal_default: - device_name: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: 尝试关闭HPLC子设备的函数。用于安全地关闭泵、检测器、进样器等各个子模块,确保设备正常断开连接并保护硬件安全。该函数提供错误处理和状态确认机制,避免强制关闭可能造成的设备损坏。适用于设备维护、系统重启、紧急停机等需要安全关闭设备的场景。 - properties: - feedback: {} - goal: - properties: - device_name: - type: string - required: [] - type: object - result: {} - required: - - goal - title: try_close_sub_device参数 - type: object - type: UniLabJsonCommand - auto-try_open_sub_device: - feedback: {} - goal: {} - goal_default: - device_name: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: 尝试打开HPLC子设备的函数。用于初始化和连接泵、检测器、进样器等各个子模块,建立设备通信并进行自检。该函数提供连接验证和错误恢复机制,确保子设备正常启动并准备就绪。适用于设备初始化、系统启动、设备重连等需要建立设备连接的场景。 - properties: - feedback: {} - goal: - properties: - device_name: - type: string - required: [] - type: object - result: {} - required: - - goal - title: try_open_sub_device参数 - type: object - type: UniLabJsonCommand - execute_command_from_outer: - feedback: {} - goal: - command: command - goal_default: - command: '' - handles: {} - result: - success: success - schema: - description: '' - properties: - feedback: - properties: - status: - type: string - required: - - status - title: SendCmd_Feedback - type: object - goal: - properties: - command: - type: string - required: - - command - title: SendCmd_Goal - type: object - result: - properties: - return_info: - type: string - success: - type: boolean - required: - - return_info - - success - title: SendCmd_Result - type: object - required: - - goal - title: SendCmd - type: object - type: SendCmd - module: unilabos.devices.hplc.AgilentHPLC:HPLCDriver - status_types: - could_run: bool - data_file: String - device_status: str - driver_init_ok: bool - finish_status: str - is_running: bool - status_text: str - success: bool - type: python - config_info: [] - description: 安捷伦高效液相色谱(HPLC)分析设备,用于复杂化合物的分离、检测和定量分析。该设备通过UI自动化技术控制安捷伦ChemStation软件,实现全自动的样品分析流程。具备序列启动、设备状态监控、数据文件提取、结果处理等功能。支持多样品批量处理和实时状态反馈,适用于药物分析、环境检测、食品安全、化学研究等需要高精度色谱分析的实验室应用。 - handles: [] - icon: '' - init_param_schema: - config: - properties: - driver_debug: - default: false - type: string - required: [] - type: object - data: - properties: - could_run: - type: boolean - data_file: - items: - type: string - type: array - device_status: - type: string - driver_init_ok: - type: boolean - finish_status: - type: string - is_running: - type: boolean - status_text: - type: string - success: - type: boolean - required: - - status_text - - device_status - - could_run - - driver_init_ok - - is_running - - success - - finish_status - - data_file - type: object - version: 1.0.0 hplc.agilent-zhida: category: - characterization_chromatic diff --git a/unilabos/registry/devices/characterization_optic.yaml b/unilabos/registry/devices/characterization_optic.yaml index 80dcf93..0967ef4 100644 --- a/unilabos/registry/devices/characterization_optic.yaml +++ b/unilabos/registry/devices/characterization_optic.yaml @@ -1,194 +1 @@ -raman.home_made: - category: - - characterization_optic - class: - action_value_mappings: - auto-ccd_time: - feedback: {} - goal: {} - goal_default: - int_time: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: 设置CCD检测器积分时间的函数。用于配置拉曼光谱仪的信号采集时间,控制光谱数据的质量和信噪比。较长的积分时间可获得更高的信号强度和更好的光谱质量,但会增加测量时间。该函数允许根据样品特性和测量要求动态调整检测参数,优化测量效果。 - properties: - feedback: {} - goal: - properties: - int_time: - type: string - required: - - int_time - type: object - result: {} - required: - - goal - title: ccd_time参数 - type: object - type: UniLabJsonCommand - auto-laser_on_power: - feedback: {} - goal: {} - goal_default: - output_voltage_laser: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: 设置激光器输出功率的函数。用于控制拉曼光谱仪激光器的功率输出,调节激光强度以适应不同样品的测量需求。适当的激光功率能够获得良好的拉曼信号同时避免样品损伤。该函数支持精确的功率控制,确保测量结果的稳定性和重现性。 - properties: - feedback: {} - goal: - properties: - output_voltage_laser: - type: string - required: - - output_voltage_laser - type: object - result: {} - required: - - goal - title: laser_on_power参数 - type: object - type: UniLabJsonCommand - auto-raman_without_background: - feedback: {} - goal: {} - goal_default: - int_time: null - laser_power: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: 执行无背景扣除的拉曼光谱测量函数。用于直接采集样品的拉曼光谱信号,不进行背景校正处理。该函数配置积分时间和激光功率参数,获取原始光谱数据用于后续的数据处理分析。适用于对光谱数据质量要求较高或需要自定义背景处理流程的测量场景。 - properties: - feedback: {} - goal: - properties: - int_time: - type: string - laser_power: - type: string - required: - - int_time - - laser_power - type: object - result: {} - required: - - goal - title: raman_without_background参数 - type: object - type: UniLabJsonCommand - auto-raman_without_background_average: - feedback: {} - goal: {} - goal_default: - average: null - int_time: null - laser_power: null - sample_name: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: 执行多次平均的无背景拉曼光谱测量函数。通过多次测量取平均值来提高光谱数据的信噪比和测量精度,减少随机噪声影响。该函数支持自定义平均次数、积分时间、激光功率等参数,并可为样品指定名称便于数据管理。适用于对测量精度要求较高的定量分析和研究应用。 - properties: - feedback: {} - goal: - properties: - average: - type: string - int_time: - type: string - laser_power: - type: string - sample_name: - type: string - required: - - sample_name - - int_time - - laser_power - - average - type: object - result: {} - required: - - goal - title: raman_without_background_average参数 - type: object - type: UniLabJsonCommand - raman_cmd: - feedback: {} - goal: - command: command - goal_default: - command: '' - handles: {} - result: - success: success - schema: - description: '' - properties: - feedback: - properties: - status: - type: string - required: - - status - title: SendCmd_Feedback - type: object - goal: - properties: - command: - type: string - required: - - command - title: SendCmd_Goal - type: object - result: - properties: - return_info: - type: string - success: - type: boolean - required: - - return_info - - success - title: SendCmd_Result - type: object - required: - - goal - title: SendCmd - type: object - type: SendCmd - module: unilabos.devices.raman_uv.home_made_raman:RamanObj - status_types: {} - type: python - config_info: [] - description: 拉曼光谱分析设备,用于物质的分子结构和化学成分表征。该设备集成激光器和CCD检测器,通过串口通信控制激光功率和光谱采集。具备背景扣除、多次平均、自动数据处理等功能,支持高精度的拉曼光谱测量。适用于材料表征、化学分析、质量控制、研究开发等需要分子指纹识别和结构分析的实验应用。 - handles: [] - icon: '' - init_param_schema: - config: - properties: - baudrate_ccd: - default: 921600 - type: string - baudrate_laser: - default: 9600 - type: string - port_ccd: - type: string - port_laser: - type: string - required: - - port_laser - - port_ccd - type: object - data: - properties: {} - required: [] - type: object - version: 1.0.0 +{} diff --git a/unilabos/registry/devices/reaction_station_bioyond.yaml b/unilabos/registry/devices/reaction_station_bioyond.yaml index 0a4090e..875de07 100644 --- a/unilabos/registry/devices/reaction_station_bioyond.yaml +++ b/unilabos/registry/devices/reaction_station_bioyond.yaml @@ -75,31 +75,6 @@ reaction_station.bioyond: title: load_bioyond_data_from_file参数 type: object type: UniLabJsonCommand - auto-merge_workflow_with_parameters: - feedback: {} - goal: {} - goal_default: - json_str: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - json_str: - type: string - required: - - json_str - type: object - result: {} - required: - - goal - title: merge_workflow_with_parameters参数 - type: object - type: UniLabJsonCommand auto-post_init: feedback: {} goal: {} @@ -729,6 +704,7 @@ reaction_station.bioyond: status_types: all_workflows: dict bioyond_status: dict + station_info: dict workstation_status: dict type: python config_info: [] @@ -742,8 +718,6 @@ reaction_station.bioyond: type: string deck: type: string - station_config: - type: string required: [] type: object data: @@ -752,11 +726,14 @@ reaction_station.bioyond: type: object bioyond_status: type: object + station_info: + type: object workstation_status: type: object required: - bioyond_status - all_workflows + - station_info - workstation_status type: object version: 1.0.0 diff --git a/unilabos/registry/devices/robot_linear_motion.yaml b/unilabos/registry/devices/robot_linear_motion.yaml index 0f8506e..7d9de36 100644 --- a/unilabos/registry/devices/robot_linear_motion.yaml +++ b/unilabos/registry/devices/robot_linear_motion.yaml @@ -834,174 +834,3 @@ linear_motion.toyo_xyz.sim: mesh: toyo_xyz type: device version: 1.0.0 -motor.iCL42: - category: - - robot_linear_motion - class: - action_value_mappings: - auto-execute_run_motor: - feedback: {} - goal: {} - goal_default: - mode: null - position: null - velocity: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: 步进电机执行运动函数。直接执行电机运动命令,包括位置设定、速度控制和路径规划。该函数处理底层的电机控制协议,消除警告信息,设置运动参数并启动电机运行。适用于需要直接控制电机运动的应用场景。 - properties: - feedback: {} - goal: - properties: - mode: - type: string - position: - type: number - velocity: - type: integer - required: - - mode - - position - - velocity - type: object - result: {} - required: - - goal - title: execute_run_motor参数 - type: object - type: UniLabJsonCommand - auto-init_device: - feedback: {} - goal: {} - goal_default: {} - handles: {} - placeholder_keys: {} - result: {} - schema: - description: iCL42电机设备初始化函数。建立与iCL42步进电机驱动器的串口通信连接,配置通信参数包括波特率、数据位、校验位等。该函数是电机使用前的必要步骤,确保驱动器处于可控状态并准备接收运动指令。 - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: init_device参数 - type: object - type: UniLabJsonCommand - auto-run_motor: - feedback: {} - goal: {} - goal_default: - mode: null - position: null - velocity: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: 步进电机运动控制函数。根据指定的运动模式、目标位置和速度参数控制电机运动。支持多种运动模式和精确的位置控制,自动处理运动轨迹规划和执行。该函数提供异步执行和状态反馈,确保运动的准确性和可靠性。 - properties: - feedback: {} - goal: - properties: - mode: - type: string - position: - type: number - velocity: - type: integer - required: - - mode - - position - - velocity - type: object - result: {} - required: - - goal - title: run_motor参数 - type: object - type: UniLabJsonCommand - execute_command_from_outer: - feedback: {} - goal: - command: command - goal_default: - command: '' - handles: {} - result: - success: success - schema: - description: '' - properties: - feedback: - properties: - status: - type: string - required: - - status - title: SendCmd_Feedback - type: object - goal: - properties: - command: - type: string - required: - - command - title: SendCmd_Goal - type: object - result: - properties: - return_info: - type: string - success: - type: boolean - required: - - return_info - - success - title: SendCmd_Result - type: object - required: - - goal - title: SendCmd - type: object - type: SendCmd - module: unilabos.devices.motor.iCL42:iCL42Driver - status_types: - is_executing_run: bool - motor_position: int - success: bool - type: python - config_info: [] - description: iCL42步进电机驱动器,用于实验室设备的精密线性运动控制。该设备通过串口通信控制iCL42型步进电机驱动器,支持多种运动模式和精确的位置、速度控制。具备位置反馈、运行状态监控和故障检测功能。适用于自动进样器、样品传送、精密定位平台等需要准确线性运动控制的实验室自动化设备。 - handles: [] - icon: '' - init_param_schema: - config: - properties: - device_address: - default: 1 - type: integer - device_com: - default: COM9 - type: string - required: [] - type: object - data: - properties: - is_executing_run: - type: boolean - motor_position: - type: integer - success: - type: boolean - required: - - motor_position - - is_executing_run - - success - type: object - version: 1.0.0 diff --git a/unilabos/registry/devices/work_station.yaml b/unilabos/registry/devices/work_station.yaml index 7efdfd2..380c3f6 100644 --- a/unilabos/registry/devices/work_station.yaml +++ b/unilabos/registry/devices/work_station.yaml @@ -6120,31 +6120,6 @@ workstation.bioyond: title: load_bioyond_data_from_file参数 type: object type: UniLabJsonCommand - auto-merge_workflow_with_parameters: - feedback: {} - goal: {} - goal_default: - json_str: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - json_str: - type: string - required: - - json_str - type: object - result: {} - required: - - goal - title: merge_workflow_with_parameters参数 - type: object - type: UniLabJsonCommand auto-post_init: feedback: {} goal: {} @@ -6460,6 +6435,7 @@ workstation.bioyond: status_types: all_workflows: dict bioyond_status: dict + station_info: dict workstation_status: dict type: python config_info: [] @@ -6473,8 +6449,6 @@ workstation.bioyond: type: string deck: type: string - station_config: - type: string required: [] type: object data: @@ -6483,11 +6457,14 @@ workstation.bioyond: type: object bioyond_status: type: object + station_info: + type: object workstation_status: type: object required: - bioyond_status - all_workflows + - station_info - workstation_status type: object version: 1.0.0 diff --git a/unilabos/registry/resources/bioyond/deck.yaml b/unilabos/registry/resources/bioyond/deck.yaml index d5a49b8..dd424ea 100644 --- a/unilabos/registry/resources/bioyond/deck.yaml +++ b/unilabos/registry/resources/bioyond/deck.yaml @@ -22,3 +22,15 @@ BIOYOND_PolymerReactionStation_Deck: init_param_schema: {} registry_type: resource version: 1.0.0 +YB_Deck14: + category: + - deck + class: + module: unilabos.resources.bioyond.decks:YB_Deck + type: pylabrobot + description: BIOYOND PolymerReactionStation Deck + handles: [] + icon: 配液站.webp + init_param_schema: {} + registry_type: resource + version: 1.0.0 diff --git a/unilabos/resources/bioyond/decks.py b/unilabos/resources/bioyond/decks.py index e8c021f..fa242c3 100644 --- a/unilabos/resources/bioyond/decks.py +++ b/unilabos/resources/bioyond/decks.py @@ -1,6 +1,7 @@ +from os import name from pylabrobot.resources import Deck, Coordinate, Rotation -from unilabos.resources.bioyond.warehouses import bioyond_warehouse_1x4x4, bioyond_warehouse_1x4x2, bioyond_warehouse_liquid_and_lid_handling +from unilabos.resources.bioyond.warehouses import bioyond_warehouse_1x4x4, bioyond_warehouse_1x4x2, bioyond_warehouse_liquid_and_lid_handling, bioyond_warehouse_1x2x2, bioyond_warehouse_1x3x3, bioyond_warehouse_10x1x1, bioyond_warehouse_3x3x1, bioyond_warehouse_3x3x1_2, bioyond_warehouse_5x1x1 class BIOYOND_PolymerReactionStation_Deck(Deck): @@ -66,3 +67,60 @@ class BIOYOND_PolymerPreparationStation_Deck(Deck): for warehouse_name, warehouse in self.warehouses.items(): self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name]) + +class BIOYOND_YB_Deck(Deck): + def __init__( + self, + name: str = "YB_Deck", + size_x: float = 4150, + size_y: float = 1400.0, + size_z: float = 2670.0, + category: str = "deck", + setup: bool = False + ) -> None: + super().__init__(name=name, size_x=4150.0, size_y=1400.0, size_z=2670.0) + if setup: + self.setup() + + def setup(self) -> None: + # 添加仓库 + self.warehouses = { + "321窗口": bioyond_warehouse_1x2x2("321窗口"), + "43窗口": bioyond_warehouse_1x2x2("43窗口"), + "手动传递窗左": bioyond_warehouse_1x3x3("手动传递窗左"), + "手动传递窗右": bioyond_warehouse_1x3x3("手动传递窗右"), + "加样头堆栈左": bioyond_warehouse_10x1x1("加样头堆栈左"), + "加样头堆栈右": bioyond_warehouse_10x1x1("加样头堆栈右"), + + "15ml配液堆栈左": bioyond_warehouse_3x3x1("15ml配液堆栈左"), + "母液加样右": bioyond_warehouse_3x3x1_2("母液加样右"), + "大瓶母液堆栈左": bioyond_warehouse_5x1x1("大瓶母液堆栈左"), + "大瓶母液堆栈右": bioyond_warehouse_5x1x1("大瓶母液堆栈右"), + } + # warehouse 的位置 + self.warehouse_locations = { + "321窗口": Coordinate(-150.0, 158.0, 0.0), + "43窗口": Coordinate(4160.0, 158.0, 0.0), + "手动传递窗左": Coordinate(-150.0, 877.0, 0.0), + "手动传递窗右": Coordinate(4160.0, 877.0, 0.0), + "加样头堆栈左": Coordinate(385.0, 1300.0, 0.0), + "加样头堆栈右": Coordinate(2187.0, 1300.0, 0.0), + + "15ml配液堆栈左": Coordinate(749.0, 355.0, 0.0), + "母液加样右": Coordinate(2152.0, 333.0, 0.0), + "大瓶母液堆栈左": Coordinate(1164.0, 676.0, 0.0), + "大瓶母液堆栈右": Coordinate(2717.0, 676.0, 0.0), + } + + for warehouse_name, warehouse in self.warehouses.items(): + self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name]) + +def YB_Deck(name: str) -> Deck: + by=BIOYOND_YB_Deck(name=name) + by.setup() + return by + + + + + diff --git a/unilabos/resources/bioyond/warehouses.py b/unilabos/resources/bioyond/warehouses.py index 266a366..477e8ae 100644 --- a/unilabos/resources/bioyond/warehouses.py +++ b/unilabos/resources/bioyond/warehouses.py @@ -5,15 +5,15 @@ def bioyond_warehouse_1x4x4(name: str) -> WareHouse: """创建BioYond 4x1x4仓库""" return warehouse_factory( name=name, - num_items_x=4, + num_items_x=1, num_items_y=4, - num_items_z=1, + num_items_z=4, dx=10.0, dy=10.0, dz=10.0, - item_dx=147.0, - item_dy=106.0, - item_dz=130.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, category="warehouse", ) @@ -34,7 +34,113 @@ def bioyond_warehouse_1x4x2(name: str) -> WareHouse: category="warehouse", removed_positions=None ) + # 定义benyond的堆栈 +def bioyond_warehouse_1x2x2(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=1, + num_items_y=2, + num_items_z=2, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) +def bioyond_warehouse_10x1x1(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=10, + num_items_y=1, + num_items_z=1, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) +def bioyond_warehouse_1x3x3(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=1, + num_items_y=3, + num_items_z=3, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) +def bioyond_warehouse_2x1x3(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=2, + num_items_y=1, + num_items_z=3, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) +def bioyond_warehouse_3x3x1(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=3, + num_items_y=3, + num_items_z=1, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) +def bioyond_warehouse_5x1x1(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=5, + num_items_y=1, + num_items_z=1, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) +def bioyond_warehouse_3x3x1_2(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=3, + num_items_y=3, + num_items_z=1, + dx=12.0, + dy=12.0, + dz=12.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse: """创建BioYond开关盖加液模块台面""" From 2f25063bf1f179c04dfb1da8b7b440e08065f627 Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Mon, 20 Oct 2025 15:30:41 +0800 Subject: [PATCH 002/104] Update bioyond_cell_workstation.py --- .../bioyond_cell/bioyond_cell_workstation.py | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index 7119b1b..8f8f052 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -14,7 +14,6 @@ from urllib3 import response from unilabos.devices.workstation.workstation_base import WorkstationBase from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService from unilabos.utils.log import logger -from pylabrobot.resources.deck import Deck def _iso_local_now_ms() -> str: # 文档要求:到毫秒 + Z,例如 2025-08-15T05:43:22.814Z @@ -43,13 +42,15 @@ class BioyondCellWorkstation(WorkstationBase): "api_key": "8A819E5C", "timeout": 30, "report_token": "CHANGE_ME_TOKEN", - "HTTP_host": "192.168.1.104", + "HTTP_host": "172.21.32.90", "HTTP_port": 8080, "debug_mode": False } # report_token :unilab自己的令牌report_token(0928未启用) self.debug_mode = self.bioyond_config["debug_mode"] self.http_service_started = False - super().__init__(station_resource=station_resource, *args, **kwargs) + deck = kwargs.pop("deck", None) + self.device_id = kwargs.pop("device_id", "bioyond_cell_workstation") + super().__init__(deck=deck, station_resource=station_resource, *args, **kwargs) logger.info(f"Bioyond工作站初始化完成 (debug_mode={self.debug_mode})") # 实例化并在后台线程启动 HTTP 报送服务 @@ -132,8 +133,7 @@ class BioyondCellWorkstation(WorkstationBase): def auto_feeding4to3( self, # ★ 修改点:默认模板路径 - xlsx_path: Optional[str] = "unilabos/devices/workstation/bioyond_cell/样品导入模板.xlsx", - + xlsx_path: Optional[str] = "unilabos/devices/workstation/bioyond_studio/bioyond_cell/样品导入模板.xlsx", # ---------------- WH4 - 加样头面 (Z=1, 12个点位) ---------------- WH4_x1_y1_z1_1_materialName: str = "", WH4_x1_y1_z1_1_quantity: float = 0.0, WH4_x2_y1_z1_2_materialName: str = "", WH4_x2_y1_z1_2_quantity: float = 0.0, @@ -196,7 +196,7 @@ class BioyondCellWorkstation(WorkstationBase): for _, row in df.iloc[1:13, 2:7].iterrows(): if pd.notna(row[5]): items.append({ - "sourceWHName": "WH4", + "sourceWHName": "四号手套箱堆栈", "posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]), "materialName": str(row[5]).strip(), "quantity": float(row[6]) if pd.notna(row[6]) else 0.0, @@ -205,7 +205,7 @@ class BioyondCellWorkstation(WorkstationBase): for _, row in df.iloc[14:23, 2:9].iterrows(): if pd.notna(row[5]): items.append({ - "sourceWHName": "WH4", + "sourceWHName": "四号手套箱堆栈", "posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]), "materialName": str(row[5]).strip(), "quantity": float(row[6]) if pd.notna(row[6]) else 0.0, @@ -216,7 +216,7 @@ class BioyondCellWorkstation(WorkstationBase): for _, row in df.iloc[25:40, 2:7].iterrows(): if pd.notna(row[5]) or pd.notna(row[6]): items.append({ - "sourceWHName": "WH3", + "sourceWHName": "三号手套箱人工堆栈", "posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]), "materialType": str(row[5]).strip() if pd.notna(row[5]) else "", "materialId": str(row[6]).strip() if pd.notna(row[6]) else "", @@ -229,28 +229,28 @@ class BioyondCellWorkstation(WorkstationBase): if not items: params = locals() for name, value in params.items(): - if name.startswith("WH4") and "materialName" in name and value: + if name.startswith("四号手套箱堆栈") and "materialName" in name and value: idx = name.split("_") items.append({ - "sourceWHName": "WH4", + "sourceWHName": "四号手套箱堆栈", "posX": int(idx[1][1:]), "posY": int(idx[2][1:]), "posZ": int(idx[3][1:]), "materialName": value, "quantity": float(params.get(name.replace("materialName", "quantity"), 0.0)) }) - elif name.startswith("WH4") and "materialType" in name and (value or params.get(name.replace("materialType", "materialName"), "")): + elif name.startswith("四号手套箱堆栈") and "materialType" in name and (value or params.get(name.replace("materialType", "materialName"), "")): idx = name.split("_") items.append({ - "sourceWHName": "WH4", + "sourceWHName": "四号手套箱堆栈", "posX": int(idx[1][1:]), "posY": int(idx[2][1:]), "posZ": int(idx[3][1:]), "materialName": params.get(name.replace("materialType", "materialName"), ""), "quantity": float(params.get(name.replace("materialType", "quantity"), 0.0)), "materialType": value, "targetWH": params.get(name.replace("materialType", "targetWH"), ""), }) - elif name.startswith("WH3") and "materialType" in name and (value or params.get(name.replace("materialType", "materialId"), "")): + elif name.startswith("三号手套箱人工堆栈") and "materialType" in name and (value or params.get(name.replace("materialType", "materialId"), "")): idx = name.split("_") items.append({ - "sourceWHName": "WH3", + "sourceWHName": "三号手套箱人工堆栈", "posX": int(idx[1][1:]), "posY": int(idx[2][1:]), "posZ": int(idx[3][1:]), "materialType": value, "materialId": params.get(name.replace("materialType", "materialId"), ""), @@ -260,7 +260,7 @@ class BioyondCellWorkstation(WorkstationBase): if not items: logger.warning("没有有效的上料条目,已跳过提交。") return {"code": 0, "message": "no valid items", "data": []} - + logger.info(items) return self._post_lims("/api/lims/order/auto-feeding4to3", items) @@ -641,9 +641,13 @@ class BioyondCellWorkstation(WorkstationBase): # -------------------------------- if __name__ == "__main__": ws = BioyondCellWorkstation() - ws.scheduler_stop() - # re=ws.scheduler_start() - print(re) + # logger.info(ws.scheduler_start()) + + logger.info(ws.auto_feeding4to3()) + # re=ws.scheduler_stop() + # re = ws.transfer_3_to_2_to_1() + + # print(re) # logger.info("调度启动完成") # ws.scheduler_continue() From ab697ce973b7988f796b1cf543fb65109fd93315 Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Mon, 20 Oct 2025 16:12:38 +0800 Subject: [PATCH 003/104] Update station.py --- .../workstation/bioyond_studio/station.py | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/station.py b/unilabos/devices/workstation/bioyond_studio/station.py index 33975d8..3c596a2 100644 --- a/unilabos/devices/workstation/bioyond_studio/station.py +++ b/unilabos/devices/workstation/bioyond_studio/station.py @@ -20,6 +20,7 @@ from unilabos.ros.nodes.base_device_node import ROS2DeviceNode, BaseROS2DeviceNo from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode from pylabrobot.resources.resource import Resource as ResourcePLR +from unilabos.resources.bioyond.decks import YB_Deck from unilabos.devices.workstation.bioyond_studio.config import ( API_CONFIG, WORKFLOW_MAPPINGS, MATERIAL_TYPE_MAPPINGS, WAREHOUSE_MAPPING ) @@ -135,7 +136,7 @@ class BioyondWorkstation(WorkstationBase): # 初始化父类 super().__init__( # 桌子 - deck=deck, + deck=YB_Deck("YB_Deck14"), *args, **kwargs, ) @@ -467,7 +468,7 @@ def create_bioyond_workstation_example(): """创建Bioyond工作站示例""" # 配置参数 - device_id = "bioyond_workstation_001" + device_id = "bioyond_cell_workstation_001" # 子资源配置 children = { @@ -486,8 +487,8 @@ def create_bioyond_workstation_example(): # Bioyond配置 bioyond_config = { - "base_url": "http://bioyond.example.com/api", - "api_key": "your_api_key_here", + "base_url": "http://172.16.11.219:44388", + "api_key": "8A819E5C", "sync_interval": 60, # 60秒同步一次 "timeout": 30 } @@ -511,4 +512,18 @@ def create_bioyond_workstation_example(): if __name__ == "__main__": + by = create_bioyond_workstation_example() + by.get_workstation_status() + by.get_device_status() + by.get_bioyond_status() + by.get_station_info() + by.get_workstation_status() + by.get_bioyond_status() + by.get_station_info() + by.get_workstation_status() + by.get_bioyond_status() + by.get_station_info() + by.get_workstation_status() + by.get_bioyond_status() + by.get_station_info() pass \ No newline at end of file From 37641c43895a84f13830c1ef2dec32d918d648e1 Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Tue, 21 Oct 2025 14:48:55 +0800 Subject: [PATCH 004/104] =?UTF-8?q?xinyu1021=E6=8E=A8=E9=80=81=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bioyond_cell/bioyond_cell_workstation.py | 120 +++++- .../bioyond_material_management.py | 374 ------------------ .../bioyond_cell/bioyond_yihua_YB.json | 8 +- .../workstation/bioyond_studio/bioyond_rpc.py | 6 +- .../workstation/workstation_http_service.py | 6 +- 5 files changed, 123 insertions(+), 391 deletions(-) delete mode 100644 unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_material_management.py diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index 8f8f052..be33125 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -42,7 +42,7 @@ class BioyondCellWorkstation(WorkstationBase): "api_key": "8A819E5C", "timeout": 30, "report_token": "CHANGE_ME_TOKEN", - "HTTP_host": "172.21.32.90", + "HTTP_host": "172.21.33.126", "HTTP_port": 8080, "debug_mode": False } # report_token :unilab自己的令牌report_token(0928未启用) @@ -51,6 +51,8 @@ class BioyondCellWorkstation(WorkstationBase): deck = kwargs.pop("deck", None) self.device_id = kwargs.pop("device_id", "bioyond_cell_workstation") super().__init__(deck=deck, station_resource=station_resource, *args, **kwargs) + # 步骤通量任务通知铃 + self._pending_events: dict[str, threading.Event] = {} logger.info(f"Bioyond工作站初始化完成 (debug_mode={self.debug_mode})") # 实例化并在后台线程启动 HTTP 报送服务 @@ -62,6 +64,98 @@ class BioyondCellWorkstation(WorkstationBase): except Exception as e: logger.error(f"unilab-HTTP后台线程启动失败: {e}") + # http报送服务 + def process_step_finish_report(self, report_request): + stepId = report_request.data.get("stepId") + logger.info(f"步骤完成: stepId: {stepId}, stepName:{report_request.data.get('stepName')}") + return report_request.data.get('executionStatus') + + def process_sample_finish_report(self, report_request): + logger.info(f"通量完成: {report_request.data.get('sampleId')}") + return {"status": "received"} + + def process_order_finish_report(self, report_request, used_materials=None): + order_code = report_request.data.get("orderCode") + + logger.info(f"任务完成: {order_code}, status={report_request.data.get('status')}") + self._set_pending_event(order_code) + return {"status": "received"} + + def _set_pending_event(self, taskname: Optional[str]) -> None: + if not taskname: + return + event = self._pending_events.get(taskname) + if event is None: + event = threading.Event() + self._pending_events[taskname] = event + event.set() + + def _wait_for_order_completion(self, order_code: Optional[str], timeout: int = 600) -> bool: + if not order_code: + logger.warning("无法等待任务完成:order_code 为空") + return False + event = self._pending_events.get(order_code) + if event is None: + event = threading.Event() + self._pending_events[order_code] = event + elif event.is_set(): + logger.info(f"任务 {order_code} 在等待之前已完成") + self._pending_events.pop(order_code, None) + return True + logger.info(f"等待任务 {order_code} 完成 (timeout={timeout}s)") + finished = event.wait(timeout) + if not finished: + logger.warning(f"等待任务 {order_code} 完成超时({timeout}s)") + self._pending_events.pop(order_code, None) + return finished + + def _wait_for_response_orders(self, response: Dict[str, Any], context: str, timeout: int = 600) -> None: + order_codes = self._extract_order_codes(response) + if not order_codes: + logger.warning(f"{context} 响应中未找到 orderCode,无法跟踪任务完成") + return + for code in order_codes: + self._wait_for_order_completion(code, timeout=timeout) + + @staticmethod + def _extract_order_codes(response: Dict[str, Any]) -> List[str]: + order_codes: List[str] = [] + if not isinstance(response, dict): + return order_codes + data = response.get("data") + keys = ["orderCode", "order_code", "orderId", "order_id"] + if isinstance(data, dict): + for key in keys: + if key in data and data[key]: + order_codes.append(str(data[key])) + if not order_codes and "orders" in data and isinstance(data["orders"], list): + for order in data["orders"]: + if isinstance(order, dict): + for key in keys: + if key in order and order[key]: + order_codes.append(str(order[key])) + elif isinstance(data, list): + for item in data: + if isinstance(item, dict): + for key in keys: + if key in item and item[key]: + order_codes.append(str(item[key])) + elif isinstance(data, str): + if data: + order_codes.append(data) + meta = response.get("orderCode") + if meta: + order_codes.append(str(meta)) + # 去重 + seen = set() + unique_codes: List[str] = [] + for code in order_codes: + if code not in seen: + seen.add(code) + unique_codes.append(code) + return unique_codes + + def _start_http_service(self, host: Optional[str] = None, port: Optional[int] = None) -> None: host = host or self.bioyond_config.get("HTTP_host", "") port = port or self.bioyond_config.get("HTTP_port", ) @@ -261,7 +355,9 @@ class BioyondCellWorkstation(WorkstationBase): logger.warning("没有有效的上料条目,已跳过提交。") return {"code": 0, "message": "no valid items", "data": []} logger.info(items) - return self._post_lims("/api/lims/order/auto-feeding4to3", items) + response = self._post_lims("/api/lims/order/auto-feeding4to3", items) + self._wait_for_response_orders(response, "auto_feeding4to3") + return response @@ -327,7 +423,9 @@ class BioyondCellWorkstation(WorkstationBase): if item["materialId"] or item["materialType"]: items.append(item) - return self._post_lims("/api/lims/order/auto-feeding4to3", items) + response = self._post_lims("/api/lims/order/auto-feeding4to3", items) + self._wait_for_response_orders(response, "auto_feeding4to3_from_xlsx") + return response def auto_batch_outbound_from_xlsx(self, xlsx_path: str) -> Dict[str, Any]: """ @@ -392,7 +490,9 @@ class BioyondCellWorkstation(WorkstationBase): "z": as_int(row[c_z]), }) - return self._post_lims("/api/lims/storage/auto-batch-out-bound", items) + response = self._post_lims("/api/lims/storage/auto-batch-out-bound", items) + self._wait_for_response_orders(response, "auto_batch_outbound_from_xlsx") + return response # 2.14 新建实验 def create_orders(self, xlsx_path: str) -> Dict[str, Any]: @@ -517,6 +617,7 @@ class BioyondCellWorkstation(WorkstationBase): # self.wait_for_transfer_task() # logger.info(f"3-2-1 转运完成,返回结果") # return r321 + self._wait_for_response_orders(response, "create_orders", timeout=1800) return response # 2.7 启动调度 @@ -641,9 +742,18 @@ class BioyondCellWorkstation(WorkstationBase): # -------------------------------- if __name__ == "__main__": ws = BioyondCellWorkstation() - # logger.info(ws.scheduler_start()) + logger.info(ws.scheduler_start()) logger.info(ws.auto_feeding4to3()) + logger.info(ws.create_orders(r"unilabos\devices\workstation\bioyond_studio\bioyond_cell\2025092701.xlsx")) + logger.info(ws.transfer_3_to_2_to_1()) + + logger.info(ws.transfer_1_to_2()) + + + + while True: + time.sleep(1) # re=ws.scheduler_stop() # re = ws.transfer_3_to_2_to_1() diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_material_management.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_material_management.py deleted file mode 100644 index 245da15..0000000 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_material_management.py +++ /dev/null @@ -1,374 +0,0 @@ -""" -Bioyond物料管理实现 -Bioyond Material Management Implementation - -基于Bioyond系统的物料管理,支持从Bioyond系统同步物料到UniLab工作站 -""" -from typing import Dict, Any, List, Optional, Union -import json -import asyncio -from abc import ABC, abstractmethod - -from pylabrobot.resources import ( - Resource as PLRResource, - Container, - Deck, - Coordinate as PLRCoordinate, -) - -from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker -from unilabos.utils.log import logger -from unilabos.resources.graphio import ( - resource_plr_to_ulab, - resource_ulab_to_plr, - resource_bioyond_to_ulab, - resource_bioyond_container_to_ulab, - resource_ulab_to_bioyond -) -from .workstation_material_management import MaterialManagementBase - - -class BioyondMaterialManagement(MaterialManagementBase): - """Bioyond物料管理类 - - 实现从Bioyond系统同步物料到UniLab工作站的功能: - 1. 从Bioyond系统获取物料数据 - 2. 转换为UniLab格式 - 3. 同步到PyLabRobot Deck - 4. 支持双向同步 - """ - - def __init__( - self, - device_id: str, - deck_config: Dict[str, Any], - resource_tracker: DeviceNodeResourceTracker, - children_config: Dict[str, Dict[str, Any]] = None, - bioyond_config: Dict[str, Any] = None - ): - self.bioyond_config = bioyond_config or {} - self.bioyond_api_client = None - self.sync_interval = self.bioyond_config.get("sync_interval", 30) # 同步间隔(秒) - - # 初始化父类 - super().__init__(device_id, deck_config, resource_tracker, children_config) - - # 初始化Bioyond API客户端 - self._initialize_bioyond_client() - - # 启动同步任务 - self._start_sync_task() - - def _initialize_bioyond_client(self): - """初始化Bioyond API客户端""" - try: - # 这里应该根据实际的Bioyond API实现 - # 暂时使用模拟客户端 - self.bioyond_api_client = BioyondAPIClient(self.bioyond_config) - logger.info(f"Bioyond API客户端初始化成功") - except Exception as e: - logger.error(f"Bioyond API客户端初始化失败: {e}") - self.bioyond_api_client = None - - def _start_sync_task(self): - """启动同步任务""" - if self.bioyond_api_client: - # 创建异步同步任务 - asyncio.create_task(self._periodic_sync()) - logger.info(f"Bioyond同步任务已启动,间隔: {self.sync_interval}秒") - - async def _periodic_sync(self): - """定期同步任务""" - while True: - try: - await self.sync_from_bioyond() - await asyncio.sleep(self.sync_interval) - except Exception as e: - logger.error(f"Bioyond同步任务出错: {e}") - await asyncio.sleep(self.sync_interval) - - async def sync_from_bioyond(self) -> bool: - """从Bioyond系统同步物料""" - try: - if not self.bioyond_api_client: - logger.warning("Bioyond API客户端未初始化") - return False - - # 1. 从Bioyond获取物料数据 - bioyond_data = await self.bioyond_api_client.get_materials() - if not bioyond_data: - logger.warning("从Bioyond获取物料数据为空") - return False - - # 2. 转换为UniLab格式 - if isinstance(bioyond_data, dict) and "data" in bioyond_data: - # 容器格式数据 - unilab_resources = resource_bioyond_container_to_ulab(bioyond_data) - else: - # 物料列表格式数据 - unilab_resources = resource_bioyond_to_ulab(bioyond_data) - - # 3. 转换为PLR格式并分配到Deck - await self._assign_resources_to_deck(unilab_resources) - - logger.info(f"从Bioyond同步了 {len(unilab_resources)} 个资源") - return True - - except Exception as e: - logger.error(f"从Bioyond同步物料失败: {e}") - return False - - async def sync_to_bioyond(self, plr_resource: PLRResource) -> bool: - """将本地物料变更同步到Bioyond系统""" - try: - if not self.bioyond_api_client: - logger.warning("Bioyond API客户端未初始化") - return False - - # 1. 转换为UniLab格式 - unilab_resource = resource_plr_to_ulab(plr_resource) - - # 2. 转换为Bioyond格式 - bioyond_materials = resource_ulab_to_bioyond([unilab_resource]) - - # 3. 发送到Bioyond系统 - success = await self.bioyond_api_client.update_materials(bioyond_materials) - - if success: - logger.info(f"成功同步物料 {plr_resource.name} 到Bioyond") - else: - logger.warning(f"同步物料 {plr_resource.name} 到Bioyond失败") - - return success - - except Exception as e: - logger.error(f"同步物料到Bioyond失败: {e}") - return False - - async def _assign_resources_to_deck(self, unilab_resources: List[Dict[str, Any]]): - """将UniLab资源分配到Deck""" - try: - # 转换为PLR格式 - from unilabos.resources.graphio import list_to_nested_dict - nested_resources = list_to_nested_dict(unilab_resources) - plr_resources = resource_ulab_to_plr(nested_resources) - - # 分配资源到Deck - if hasattr(plr_resources, 'children'): - resources_to_assign = plr_resources.children - elif isinstance(plr_resources, list): - resources_to_assign = plr_resources - else: - resources_to_assign = [plr_resources] - - for resource in resources_to_assign: - try: - # 获取资源位置 - if hasattr(resource, 'location') and resource.location: - location = PLRCoordinate(resource.location.x, resource.location.y, resource.location.z) - else: - location = PLRCoordinate(0, 0, 0) - - # 分配资源到Deck - self.plr_deck.assign_child_resource(resource, location) - - # 注册到resource tracker - self.resource_tracker.add_resource(resource) - - # 保存资源引用 - self.plr_resources[resource.name] = resource - - except Exception as e: - logger.error(f"分配资源 {resource.name} 到Deck失败: {e}") - - logger.info(f"成功分配了 {len(resources_to_assign)} 个资源到Deck") - - except Exception as e: - logger.error(f"分配资源到Deck失败: {e}") - - def _create_resource_by_type( - self, - resource_id: str, - resource_type: str, - config: Dict[str, Any], - data: Dict[str, Any], - location: PLRCoordinate - ) -> Optional[PLRResource]: - """根据类型创建Bioyond相关资源""" - try: - # 这里可以根据需要实现特定的Bioyond资源类型 - # 目前使用通用的容器类型 - if resource_type in ["container", "plate", "well"]: - return self._create_generic_container(resource_id, resource_type, config, data, location) - else: - logger.warning(f"未知的Bioyond资源类型: {resource_type}") - return None - - except Exception as e: - logger.error(f"创建Bioyond资源失败 {resource_id} ({resource_type}): {e}") - return None - - def _create_generic_container( - self, - resource_id: str, - resource_type: str, - config: Dict[str, Any], - data: Dict[str, Any], - location: PLRCoordinate - ) -> Optional[PLRResource]: - """创建通用容器资源""" - try: - from pylabrobot.resources import Plate, Well - - if resource_type == "plate": - return Plate( - name=resource_id, - size_x=config.get("size_x", 127.76), - size_y=config.get("size_y", 85.48), - size_z=config.get("size_z", 14.35), - location=location, - category="plate" - ) - elif resource_type == "well": - return Well( - name=resource_id, - size_x=config.get("size_x", 9.0), - size_y=config.get("size_y", 9.0), - size_z=config.get("size_z", 10.0), - location=location, - category="well" - ) - else: - return Container( - name=resource_id, - size_x=config.get("size_x", 50.0), - size_y=config.get("size_y", 50.0), - size_z=config.get("size_z", 10.0), - location=location, - category="container" - ) - - except Exception as e: - logger.error(f"创建通用容器失败 {resource_id}: {e}") - return None - - def get_bioyond_materials(self) -> List[Dict[str, Any]]: - """获取当前Bioyond物料列表""" - try: - # 将当前PLR资源转换为Bioyond格式 - bioyond_materials = [] - for resource in self.plr_resources.values(): - unilab_resource = resource_plr_to_ulab(resource) - bioyond_materials.extend(resource_ulab_to_bioyond([unilab_resource])) - return bioyond_materials - except Exception as e: - logger.error(f"获取Bioyond物料列表失败: {e}") - return [] - - def update_material_from_bioyond(self, material_id: str, bioyond_data: Dict[str, Any]) -> bool: - """从Bioyond数据更新指定物料""" - try: - # 查找现有物料 - material = self.find_material_by_id(material_id) - if not material: - logger.warning(f"未找到物料: {material_id}") - return False - - # 转换Bioyond数据为UniLab格式 - unilab_resources = resource_bioyond_to_ulab([bioyond_data]) - if not unilab_resources: - logger.warning(f"转换Bioyond数据失败: {material_id}") - return False - - # 更新物料属性 - unilab_resource = unilab_resources[0] - material.name = unilab_resource.get("name", material.name) - - # 更新位置 - position = unilab_resource.get("position", {}) - if position: - material.location = PLRCoordinate( - position.get("x", 0), - position.get("y", 0), - position.get("z", 0) - ) - - logger.info(f"成功更新物料: {material_id}") - return True - - except Exception as e: - logger.error(f"更新物料失败 {material_id}: {e}") - return False - - -class BioyondAPIClient: - """Bioyond API客户端(模拟实现) - - 实际使用时需要根据Bioyond系统的API接口实现 - """ - - def __init__(self, config: Dict[str, Any]): - self.config = config - self.base_url = config.get("base_url", "http://localhost:8080") - self.api_key = config.get("api_key", "") - self.timeout = config.get("timeout", 30) - - async def get_materials(self) -> Optional[Union[Dict[str, Any], List[Dict[str, Any]]]]: - """从Bioyond系统获取物料数据""" - try: - # 这里应该实现实际的API调用 - # 暂时返回模拟数据 - logger.info("从Bioyond API获取物料数据") - - # 模拟API调用延迟 - await asyncio.sleep(0.1) - - # 返回模拟数据(实际应该从API获取) - return { - "data": [], - "code": 1, - "message": "success", - "timestamp": 1234567890 - } - - except Exception as e: - logger.error(f"Bioyond API调用失败: {e}") - return None - - async def update_materials(self, materials: List[Dict[str, Any]]) -> bool: - """更新Bioyond系统中的物料数据""" - try: - # 这里应该实现实际的API调用 - logger.info(f"更新Bioyond系统中的 {len(materials)} 个物料") - - # 模拟API调用延迟 - await asyncio.sleep(0.1) - - # 模拟成功响应 - return True - - except Exception as e: - logger.error(f"更新Bioyond物料失败: {e}") - return False - - async def get_material_by_id(self, material_id: str) -> Optional[Dict[str, Any]]: - """根据ID获取单个物料""" - try: - # 这里应该实现实际的API调用 - logger.info(f"从Bioyond API获取物料: {material_id}") - - # 模拟API调用延迟 - await asyncio.sleep(0.1) - - # 返回模拟数据 - return { - "id": material_id, - "name": f"material_{material_id}", - "type": "container", - "quantity": 1.0, - "unit": "个" - } - - except Exception as e: - logger.error(f"获取Bioyond物料失败 {material_id}: {e}") - return None diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json index 29f6254..d6a917f 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json @@ -10,13 +10,9 @@ "class": "bioyond_cell", "config": { "protocol_type": [], - "station_resource": {}, + "station_resource": {} + - "bioyond_config": { - "api_key": "8A819E5C", - "api_host": "http://192.168.1.200:44388", - "debug_mode": false - } }, "data": {} }, diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_rpc.py b/unilabos/devices/workstation/bioyond_studio/bioyond_rpc.py index 45d0cad..5aef45b 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_rpc.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_rpc.py @@ -47,8 +47,8 @@ class BioyondV1RPC(BaseRequest): super().__init__() print("开始初始化 BioyondV1RPC") self.config = config - self.api_key = config["api_key"] - self.host = config["api_host"] + self.api_key = config["8A819E5C"] + self.host = config["http://172.16.11.219:44388"] self._logger = SimpleLogger() self.material_cache = {} self._load_material_cache() @@ -61,7 +61,7 @@ class BioyondV1RPC(BaseRequest): :return: 当前时间的 ISO 8601 格式字符串 """ - current_time = datetime.now(timezone.utc).isoformat( + current_time = datetime.now().isoformat( timespec='milliseconds' ) # 替换时区部分为 'Z' diff --git a/unilabos/devices/workstation/workstation_http_service.py b/unilabos/devices/workstation/workstation_http_service.py index 27f869c..12eb926 100644 --- a/unilabos/devices/workstation/workstation_http_service.py +++ b/unilabos/devices/workstation/workstation_http_service.py @@ -71,11 +71,11 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler): if content_length > 0: post_data = self.rfile.read(content_length) request_data = json.loads(post_data.decode('utf-8')) + else: request_data = {} - - logger.info(f"收到工作站报送: {endpoint} - {request_data.get('token', 'unknown')}") - + logger.info(f"收到工作站报送: {endpoint} 收到接受数据:{request_data}") + # logger.info(f"收到的json数据: {request_data}") # 统一的报送端点路由(基于LIMS协议规范) if endpoint == '/report/step_finish': response = self._handle_step_finish_report(request_data) From 2c2d1e5569e857e76c50844a27737c7ef9f7fd24 Mon Sep 17 00:00:00 2001 From: calvincao Date: Tue, 21 Oct 2025 14:58:38 +0800 Subject: [PATCH 005/104] =?UTF-8?q?=E5=9C=A8=20**bioyond=5Fcell=5Fworkstat?= =?UTF-8?q?ion.py**=20=E4=B8=AD=E5=AE=9E=E7=8E=B0=20`update=5Fpush=5Fip`?= =?UTF-8?q?=20=E6=96=B9=E6=B3=95=E5=B9=B6=E5=A2=9E=E5=BC=BA=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E5=A4=84=E7=90=86=EF=BC=9B=E4=BF=AE=E5=A4=8D=20**bioy?= =?UTF-8?q?ond=5Fyihua=5FYB.json**=20=E4=B8=AD=E7=9A=84=20JSON=20=E5=90=88?= =?UTF-8?q?=E5=B9=B6=E5=86=B2=E7=AA=81=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bioyond_cell/bioyond_cell_workstation.py | 48 +++++++++++++++++++ .../bioyond_cell/bioyond_yihua_YB.json | 3 ++ 2 files changed, 51 insertions(+) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index be33125..7ed0e9f 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -167,6 +167,12 @@ class BioyondCellWorkstation(WorkstationBase): self.service.start() self.http_service_started = True logger.info(f"WorkstationHTTPService成功启动: {host}:{port}") + # 启动成功后,上报本机推送地址(3.36) + try: + r = self.update_push_ip(host, port) + logger.info(f"更新推送IP结果: {r}") + except Exception as e: + logger.warning(f"调用更新推送IP接口失败: {e}") #一直挂着,直到进程退出 while True: time.sleep(1) @@ -207,6 +213,48 @@ class BioyondCellWorkstation(WorkstationBase): logger.error(f"POST {path} 失败: {e}") return {"error": str(e)} + def _put_lims(self, path: str, data: Optional[Any] = None) -> Dict[str, Any]: + """LIMS API:PUT {apiKey/requestTime,data} 包装""" + payload = { + "apiKey": self.bioyond_config["api_key"], + "requestTime": _iso_local_now_ms() + } + if data is not None: + payload["data"] = data + + if self.debug_mode: + logger.info(f"[DEBUG] PUT {path} with payload={payload}") + return {"debug": True, "url": self._url(path), "payload": payload, "status": "ok"} + + try: + response = requests.put( + self._url(path), + json=payload, + timeout=self.bioyond_config.get("timeout", 30), + headers={"Content-Type": "application/json"} + ) + response.raise_for_status() + return response.json() + except Exception as e: + logger.info(f"{self.bioyond_config['base_url'].rstrip('/')}/{path.lstrip('/')}") + logger.error(f"PUT {path} 失败: {e}") + return {"error": str(e)} + + # -------------------- 3.36 更新推送 IP 地址 -------------------- + def update_push_ip(self, ip: Optional[str] = None, port: Optional[int] = None) -> Dict[str, Any]: + """ + 3.36 更新推送 IP 地址接口(PUT) + URL: /api/lims/order/ip-config + 请求体:{ apiKey, requestTime, data: { ip, port } } + """ + target_ip = ip or self.bioyond_config.get("HTTP_host", "") + target_port = int(port or self.bioyond_config.get("HTTP_port", 0)) + data = {"ip": target_ip, "port": target_port} + + # 固定接口路径,不做其他路径兼容 + path = "/api/lims/order/ip-config" + return self._put_lims(path, data) + # -------------------- 单点接口封装 -------------------- # 2.17 入库物料(单个) def storage_inbound(self, material_id: str, location_id: str) -> Dict[str, Any]: diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json index d6a917f..81e1ee7 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json @@ -11,7 +11,10 @@ "config": { "protocol_type": [], "station_resource": {} +<<<<<<< Updated upstream +======= +>>>>>>> Stashed changes }, "data": {} From e70c545ec80e5b9eadde7471ea383156d9c73ef0 Mon Sep 17 00:00:00 2001 From: calvincao Date: Tue, 21 Oct 2025 15:19:44 +0800 Subject: [PATCH 006/104] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20**bioyond=5Fyihua?= =?UTF-8?q?=5FYB.json**=20=E4=B8=AD=E7=9A=84=20JSON=20=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E5=86=B2=E7=AA=81=EF=BC=8C=E6=B8=85=E7=90=86=E4=B8=8D=E5=BF=85?= =?UTF-8?q?=E8=A6=81=E7=9A=84=E6=A0=87=E8=AE=B0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bioyond_studio/bioyond_cell/bioyond_yihua_YB.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json index 81e1ee7..3d1b98a 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json @@ -11,10 +11,6 @@ "config": { "protocol_type": [], "station_resource": {} -<<<<<<< Updated upstream - -======= ->>>>>>> Stashed changes }, "data": {} From de7c80c3c2d8eb7b262b8e597173edef8b9691df Mon Sep 17 00:00:00 2001 From: calvincao Date: Wed, 22 Oct 2025 16:13:36 +0800 Subject: [PATCH 007/104] =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=9A=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E9=85=8D=E7=BD=AE=E5=8A=A0=E8=BD=BD=E6=9C=BA=E5=88=B6?= =?UTF-8?q?=E4=B8=8E=E5=88=9D=E5=A7=8B=E5=8C=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增环境变量覆盖机制,增强配置灵活性 优化 bioyond_rpc.py 与 bioyond_cell_workstation.py 的初始化流程与结构 修正 station.py 工作流映射逻辑,确保正确性 提高代码可读性与模块间解耦程度 --- .../bioyond_cell/bioyond_cell_workstation.py | 27 +++++++++++-------- .../workstation/bioyond_studio/bioyond_rpc.py | 4 +-- .../workstation/bioyond_studio/config.py | 27 ++++++++++++++++--- .../workstation/bioyond_studio/station.py | 4 +-- 4 files changed, 44 insertions(+), 18 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index 7ed0e9f..78b635c 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -9,9 +9,14 @@ import time from datetime import datetime, timedelta import re import threading +import os from urllib3 import response from unilabos.devices.workstation.workstation_base import WorkstationBase +from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation +from unilabos.devices.workstation.bioyond_studio.config import ( + BIOYOND_FULL_CONFIG, WORKFLOW_MAPPINGS, MATERIAL_TYPE_MAPPINGS, WAREHOUSE_MAPPING +) from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService from unilabos.utils.log import logger @@ -22,7 +27,7 @@ def _iso_local_now_ms() -> str: return dt.strftime("%Y-%m-%dT%H:%M:%S.") + f"{int(dt.microsecond/1000):03d}Z" -class BioyondCellWorkstation(WorkstationBase): +class BioyondCellWorkstation(BioyondWorkstation): """ 集成 Bioyond LIMS 的工作站示例, 覆盖:入库(2.17/2.18) → 新建实验(2.14) → 启动调度(2.7) → @@ -37,20 +42,18 @@ class BioyondCellWorkstation(WorkstationBase): *args, **kwargs, ): + # 使用统一配置,支持自定义覆盖 self.bioyond_config = bioyond_config or { - "base_url": "http://172.16.11.219:44388", - "api_key": "8A819E5C", - "timeout": 30, - "report_token": "CHANGE_ME_TOKEN", - "HTTP_host": "172.21.33.126", - "HTTP_port": 8080, - "debug_mode": False - } # report_token :unilab自己的令牌report_token(0928未启用) + **BIOYOND_FULL_CONFIG, # 从 config.py 加载完整配置 + "workflow_mappings": WORKFLOW_MAPPINGS, + "material_type_mappings": MATERIAL_TYPE_MAPPINGS, + "warehouse_mapping": WAREHOUSE_MAPPING + } self.debug_mode = self.bioyond_config["debug_mode"] self.http_service_started = False deck = kwargs.pop("deck", None) self.device_id = kwargs.pop("device_id", "bioyond_cell_workstation") - super().__init__(deck=deck, station_resource=station_resource, *args, **kwargs) + super().__init__(bioyond_config=self.bioyond_config, deck=deck, station_resource=station_resource, *args, **kwargs) # 步骤通量任务通知铃 self._pending_events: dict[str, threading.Event] = {} logger.info(f"Bioyond工作站初始化完成 (debug_mode={self.debug_mode})") @@ -793,7 +796,9 @@ if __name__ == "__main__": logger.info(ws.scheduler_start()) logger.info(ws.auto_feeding4to3()) - logger.info(ws.create_orders(r"unilabos\devices\workstation\bioyond_studio\bioyond_cell\2025092701.xlsx")) + # 使用正斜杠或 Path 对象来指定文件路径 + excel_path = Path("unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx") + logger.info(ws.create_orders(excel_path)) logger.info(ws.transfer_3_to_2_to_1()) logger.info(ws.transfer_1_to_2()) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_rpc.py b/unilabos/devices/workstation/bioyond_studio/bioyond_rpc.py index 5aef45b..ef6ff77 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_rpc.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_rpc.py @@ -47,8 +47,8 @@ class BioyondV1RPC(BaseRequest): super().__init__() print("开始初始化 BioyondV1RPC") self.config = config - self.api_key = config["8A819E5C"] - self.host = config["http://172.16.11.219:44388"] + self.api_key = config.get("api_key", "") + self.host = config.get("api_host", "") or config.get("base_url", "") self._logger = SimpleLogger() self.material_cache = {} self._load_material_cache() diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 52ae82e..c73abd3 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -2,11 +2,32 @@ """ 配置文件 - 包含所有配置信息和映射关系 """ +import os -# API配置 +# ==================== API 基础配置 ==================== +# 支持通过环境变量覆盖默认值 API_CONFIG = { - "api_key": "", - "api_host": "" + "api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"), + "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.11.219:44388"), +} + +# ==================== 完整的 Bioyond 配置 ==================== +# BioyondCellWorkstation 默认配置(包含所有必需参数) +BIOYOND_FULL_CONFIG = { + # API 连接配置 + "base_url": os.getenv("BIOYOND_API_HOST", "http://172.16.11.219:44388"), + "api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"), + "timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")), + + # 报送配置 + "report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"), + + # HTTP 服务配置 + "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "0.0.0.0"), # 0.0.0.0 绑定所有网络接口 + "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), + + # 调试模式 + "debug_mode": os.getenv("BIOYOND_DEBUG_MODE", "False").lower() == "true", } # 工作流映射配置 diff --git a/unilabos/devices/workstation/bioyond_studio/station.py b/unilabos/devices/workstation/bioyond_studio/station.py index 3c596a2..795b740 100644 --- a/unilabos/devices/workstation/bioyond_studio/station.py +++ b/unilabos/devices/workstation/bioyond_studio/station.py @@ -165,8 +165,8 @@ class BioyondWorkstation(WorkstationBase): self.workflow_sequence = [] self.pending_task_params = [] - if "workflow_mappings" in bioyond_config: - self._set_workflow_mappings(bioyond_config["workflow_mappings"]) + if self.bioyond_config and "workflow_mappings" in self.bioyond_config: + self._set_workflow_mappings(self.bioyond_config["workflow_mappings"]) logger.info(f"Bioyond工作站初始化完成") def post_init(self, ros_node: ROS2WorkstationNode): From 9c4d0256cf8abb179a9f0d7f1e7cd526f6497359 Mon Sep 17 00:00:00 2001 From: calvincao Date: Wed, 22 Oct 2025 16:38:32 +0800 Subject: [PATCH 008/104] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6=EF=BC=8C=E6=96=B0=E5=A2=9E=20report=5Fip=20?= =?UTF-8?q?=E9=80=89=E9=A1=B9=E4=BB=A5=E6=94=AF=E6=8C=81=E6=9C=AC=E6=9C=BA?= =?UTF-8?q?=20IP=20=E5=9C=B0=E5=9D=80=E7=9A=84=E7=81=B5=E6=B4=BB=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=EF=BC=9B=E5=9C=A8=20bioyond=5Fcell=5Fworkstation.py?= =?UTF-8?q?=20=E4=B8=AD=E4=BC=98=E5=8C=96=E6=8E=A8=E9=80=81=E5=9C=B0?= =?UTF-8?q?=E5=9D=80=E6=9B=B4=E6=96=B0=E9=80=BB=E8=BE=91=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E8=87=AA=E5=8A=A8=E6=A3=80=E6=B5=8B=E5=92=8C=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E4=BC=98=E5=85=88=E7=BA=A7=E5=A4=84=E7=90=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bioyond_cell/bioyond_cell_workstation.py | 22 +++++++++++++++++-- .../workstation/bioyond_studio/config.py | 3 ++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index 78b635c..ef4004e 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -10,6 +10,7 @@ from datetime import datetime, timedelta import re import threading import os +import socket from urllib3 import response from unilabos.devices.workstation.workstation_base import WorkstationBase @@ -170,12 +171,29 @@ class BioyondCellWorkstation(BioyondWorkstation): self.service.start() self.http_service_started = True logger.info(f"WorkstationHTTPService成功启动: {host}:{port}") + # 启动成功后,上报本机推送地址(3.36) try: - r = self.update_push_ip(host, port) - logger.info(f"更新推送IP结果: {r}") + # 优先使用配置中的 report_ip + report_ip = self.bioyond_config.get("report_ip", "").strip() + + # 如果配置中没有指定 report_ip,且监听地址是 0.0.0.0,则自动检测 + if not report_ip and host in ("0.0.0.0", ""): + # 从 Bioyond 配置中提取服务器地址 + bioyond_server = self.bioyond_config.get("base_url", "") + if bioyond_server: + import urllib.parse + parsed = urllib.parse.urlparse(bioyond_server) + + elif not report_ip: + # 如果没有配置 report_ip,使用监听地址 + report_ip = host + + r = self.update_push_ip(report_ip, port) + logger.info(f"向 Bioyond 报送推送地址: {report_ip}:{port}, 结果: {r}") except Exception as e: logger.warning(f"调用更新推送IP接口失败: {e}") + #一直挂着,直到进程退出 while True: time.sleep(1) diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index c73abd3..e2506bc 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -23,8 +23,9 @@ BIOYOND_FULL_CONFIG = { "report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"), # HTTP 服务配置 - "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "0.0.0.0"), # 0.0.0.0 绑定所有网络接口 + "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "0.0.0.0"), # HTTP服务监听地址(0.0.0.0 表示监听所有网络接口) "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), + "report_ip": os.getenv("BIOYOND_REPORT_IP", "172.21.33.141"), # 报送给 Bioyond 的本机IP地址(留空则自动检测) # 调试模式 "debug_mode": os.getenv("BIOYOND_DEBUG_MODE", "False").lower() == "true", From 5a7845d8ca7b190f7dce9f0411abe8d368e6f57e Mon Sep 17 00:00:00 2001 From: calvincao Date: Thu, 23 Oct 2025 08:34:33 +0800 Subject: [PATCH 009/104] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=AD=E7=9A=84=20report=5Fip=20=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=80=BC=EF=BC=8C=E4=BC=98=E5=8C=96=20bioyond=5Fcell?= =?UTF-8?q?=5Fworkstation.py=20=E4=B8=AD=E7=9A=84=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=A4=9A=E4=B8=AA=E7=93=B6=E5=AD=90=E5=92=8C?= =?UTF-8?q?=E8=BD=BD=E6=9E=B6=E7=B1=BB=E5=9E=8B=E7=9A=84=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=EF=BC=8C=E8=B0=83=E6=95=B4=E4=BB=93=E5=BA=93=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E4=BB=A5=E6=94=AF=E6=8C=81=E6=9B=B4=E7=81=B5=E6=B4=BB=E7=9A=84?= =?UTF-8?q?=E7=89=A9=E6=96=99=E7=AE=A1=E7=90=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bioyond_cell/bioyond_cell_workstation.py | 28 +- .../workstation/bioyond_studio/config.py | 2 +- .../resources/bioyond/bottle_carriers.yaml | 84 +++++ .../registry/resources/bioyond/bottles.yaml | 90 +++++ unilabos/registry/resources/bioyond/deck.yaml | 2 +- unilabos/resources/bioyond/bottle_carriers.py | 348 +++++++++++++++++- unilabos/resources/bioyond/bottles.py | 163 ++++++++ unilabos/resources/bioyond/warehouses.py | 4 +- 8 files changed, 711 insertions(+), 10 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index ef4004e..ac3ea5b 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -80,8 +80,15 @@ class BioyondCellWorkstation(BioyondWorkstation): def process_order_finish_report(self, report_request, used_materials=None): order_code = report_request.data.get("orderCode") + status = report_request.data.get("status") + logger.info(f"report_request: {report_request}") + + logger.info(f"任务完成: {order_code}, status={status}") + + # 记录订单状态码 + if order_code: + self.order_status[order_code] = status - logger.info(f"任务完成: {order_code}, status={report_request.data.get('status')}") self._set_pending_event(order_code) return {"status": "received"} @@ -119,7 +126,18 @@ class BioyondCellWorkstation(BioyondWorkstation): logger.warning(f"{context} 响应中未找到 orderCode,无法跟踪任务完成") return for code in order_codes: - self._wait_for_order_completion(code, timeout=timeout) + finished = self._wait_for_order_completion(code, timeout=timeout) + if finished: + # 检查订单返回码是否为30(正常完成) + status = self.order_status.get(code) + if status == 30 or status == "30": + logger.info(f"订单 {code} 成功完成,状态码: {status}") + else: + logger.warning(f"订单 {code} 完成但状态码异常: {status} (期望: 30, -11=异常停止, -12=人工停止)") + # 清理状态记录 + self.order_status.pop(code, None) + else: + logger.error(f"订单 {code} 等待超时,未收到完成通知") @staticmethod def _extract_order_codes(response: Dict[str, Any]) -> List[str]: @@ -296,7 +314,7 @@ class BioyondCellWorkstation(BioyondWorkstation): def auto_feeding4to3( self, # ★ 修改点:默认模板路径 - xlsx_path: Optional[str] = "unilabos/devices/workstation/bioyond_studio/bioyond_cell/样品导入模板.xlsx", + xlsx_path: Optional[str] = "/Users/calvincao/Desktop/work/uni-lab-all/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/样品导入模板.xlsx", # ---------------- WH4 - 加样头面 (Z=1, 12个点位) ---------------- WH4_x1_y1_z1_1_materialName: str = "", WH4_x1_y1_z1_1_quantity: float = 0.0, WH4_x2_y1_z1_2_materialName: str = "", WH4_x2_y1_z1_2_quantity: float = 0.0, @@ -812,8 +830,8 @@ class BioyondCellWorkstation(BioyondWorkstation): if __name__ == "__main__": ws = BioyondCellWorkstation() logger.info(ws.scheduler_start()) - - logger.info(ws.auto_feeding4to3()) + #TODO:新建入库 + logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 # 使用正斜杠或 Path 对象来指定文件路径 excel_path = Path("unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx") logger.info(ws.create_orders(excel_path)) diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index e2506bc..ed14f7b 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -25,7 +25,7 @@ BIOYOND_FULL_CONFIG = { # HTTP 服务配置 "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "0.0.0.0"), # HTTP服务监听地址(0.0.0.0 表示监听所有网络接口) "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), - "report_ip": os.getenv("BIOYOND_REPORT_IP", "172.21.33.141"), # 报送给 Bioyond 的本机IP地址(留空则自动检测) + "report_ip": os.getenv("BIOYOND_REPORT_IP", "172.21.32.57"), # 报送给 Bioyond 的本机IP地址(留空则自动检测) # 调试模式 "debug_mode": os.getenv("BIOYOND_DEBUG_MODE", "False").lower() == "true", diff --git a/unilabos/registry/resources/bioyond/bottle_carriers.yaml b/unilabos/registry/resources/bioyond/bottle_carriers.yaml index 44e5427..e4d7a48 100644 --- a/unilabos/registry/resources/bioyond/bottle_carriers.yaml +++ b/unilabos/registry/resources/bioyond/bottle_carriers.yaml @@ -46,3 +46,87 @@ BIOYOND_PolymerStation_6VialCarrier: init_param_schema: {} registry_type: resource version: 1.0.0 +BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier + type: pylabrobot + description: BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 +BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier + type: pylabrobot + description: BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 +BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier + type: pylabrobot + description: BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 +BIOYOND_PolymerStation_4x_LargeSolutionBottleCarrier: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_4x_LargeSolutionBottleCarrier + type: pylabrobot + description: BIOYOND_PolymerStation_4x_LargeSolutionBottleCarrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 +BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier + type: pylabrobot + description: BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 +BIOYOND_PolymerStation_AdapterBlock: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_AdapterBlock + type: pylabrobot + description: BIOYOND_PolymerStation_AdapterBlock + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 +BIOYOND_PolymerStation_TipBox: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_TipBox + type: pylabrobot + description: BIOYOND_PolymerStation_TipBox + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 diff --git a/unilabos/registry/resources/bioyond/bottles.yaml b/unilabos/registry/resources/bioyond/bottles.yaml index 55da690..86d5eed 100644 --- a/unilabos/registry/resources/bioyond/bottles.yaml +++ b/unilabos/registry/resources/bioyond/bottles.yaml @@ -48,3 +48,93 @@ BIOYOND_PolymerStation_Solution_Beaker: icon: '' init_param_schema: {} version: 1.0.0 +BIOYOND_PolymerStation_100ml_Liquid_Bottle: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_100ml_Liquid_Bottle + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +BIOYOND_PolymerStation_Liquid_Bottle: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Liquid_Bottle + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +BIOYOND_PolymerStation_High_Viscosity_Liquid_Bottle: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_High_Viscosity_Liquid_Bottle + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +BIOYOND_PolymerStation_Large_Dispense_Head: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Large_Dispense_Head + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +BIOYOND_PolymerStation_5ml_Dispensing_Vial: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_5ml_Dispensing_Vial + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +BIOYOND_PolymerStation_20ml_Dispensing_Vial: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_20ml_Dispensing_Vial + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +BIOYOND_PolymerStation_Small_Solution_Bottle: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Small_Solution_Bottle + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +BIOYOND_PolymerStation_Large_Solution_Bottle: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Large_Solution_Bottle + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +BIOYOND_PolymerStation_Pipette_Tip: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Pipette_Tip + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 diff --git a/unilabos/registry/resources/bioyond/deck.yaml b/unilabos/registry/resources/bioyond/deck.yaml index dd424ea..140dc5f 100644 --- a/unilabos/registry/resources/bioyond/deck.yaml +++ b/unilabos/registry/resources/bioyond/deck.yaml @@ -22,7 +22,7 @@ BIOYOND_PolymerReactionStation_Deck: init_param_schema: {} registry_type: resource version: 1.0.0 -YB_Deck14: +YB_Deck15: category: - deck class: diff --git a/unilabos/resources/bioyond/bottle_carriers.py b/unilabos/resources/bioyond/bottle_carriers.py index f56c2f1..a772fb8 100644 --- a/unilabos/resources/bioyond/bottle_carriers.py +++ b/unilabos/resources/bioyond/bottle_carriers.py @@ -6,7 +6,13 @@ from unilabos.resources.bioyond.bottles import ( BIOYOND_PolymerStation_Solid_Vial, BIOYOND_PolymerStation_Liquid_Vial, BIOYOND_PolymerStation_Solution_Beaker, - BIOYOND_PolymerStation_Reagent_Bottle + BIOYOND_PolymerStation_Reagent_Bottle, + BIOYOND_PolymerStation_5ml_Dispensing_Vial, + BIOYOND_PolymerStation_20ml_Dispensing_Vial, + BIOYOND_PolymerStation_Small_Solution_Bottle, + BIOYOND_PolymerStation_Large_Solution_Bottle, + BIOYOND_PolymerStation_Large_Dispense_Head, + BIOYOND_PolymerStation_Pipette_Tip ) # 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial @@ -274,3 +280,343 @@ def BIOYOND_PolymerStation_1FlaskCarrier(name: str) -> BottleCarrier: carrier.num_items_z = 1 carrier[0] = BIOYOND_PolymerStation_Reagent_Bottle(f"{name}_bottle_1") return carrier + + +def BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier(name: str) -> BottleCarrier: + """5ml分液瓶板 - 2x3布局,6个位置""" + + # 载架尺寸 (mm) + carrier_size_x = 127.8 + carrier_size_y = 85.5 + carrier_size_z = 50.0 + + # 瓶位尺寸 + bottle_diameter = 15.0 + bottle_spacing_x = 42.0 # X方向间距 + bottle_spacing_y = 35.0 # Y方向间距 + + # 计算起始位置 (居中排列) + start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2 + start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2 + + sites = create_ordered_items_2d( + klass=ResourceHolder, + num_items_x=3, + num_items_y=2, + dx=start_x, + dy=start_y, + dz=5.0, + item_dx=bottle_spacing_x, + item_dy=bottle_spacing_y, + size_x=bottle_diameter, + size_y=bottle_diameter, + size_z=carrier_size_z, + ) + for k, v in sites.items(): + v.name = f"{name}_{v.name}" + + carrier = BottleCarrier( + name=name, + size_x=carrier_size_x, + size_y=carrier_size_y, + size_z=carrier_size_z, + sites=sites, + model="BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier", + ) + carrier.num_items_x = 3 + carrier.num_items_y = 2 + carrier.num_items_z = 1 + ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] + for i in range(6): + carrier[i] = BIOYOND_PolymerStation_5ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}") + return carrier + + +def BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier(name: str) -> BottleCarrier: + """20ml分液瓶板 - 2x3布局,6个位置""" + + # 载架尺寸 (mm) + carrier_size_x = 127.8 + carrier_size_y = 85.5 + carrier_size_z = 70.0 + + # 瓶位尺寸 + bottle_diameter = 20.0 + bottle_spacing_x = 42.0 # X方向间距 + bottle_spacing_y = 35.0 # Y方向间距 + + # 计算起始位置 (居中排列) + start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2 + start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2 + + sites = create_ordered_items_2d( + klass=ResourceHolder, + num_items_x=3, + num_items_y=2, + dx=start_x, + dy=start_y, + dz=5.0, + item_dx=bottle_spacing_x, + item_dy=bottle_spacing_y, + size_x=bottle_diameter, + size_y=bottle_diameter, + size_z=carrier_size_z, + ) + for k, v in sites.items(): + v.name = f"{name}_{v.name}" + + carrier = BottleCarrier( + name=name, + size_x=carrier_size_x, + size_y=carrier_size_y, + size_z=carrier_size_z, + sites=sites, + model="BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier", + ) + carrier.num_items_x = 3 + carrier.num_items_y = 2 + carrier.num_items_z = 1 + ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] + for i in range(6): + carrier[i] = BIOYOND_PolymerStation_20ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}") + return carrier + + +def BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier(name: str) -> BottleCarrier: + """配液瓶(小)板 - 2x3布局,6个位置""" + + # 载架尺寸 (mm) + carrier_size_x = 127.8 + carrier_size_y = 85.5 + carrier_size_z = 65.0 + + # 瓶位尺寸 + bottle_diameter = 35.0 + bottle_spacing_x = 42.0 # X方向间距 + bottle_spacing_y = 35.0 # Y方向间距 + + # 计算起始位置 (居中排列) + start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2 + start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2 + + sites = create_ordered_items_2d( + klass=ResourceHolder, + num_items_x=3, + num_items_y=2, + dx=start_x, + dy=start_y, + dz=5.0, + item_dx=bottle_spacing_x, + item_dy=bottle_spacing_y, + size_x=bottle_diameter, + size_y=bottle_diameter, + size_z=carrier_size_z, + ) + for k, v in sites.items(): + v.name = f"{name}_{v.name}" + + carrier = BottleCarrier( + name=name, + size_x=carrier_size_x, + size_y=carrier_size_y, + size_z=carrier_size_z, + sites=sites, + model="BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier", + ) + carrier.num_items_x = 3 + carrier.num_items_y = 2 + carrier.num_items_z = 1 + ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] + for i in range(6): + carrier[i] = BIOYOND_PolymerStation_Small_Solution_Bottle(f"{name}_bottle_{ordering[i]}") + return carrier + + +def BIOYOND_PolymerStation_4x_LargeSolutionBottleCarrier(name: str) -> BottleCarrier: + """配液瓶(大)板 - 2x2布局,4个位置""" + + # 载架尺寸 (mm) + carrier_size_x = 127.8 + carrier_size_y = 85.5 + carrier_size_z = 95.0 + + # 瓶位尺寸 + bottle_diameter = 55.0 + bottle_spacing_x = 60.0 # X方向间距 + bottle_spacing_y = 60.0 # Y方向间距 + + # 计算起始位置 (居中排列) + start_x = (carrier_size_x - (2 - 1) * bottle_spacing_x - bottle_diameter) / 2 + start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2 + + sites = create_ordered_items_2d( + klass=ResourceHolder, + num_items_x=2, + num_items_y=2, + dx=start_x, + dy=start_y, + dz=5.0, + item_dx=bottle_spacing_x, + item_dy=bottle_spacing_y, + size_x=bottle_diameter, + size_y=bottle_diameter, + size_z=carrier_size_z, + ) + for k, v in sites.items(): + v.name = f"{name}_{v.name}" + + carrier = BottleCarrier( + name=name, + size_x=carrier_size_x, + size_y=carrier_size_y, + size_z=carrier_size_z, + sites=sites, + model="BIOYOND_PolymerStation_4x_LargeSolutionBottleCarrier", + ) + carrier.num_items_x = 2 + carrier.num_items_y = 2 + carrier.num_items_z = 1 + ordering = ["A1", "A2", "B1", "B2"] + for i in range(4): + carrier[i] = BIOYOND_PolymerStation_Large_Solution_Bottle(f"{name}_bottle_{ordering[i]}") + return carrier + + +def BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier(name: str) -> BottleCarrier: + """加样头(大)板 - 2x3布局,6个位置""" + + # 载架尺寸 (mm) + carrier_size_x = 127.8 + carrier_size_y = 85.5 + carrier_size_z = 95.0 + + # 瓶位尺寸 + bottle_diameter = 35.0 + bottle_spacing_x = 42.0 # X方向间距 + bottle_spacing_y = 35.0 # Y方向间距 + + # 计算起始位置 (居中排列) + start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2 + start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2 + + sites = create_ordered_items_2d( + klass=ResourceHolder, + num_items_x=3, + num_items_y=2, + dx=start_x, + dy=start_y, + dz=5.0, + item_dx=bottle_spacing_x, + item_dy=bottle_spacing_y, + size_x=bottle_diameter, + size_y=bottle_diameter, + size_z=carrier_size_z, + ) + for k, v in sites.items(): + v.name = f"{name}_{v.name}" + + carrier = BottleCarrier( + name=name, + size_x=carrier_size_x, + size_y=carrier_size_y, + size_z=carrier_size_z, + sites=sites, + model="BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier", + ) + carrier.num_items_x = 3 + carrier.num_items_y = 2 + carrier.num_items_z = 1 + ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] + for i in range(6): + carrier[i] = BIOYOND_PolymerStation_Large_Dispense_Head(f"{name}_head_{ordering[i]}") + return carrier + + +def BIOYOND_PolymerStation_AdapterBlock(name: str) -> BottleCarrier: + """适配器块 - 单个中央位置""" + + # 载架尺寸 (mm) + carrier_size_x = 127.8 + carrier_size_y = 85.5 + carrier_size_z = 30.0 + + # 适配器尺寸 + adapter_diameter = 80.0 + + # 计算中央位置 + center_x = (carrier_size_x - adapter_diameter) / 2 + center_y = (carrier_size_y - adapter_diameter) / 2 + center_z = 0.0 + + carrier = BottleCarrier( + name=name, + size_x=carrier_size_x, + size_y=carrier_size_y, + size_z=carrier_size_z, + sites=create_homogeneous_resources( + klass=ResourceHolder, + locations=[Coordinate(center_x, center_y, center_z)], + resource_size_x=adapter_diameter, + resource_size_y=adapter_diameter, + name_prefix=name, + ), + model="BIOYOND_PolymerStation_AdapterBlock", + ) + carrier.num_items_x = 1 + carrier.num_items_y = 1 + carrier.num_items_z = 1 + # 适配器块本身不包含瓶子,只是一个支撑结构 + return carrier + + +def BIOYOND_PolymerStation_TipBox(name: str) -> BottleCarrier: + """枪头盒 - 8x12布局,96个位置""" + + # 载架尺寸 (mm) + carrier_size_x = 127.8 + carrier_size_y = 85.5 + carrier_size_z = 55.0 + + # 枪头尺寸 + tip_diameter = 10.0 + tip_spacing_x = 9.0 # X方向间距 + tip_spacing_y = 9.0 # Y方向间距 + + # 计算起始位置 (居中排列) + start_x = (carrier_size_x - (12 - 1) * tip_spacing_x - tip_diameter) / 2 + start_y = (carrier_size_y - (8 - 1) * tip_spacing_y - tip_diameter) / 2 + + sites = create_ordered_items_2d( + klass=ResourceHolder, + num_items_x=12, + num_items_y=8, + dx=start_x, + dy=start_y, + dz=5.0, + item_dx=tip_spacing_x, + item_dy=tip_spacing_y, + size_x=tip_diameter, + size_y=tip_diameter, + size_z=carrier_size_z, + ) + for k, v in sites.items(): + v.name = f"{name}_{v.name}" + + carrier = BottleCarrier( + name=name, + size_x=carrier_size_x, + size_y=carrier_size_y, + size_z=carrier_size_z, + sites=sites, + model="BIOYOND_PolymerStation_TipBox", + ) + carrier.num_items_x = 12 + carrier.num_items_y = 8 + carrier.num_items_z = 1 + # 创建96个枪头 + for i in range(96): + row = chr(65 + i // 12) # A-H + col = (i % 12) + 1 # 1-12 + carrier[i] = BIOYOND_PolymerStation_Pipette_Tip(f"{name}_tip_{row}{col}") + return carrier + diff --git a/unilabos/resources/bioyond/bottles.py b/unilabos/resources/bioyond/bottles.py index 7d241a7..22fc6b7 100644 --- a/unilabos/resources/bioyond/bottles.py +++ b/unilabos/resources/bioyond/bottles.py @@ -90,3 +90,166 @@ def BIOYOND_PolymerStation_Reagent_Bottle( barcode=barcode, model="BIOYOND_PolymerStation_Reagent_Bottle", ) + + +def BIOYOND_PolymerStation_100ml_Liquid_Bottle( + name: str, + diameter: float = 50.0, + height: float = 80.0, + max_volume: float = 100000.0, # 100mL + barcode: str = None, +) -> Bottle: + """创建100ml液体瓶""" + return Bottle( + name=name, + diameter=diameter, + height=height, + max_volume=max_volume, + barcode=barcode, + model="BIOYOND_PolymerStation_100ml_Liquid_Bottle", + ) + + +def BIOYOND_PolymerStation_Liquid_Bottle( + name: str, + diameter: float = 40.0, + height: float = 70.0, + max_volume: float = 50000.0, # 50mL + barcode: str = None, +) -> Bottle: + """创建液体瓶""" + return Bottle( + name=name, + diameter=diameter, + height=height, + max_volume=max_volume, + barcode=barcode, + model="BIOYOND_PolymerStation_Liquid_Bottle", + ) + + +def BIOYOND_PolymerStation_High_Viscosity_Liquid_Bottle( + name: str, + diameter: float = 45.0, + height: float = 75.0, + max_volume: float = 60000.0, # 60mL + barcode: str = None, +) -> Bottle: + """创建高粘液瓶""" + return Bottle( + name=name, + diameter=diameter, + height=height, + max_volume=max_volume, + barcode=barcode, + model="BIOYOND_PolymerStation_High_Viscosity_Liquid_Bottle", + ) + + +def BIOYOND_PolymerStation_Large_Dispense_Head( + name: str, + diameter: float = 35.0, + height: float = 90.0, + max_volume: float = 50000.0, # 50mL + barcode: str = None, +) -> Bottle: + """创建加样头(大)""" + return Bottle( + name=name, + diameter=diameter, + height=height, + max_volume=max_volume, + barcode=barcode, + model="BIOYOND_PolymerStation_Large_Dispense_Head", + ) + + +def BIOYOND_PolymerStation_5ml_Dispensing_Vial( + name: str, + diameter: float = 15.0, + height: float = 45.0, + max_volume: float = 5000.0, # 5mL + barcode: str = None, +) -> Bottle: + """创建5ml分液瓶""" + return Bottle( + name=name, + diameter=diameter, + height=height, + max_volume=max_volume, + barcode=barcode, + model="BIOYOND_PolymerStation_5ml_Dispensing_Vial", + ) + + +def BIOYOND_PolymerStation_20ml_Dispensing_Vial( + name: str, + diameter: float = 20.0, + height: float = 65.0, + max_volume: float = 20000.0, # 20mL + barcode: str = None, +) -> Bottle: + """创建20ml分液瓶""" + return Bottle( + name=name, + diameter=diameter, + height=height, + max_volume=max_volume, + barcode=barcode, + model="BIOYOND_PolymerStation_20ml_Dispensing_Vial", + ) + + +def BIOYOND_PolymerStation_Small_Solution_Bottle( + name: str, + diameter: float = 35.0, + height: float = 60.0, + max_volume: float = 40000.0, # 40mL + barcode: str = None, +) -> Bottle: + """创建配液瓶(小)""" + return Bottle( + name=name, + diameter=diameter, + height=height, + max_volume=max_volume, + barcode=barcode, + model="BIOYOND_PolymerStation_Small_Solution_Bottle", + ) + + +def BIOYOND_PolymerStation_Large_Solution_Bottle( + name: str, + diameter: float = 55.0, + height: float = 90.0, + max_volume: float = 150000.0, # 150mL + barcode: str = None, +) -> Bottle: + """创建配液瓶(大)""" + return Bottle( + name=name, + diameter=diameter, + height=height, + max_volume=max_volume, + barcode=barcode, + model="BIOYOND_PolymerStation_Large_Solution_Bottle", + ) + + +def BIOYOND_PolymerStation_Pipette_Tip( + name: str, + diameter: float = 10.0, + height: float = 50.0, + max_volume: float = 1000.0, # 1mL + barcode: str = None, +) -> Bottle: + """创建枪头""" + return Bottle( + name=name, + diameter=diameter, + height=height, + max_volume=max_volume, + barcode=barcode, + model="BIOYOND_PolymerStation_Pipette_Tip", + ) + diff --git a/unilabos/resources/bioyond/warehouses.py b/unilabos/resources/bioyond/warehouses.py index 477e8ae..22be38b 100644 --- a/unilabos/resources/bioyond/warehouses.py +++ b/unilabos/resources/bioyond/warehouses.py @@ -39,9 +39,9 @@ def bioyond_warehouse_1x2x2(name: str) -> WareHouse: """创建BioYond 4x1x4仓库""" return warehouse_factory( name=name, - num_items_x=1, + num_items_x=2, num_items_y=2, - num_items_z=2, + num_items_z=1, dx=10.0, dy=10.0, dz=10.0, From d3d8ba65007549e69c693fc7119dfc4c2b0f9c9b Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Thu, 23 Oct 2025 15:32:36 +0800 Subject: [PATCH 010/104] =?UTF-8?q?fix(yb3):=20=E7=89=A9=E6=96=99=E5=90=8D?= =?UTF-8?q?=E7=A7=B0=E4=B8=8E=E6=A8=A1=E5=9E=8B=E5=AF=B9=E9=BD=90=EF=BC=9B?= =?UTF-8?q?YAML=20=E5=8E=BB=E6=8E=89=20BIOYOND=5FPolymerStation=5F=20?= =?UTF-8?q?=E5=89=8D=E7=BC=80=EF=BC=9B=E4=BF=AE=E5=A4=8D=206StockCarrier?= =?UTF-8?q?=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/bioyond/bottle_carriers.yaml | 44 +++++++++---------- .../registry/resources/bioyond/bottles.yaml | 28 ++++++------ unilabos/resources/bioyond/bottle_carriers.py | 26 +++++------ unilabos/resources/bioyond/bottles.py | 28 ++++++------ 4 files changed, 63 insertions(+), 63 deletions(-) diff --git a/unilabos/registry/resources/bioyond/bottle_carriers.yaml b/unilabos/registry/resources/bioyond/bottle_carriers.yaml index e4d7a48..5889bcc 100644 --- a/unilabos/registry/resources/bioyond/bottle_carriers.yaml +++ b/unilabos/registry/resources/bioyond/bottle_carriers.yaml @@ -1,130 +1,130 @@ -BIOYOND_PolymerStation_1BottleCarrier: +1BottleCarrier: category: - bottle_carriers class: module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_1BottleCarrier type: pylabrobot - description: BIOYOND_PolymerStation_1BottleCarrier + description: 1BottleCarrier handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 -BIOYOND_PolymerStation_1FlaskCarrier: +1FlaskCarrier: category: - bottle_carriers class: module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_1FlaskCarrier type: pylabrobot - description: BIOYOND_PolymerStation_1FlaskCarrier + description: 1FlaskCarrier handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 -BIOYOND_PolymerStation_6StockCarrier: +6StockCarrier: category: - bottle_carriers class: module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6StockCarrier type: pylabrobot - description: BIOYOND_PolymerStation_6StockCarrier + description: 6StockCarrier handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 -BIOYOND_PolymerStation_6VialCarrier: +6VialCarrier: category: - bottle_carriers class: module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6VialCarrier type: pylabrobot - description: BIOYOND_PolymerStation_6VialCarrier + description: 6VialCarrier handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 -BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier: +6x5ml_DispensingVialCarrier: category: - yb3 class: module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier type: pylabrobot - description: BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier + description: 6x5ml_DispensingVialCarrier handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 -BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier: +6x20ml_DispensingVialCarrier: category: - yb3 class: module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier type: pylabrobot - description: BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier + description: 6x20ml_DispensingVialCarrier handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 -BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier: +6x_SmallSolutionBottleCarrier: category: - yb3 class: module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier type: pylabrobot - description: BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier + description: 6x_SmallSolutionBottleCarrier handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 -BIOYOND_PolymerStation_4x_LargeSolutionBottleCarrier: +4x_LargeSolutionBottleCarrier: category: - yb3 class: module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_4x_LargeSolutionBottleCarrier type: pylabrobot - description: BIOYOND_PolymerStation_4x_LargeSolutionBottleCarrier + description: 4x_LargeSolutionBottleCarrier handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 -BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier: +6x_LargeDispenseHeadCarrier: category: - yb3 class: module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier type: pylabrobot - description: BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier + description: 6x_LargeDispenseHeadCarrier handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 -BIOYOND_PolymerStation_AdapterBlock: +AdapterBlock: category: - yb3 class: module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_AdapterBlock type: pylabrobot - description: BIOYOND_PolymerStation_AdapterBlock + description: AdapterBlock handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 -BIOYOND_PolymerStation_TipBox: +TipBox: category: - yb3 class: module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_TipBox type: pylabrobot - description: BIOYOND_PolymerStation_TipBox + description: TipBox handles: [] icon: '' init_param_schema: {} diff --git a/unilabos/registry/resources/bioyond/bottles.yaml b/unilabos/registry/resources/bioyond/bottles.yaml index 86d5eed..8d71f28 100644 --- a/unilabos/registry/resources/bioyond/bottles.yaml +++ b/unilabos/registry/resources/bioyond/bottles.yaml @@ -1,4 +1,4 @@ -BIOYOND_PolymerStation_Liquid_Vial: +Liquid_Vial: category: - bottles class: @@ -8,7 +8,7 @@ BIOYOND_PolymerStation_Liquid_Vial: icon: '' init_param_schema: {} version: 1.0.0 -BIOYOND_PolymerStation_Reagent_Bottle: +Reagent_Bottle: category: - bottles class: @@ -18,7 +18,7 @@ BIOYOND_PolymerStation_Reagent_Bottle: icon: '' init_param_schema: {} version: 1.0.0 -BIOYOND_PolymerStation_Solid_Stock: +Solid_Stock: category: - bottles class: @@ -28,7 +28,7 @@ BIOYOND_PolymerStation_Solid_Stock: icon: '' init_param_schema: {} version: 1.0.0 -BIOYOND_PolymerStation_Solid_Vial: +Solid_Vial: category: - bottles class: @@ -38,7 +38,7 @@ BIOYOND_PolymerStation_Solid_Vial: icon: '' init_param_schema: {} version: 1.0.0 -BIOYOND_PolymerStation_Solution_Beaker: +Solution_Beaker: category: - bottles class: @@ -48,7 +48,7 @@ BIOYOND_PolymerStation_Solution_Beaker: icon: '' init_param_schema: {} version: 1.0.0 -BIOYOND_PolymerStation_100ml_Liquid_Bottle: +100ml_Liquid_Bottle: category: - yb3 class: @@ -58,7 +58,7 @@ BIOYOND_PolymerStation_100ml_Liquid_Bottle: icon: '' init_param_schema: {} version: 1.0.0 -BIOYOND_PolymerStation_Liquid_Bottle: +Liquid_Bottle: category: - yb3 class: @@ -68,7 +68,7 @@ BIOYOND_PolymerStation_Liquid_Bottle: icon: '' init_param_schema: {} version: 1.0.0 -BIOYOND_PolymerStation_High_Viscosity_Liquid_Bottle: +High_Viscosity_Liquid_Bottle: category: - yb3 class: @@ -78,7 +78,7 @@ BIOYOND_PolymerStation_High_Viscosity_Liquid_Bottle: icon: '' init_param_schema: {} version: 1.0.0 -BIOYOND_PolymerStation_Large_Dispense_Head: +Large_Dispense_Head: category: - yb3 class: @@ -88,7 +88,7 @@ BIOYOND_PolymerStation_Large_Dispense_Head: icon: '' init_param_schema: {} version: 1.0.0 -BIOYOND_PolymerStation_5ml_Dispensing_Vial: +5ml_Dispensing_Vial: category: - yb3 class: @@ -98,7 +98,7 @@ BIOYOND_PolymerStation_5ml_Dispensing_Vial: icon: '' init_param_schema: {} version: 1.0.0 -BIOYOND_PolymerStation_20ml_Dispensing_Vial: +20ml_Dispensing_Vial: category: - yb3 class: @@ -108,7 +108,7 @@ BIOYOND_PolymerStation_20ml_Dispensing_Vial: icon: '' init_param_schema: {} version: 1.0.0 -BIOYOND_PolymerStation_Small_Solution_Bottle: +Small_Solution_Bottle: category: - yb3 class: @@ -118,7 +118,7 @@ BIOYOND_PolymerStation_Small_Solution_Bottle: icon: '' init_param_schema: {} version: 1.0.0 -BIOYOND_PolymerStation_Large_Solution_Bottle: +Large_Solution_Bottle: category: - yb3 class: @@ -128,7 +128,7 @@ BIOYOND_PolymerStation_Large_Solution_Bottle: icon: '' init_param_schema: {} version: 1.0.0 -BIOYOND_PolymerStation_Pipette_Tip: +Pipette_Tip: category: - yb3 class: diff --git a/unilabos/resources/bioyond/bottle_carriers.py b/unilabos/resources/bioyond/bottle_carriers.py index a772fb8..87f84df 100644 --- a/unilabos/resources/bioyond/bottle_carriers.py +++ b/unilabos/resources/bioyond/bottle_carriers.py @@ -57,7 +57,7 @@ def BIOYOND_Electrolyte_6VialCarrier(name: str) -> BottleCarrier: size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="BIOYOND_Electrolyte_6VialCarrier", + model="Electrolyte_6VialCarrier", ) carrier.num_items_x = 3 carrier.num_items_y = 2 @@ -95,7 +95,7 @@ def BIOYOND_Electrolyte_1BottleCarrier(name: str) -> BottleCarrier: resource_size_y=beaker_diameter, name_prefix=name, ), - model="BIOYOND_Electrolyte_1BottleCarrier", + model="Electrolyte_1BottleCarrier", ) carrier.num_items_x = 1 carrier.num_items_y = 1 @@ -144,7 +144,7 @@ def BIOYOND_PolymerStation_6StockCarrier(name: str) -> BottleCarrier: size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="BIOYOND_PolymerStation_6VialCarrier", + model="6StockCarrier", ) carrier.num_items_x = 3 carrier.num_items_y = 2 @@ -195,7 +195,7 @@ def BIOYOND_PolymerStation_6VialCarrier(name: str) -> BottleCarrier: size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="BIOYOND_PolymerStation_6VialCarrier", + model="6VialCarrier", ) carrier.num_items_x = 3 carrier.num_items_y = 2 @@ -236,7 +236,7 @@ def BIOYOND_PolymerStation_1BottleCarrier(name: str) -> BottleCarrier: resource_size_y=beaker_diameter, name_prefix=name, ), - model="BIOYOND_PolymerStation_1BottleCarrier", + model="1BottleCarrier", ) carrier.num_items_x = 1 carrier.num_items_y = 1 @@ -273,7 +273,7 @@ def BIOYOND_PolymerStation_1FlaskCarrier(name: str) -> BottleCarrier: resource_size_y=beaker_diameter, name_prefix=name, ), - model="BIOYOND_PolymerStation_1FlaskCarrier", + model="1FlaskCarrier", ) carrier.num_items_x = 1 carrier.num_items_y = 1 @@ -321,7 +321,7 @@ def BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier(name: str) -> BottleCarri size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier", + model="6x5ml_DispensingVialCarrier", ) carrier.num_items_x = 3 carrier.num_items_y = 2 @@ -371,7 +371,7 @@ def BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier(name: str) -> BottleCarr size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier", + model="6x20ml_DispensingVialCarrier", ) carrier.num_items_x = 3 carrier.num_items_y = 2 @@ -421,7 +421,7 @@ def BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier(name: str) -> BottleCar size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier", + model="6x_SmallSolutionBottleCarrier", ) carrier.num_items_x = 3 carrier.num_items_y = 2 @@ -471,7 +471,7 @@ def BIOYOND_PolymerStation_4x_LargeSolutionBottleCarrier(name: str) -> BottleCar size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="BIOYOND_PolymerStation_4x_LargeSolutionBottleCarrier", + model="4x_LargeSolutionBottleCarrier", ) carrier.num_items_x = 2 carrier.num_items_y = 2 @@ -521,7 +521,7 @@ def BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier(name: str) -> BottleCarri size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier", + model="6x_LargeDispenseHeadCarrier", ) carrier.num_items_x = 3 carrier.num_items_y = 2 @@ -560,7 +560,7 @@ def BIOYOND_PolymerStation_AdapterBlock(name: str) -> BottleCarrier: resource_size_y=adapter_diameter, name_prefix=name, ), - model="BIOYOND_PolymerStation_AdapterBlock", + model="AdapterBlock", ) carrier.num_items_x = 1 carrier.num_items_y = 1 @@ -608,7 +608,7 @@ def BIOYOND_PolymerStation_TipBox(name: str) -> BottleCarrier: size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="BIOYOND_PolymerStation_TipBox", + model="TipBox", ) carrier.num_items_x = 12 carrier.num_items_y = 8 diff --git a/unilabos/resources/bioyond/bottles.py b/unilabos/resources/bioyond/bottles.py index 22fc6b7..b5fb087 100644 --- a/unilabos/resources/bioyond/bottles.py +++ b/unilabos/resources/bioyond/bottles.py @@ -16,7 +16,7 @@ def BIOYOND_PolymerStation_Solid_Stock( height=height, max_volume=max_volume, barcode=barcode, - model="BIOYOND_PolymerStation_Solid_Stock", + model="Solid_Stock", ) @@ -34,7 +34,7 @@ def BIOYOND_PolymerStation_Solid_Vial( height=height, max_volume=max_volume, barcode=barcode, - model="BIOYOND_PolymerStation_Solid_Vial", + model="Solid_Vial", ) @@ -52,7 +52,7 @@ def BIOYOND_PolymerStation_Liquid_Vial( height=height, max_volume=max_volume, barcode=barcode, - model="BIOYOND_PolymerStation_Liquid_Vial", + model="Liquid_Vial", ) @@ -70,7 +70,7 @@ def BIOYOND_PolymerStation_Solution_Beaker( height=height, max_volume=max_volume, barcode=barcode, - model="BIOYOND_PolymerStation_Solution_Beaker", + model="Solution_Beaker", ) @@ -88,7 +88,7 @@ def BIOYOND_PolymerStation_Reagent_Bottle( height=height, max_volume=max_volume, barcode=barcode, - model="BIOYOND_PolymerStation_Reagent_Bottle", + model="Reagent_Bottle", ) @@ -106,7 +106,7 @@ def BIOYOND_PolymerStation_100ml_Liquid_Bottle( height=height, max_volume=max_volume, barcode=barcode, - model="BIOYOND_PolymerStation_100ml_Liquid_Bottle", + model="100ml_Liquid_Bottle", ) @@ -124,7 +124,7 @@ def BIOYOND_PolymerStation_Liquid_Bottle( height=height, max_volume=max_volume, barcode=barcode, - model="BIOYOND_PolymerStation_Liquid_Bottle", + model="Liquid_Bottle", ) @@ -142,7 +142,7 @@ def BIOYOND_PolymerStation_High_Viscosity_Liquid_Bottle( height=height, max_volume=max_volume, barcode=barcode, - model="BIOYOND_PolymerStation_High_Viscosity_Liquid_Bottle", + model="High_Viscosity_Liquid_Bottle", ) @@ -160,7 +160,7 @@ def BIOYOND_PolymerStation_Large_Dispense_Head( height=height, max_volume=max_volume, barcode=barcode, - model="BIOYOND_PolymerStation_Large_Dispense_Head", + model="Large_Dispense_Head", ) @@ -178,7 +178,7 @@ def BIOYOND_PolymerStation_5ml_Dispensing_Vial( height=height, max_volume=max_volume, barcode=barcode, - model="BIOYOND_PolymerStation_5ml_Dispensing_Vial", + model="5ml_Dispensing_Vial", ) @@ -196,7 +196,7 @@ def BIOYOND_PolymerStation_20ml_Dispensing_Vial( height=height, max_volume=max_volume, barcode=barcode, - model="BIOYOND_PolymerStation_20ml_Dispensing_Vial", + model="20ml_Dispensing_Vial", ) @@ -214,7 +214,7 @@ def BIOYOND_PolymerStation_Small_Solution_Bottle( height=height, max_volume=max_volume, barcode=barcode, - model="BIOYOND_PolymerStation_Small_Solution_Bottle", + model="Small_Solution_Bottle", ) @@ -232,7 +232,7 @@ def BIOYOND_PolymerStation_Large_Solution_Bottle( height=height, max_volume=max_volume, barcode=barcode, - model="BIOYOND_PolymerStation_Large_Solution_Bottle", + model="Large_Solution_Bottle", ) @@ -250,6 +250,6 @@ def BIOYOND_PolymerStation_Pipette_Tip( height=height, max_volume=max_volume, barcode=barcode, - model="BIOYOND_PolymerStation_Pipette_Tip", + model="Pipette_Tip", ) From 1af6ffafc675c3de4a2fcd7d32ac7f3b2e02e1d3 Mon Sep 17 00:00:00 2001 From: calvincao Date: Thu, 23 Oct 2025 17:32:08 +0800 Subject: [PATCH 011/104] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E5=9B=BA=E4=BD=93=E7=89=A9=E6=96=99=E5=92=8C?= =?UTF-8?q?=E4=BB=8ECSV=E6=96=87=E4=BB=B6=E5=85=A5=E5=BA=93=E7=9A=84?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=9B=E6=9B=B4=E6=96=B0=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=AD=E7=9A=84=20report=5Fip=20=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=80=BC=EF=BC=9B=E6=96=B0=E5=A2=9E=20solid=5Fmateria?= =?UTF-8?q?ls.csv=20=E6=96=87=E4=BB=B6=E4=BB=A5=E6=94=AF=E6=8C=81=E7=89=A9?= =?UTF-8?q?=E6=96=99=E5=90=8D=E7=A7=B0=E5=AF=BC=E5=85=A5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bioyond_cell/bioyond_cell_workstation.py | 211 +++++++++++++++++- .../bioyond_cell/solid_materials.csv | 7 + .../workstation/bioyond_studio/config.py | 27 ++- 3 files changed, 243 insertions(+), 2 deletions(-) create mode 100644 unilabos/devices/workstation/bioyond_studio/bioyond_cell/solid_materials.csv diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index ac3ea5b..4a343e7 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -825,12 +825,221 @@ class BioyondCellWorkstation(BioyondWorkstation): logger.warning("超时未找到成功的物料转移任务") return False + def create_solid_materials(self, material_names: List[str], type_id: str = "3a190ca0-b2f6-9aeb-8067-547e72c11469") -> List[Dict[str, Any]]: + """ + 批量创建固体物料 + + Args: + material_names: 物料名称列表 + type_id: 物料类型ID(默认为固体物料类型) + + Returns: + 创建的物料列表,每个元素包含物料信息和ID + """ + created_materials = [] + total = len(material_names) + + for i, name in enumerate(material_names, 1): + # 根据接口文档构建完整的请求体 + material_data = { + "typeId": type_id, + "name": name, + "unit": "g", # 添加单位 + "quantity": 1, # 添加数量(默认1) + "parameters": "" # 参数字段(空字符串表示无参数) + } + + logger.info(f"正在创建第 {i}/{total} 个固体物料: {name}") + result = self._post_lims("/api/lims/storage/material", material_data) + + if result and result.get("code") == 1: + # data 字段可能是字符串(物料ID)或字典(包含id字段) + data = result.get("data") + if isinstance(data, str): + # data 直接是物料ID字符串 + material_id = data + elif isinstance(data, dict): + # data 是字典,包含id字段 + material_id = data.get("id") + else: + material_id = None + + if material_id: + created_materials.append({ + "name": name, + "materialId": material_id, + "typeId": type_id + }) + logger.info(f"✓ 成功创建物料: {name}, ID: {material_id}") + else: + logger.error(f"✗ 创建物料失败: {name}, 未返回ID") + logger.error(f" 响应数据: {result}") + else: + error_msg = result.get("error") or result.get("message", "未知错误") + logger.error(f"✗ 创建物料失败: {name}") + logger.error(f" 错误信息: {error_msg}") + logger.error(f" 完整响应: {result}") + + # 避免请求过快 + time.sleep(0.3) + + logger.info(f"物料创建完成,成功创建 {len(created_materials)}/{total} 个固体物料") + return created_materials + + def create_and_inbound_materials_from_csv( + self, + csv_path: str = "solid_materials.csv", + type_id: str = "3a190ca0-b2f6-9aeb-8067-547e72c11469", + warehouse_name: str = "粉末加样头堆栈" + ) -> Dict[str, Any]: + """ + 从CSV文件读取物料列表,创建物料并批量入库到指定堆栈 + + Args: + csv_path: CSV文件路径 + type_id: 物料类型ID(默认为固体物料类型) + warehouse_name: 仓库名称(默认为"粉末加样头堆栈") + + Returns: + 包含执行结果的字典 + """ + logger.info("=" * 60) + logger.info(f"开始执行:从CSV读取物料列表并批量创建入库到 {warehouse_name}") + logger.info("=" * 60) + + # 从配置中获取位置ID列表 + warehouse_mapping = self.bioyond_config.get("warehouse_mapping", WAREHOUSE_MAPPING) + + if warehouse_name not in warehouse_mapping: + error_msg = f"配置中未找到仓库: {warehouse_name}" + logger.error(error_msg) + logger.info(f"可用的仓库: {list(warehouse_mapping.keys())}") + return {"success": False, "error": error_msg} + + warehouse_config = warehouse_mapping[warehouse_name] + site_uuids = warehouse_config.get("site_uuids", {}) + + if not site_uuids: + error_msg = f"仓库 {warehouse_name} 没有配置位置" + logger.error(error_msg) + return {"success": False, "error": error_msg} + + # 按顺序获取位置ID(A01, B01, C01...) + all_location_ids = [] + position_names = [] + for key in sorted(site_uuids.keys()): + all_location_ids.append(site_uuids[key]) + position_names.append(key) + + logger.info(f"✓ 从配置文件加载 {len(all_location_ids)} 个位置") + logger.info(f" 仓库: {warehouse_name}") + logger.info(f" 位置范围: {position_names[0]} ~ {position_names[-1]}") + + # 读取CSV文件 + csv_file_path = Path(csv_path) + material_names = [] + + try: + df_materials = pd.read_csv(csv_file_path) + if 'material_name' in df_materials.columns: + material_names = df_materials['material_name'].dropna().astype(str).str.strip().tolist() + logger.info(f"✓ 成功从CSV文件读取 {len(material_names)} 个物料名称") + logger.info(f" 文件路径: {csv_file_path}") + else: + logger.error(f"✗ CSV文件缺少 'material_name' 列") + return {"success": False, "error": "CSV文件缺少 'material_name' 列"} + except FileNotFoundError: + logger.error(f"✗ 未找到CSV文件: {csv_file_path}") + logger.info("请创建CSV文件,格式:") + logger.info(" material_name") + logger.info(" LiPF6") + logger.info(" LiDFOB") + logger.info(" ...") + return {"success": False, "error": f"未找到CSV文件: {csv_file_path}"} + except Exception as e: + logger.error(f"✗ 读取CSV文件失败: {e}") + return {"success": False, "error": f"读取CSV文件失败: {e}"} + + if not material_names: + logger.error("CSV文件中没有有效的物料名称") + return {"success": False, "error": "CSV文件中没有有效的物料名称"} + + # 检查物料数量 + if len(material_names) > len(all_location_ids): + logger.warning(f"物料数量({len(material_names)})超过可用位置数量({len(all_location_ids)})!") + logger.warning(f"将仅创建前 {len(all_location_ids)} 个物料") + material_names = material_names[:len(all_location_ids)] + + # 准备位置信息 + location_ids = all_location_ids[:len(material_names)] + selected_positions = position_names[:len(material_names)] + + # 步骤1: 创建固体物料 + logger.info(f"\n【步骤1/2】创建 {len(material_names)} 个固体物料...") + logger.info(f"物料类型ID: {type_id}") + logger.info(f"物料列表: {', '.join(material_names)}") + + created_materials = self.create_solid_materials( + material_names=material_names, + type_id=type_id + ) + + if len(created_materials) != len(material_names): + logger.warning(f"创建的物料数量({len(created_materials)})与计划数量({len(material_names)})不匹配!") + logger.warning("将仅对成功创建的物料进行入库操作") + + if not created_materials: + logger.error("没有成功创建任何物料") + return {"success": False, "error": "没有成功创建任何物料"} + + # 步骤2: 批量入库到指定位置 + logger.info(f"\n【步骤2/2】批量入库物料到 {warehouse_name}...") + inbound_items = [] + + for idx, material in enumerate(created_materials): + if idx < len(location_ids): + inbound_items.append({ + "materialId": material["materialId"], + "locationId": location_ids[idx] + }) + logger.info(f" - {material['name']} (ID: {material['materialId'][:8]}...) → 位置 {selected_positions[idx]}") + + logger.info(f"\n正在执行批量入库,共 {len(inbound_items)} 条记录...") + result = self.storage_batch_inbound(inbound_items) + + if result.get("code") == 1: + logger.info(f"✓ 批量入库成功!") + logger.info(f" 响应数据: {result.get('data', {})}") + else: + logger.error(f"✗ 批量入库失败!") + logger.error(f" 响应: {result}") + + logger.info("\n" + "=" * 60) + logger.info("固体物料创建和入库流程完成") + logger.info("=" * 60 + "\n") + + return { + "success": result.get("code") == 1, + "created_materials": created_materials, + "inbound_result": result, + "total_created": len(created_materials), + "total_inbound": len(inbound_items), + "warehouse": warehouse_name, + "positions": selected_positions + } + # -------------------------------- + + if __name__ == "__main__": ws = BioyondCellWorkstation() logger.info(ws.scheduler_start()) - #TODO:新建入库 + + # 从CSV文件读取物料列表并批量创建入库 + result = ws.create_and_inbound_materials_from_csv() + + # 继续后续流程 logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 # 使用正斜杠或 Path 对象来指定文件路径 excel_path = Path("unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx") diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/solid_materials.csv b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/solid_materials.csv new file mode 100644 index 0000000..8db9a5c --- /dev/null +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/solid_materials.csv @@ -0,0 +1,7 @@ +material_name +LiPF6 +LiDFOB +DTD +LiFSI +LiPO2F2 + diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index ed14f7b..9294300 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -25,7 +25,7 @@ BIOYOND_FULL_CONFIG = { # HTTP 服务配置 "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "0.0.0.0"), # HTTP服务监听地址(0.0.0.0 表示监听所有网络接口) "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), - "report_ip": os.getenv("BIOYOND_REPORT_IP", "172.21.32.57"), # 报送给 Bioyond 的本机IP地址(留空则自动检测) + "report_ip": os.getenv("BIOYOND_REPORT_IP", "172.21.32.172"), # 报送给 Bioyond 的本机IP地址(留空则自动检测) # 调试模式 "debug_mode": os.getenv("BIOYOND_DEBUG_MODE", "False").lower() == "true", @@ -112,6 +112,31 @@ WAREHOUSE_MAPPING = { "B3": "3a14198c-c2d0-725e-523d-34c037ac2440", "B4": "3a14198c-c2d0-efce-0939-69ca5a7dfd39" } + }, + "粉末加样头堆栈": { + "uuid": "", + "site_uuids": { + "A01": "3a19da56-1379-20c8-5886-f7c4fbcb5733", + "B01": "3a19da56-1379-2424-d751-fe6e94cef938", + "C01": "3a19da56-1379-271c-03e3-6bdb590e395e", + "D01": "3a19da56-1379-277f-2b1b-0d11f7cf92c6", + "E01": "3a19da56-1379-2f1c-a15b-e01db90eb39a", + "F01": "3a19da56-1379-3fa1-846b-088158ac0b3d", + "G01": "3a19da56-1379-5aeb-d0cd-d3b4609d66e1", + "H01": "3a19da56-1379-6077-8258-bdc036870b78", + "I01": "3a19da56-1379-863b-a120-f606baf04617", + "J01": "3a19da56-1379-8a74-74e5-35a9b41d4fd5", + "K01": "3a19da56-1379-b270-b7af-f18773918abe", + "L01": "3a19da56-1379-ba54-6d78-fd770a671ffc", + "M01": "3a19da56-1379-c22d-c96f-0ceb5eb54a04", + "N01": "3a19da56-1379-d64e-c6c5-c72ea4829888", + "O01": "3a19da56-1379-d887-1a3c-6f9cce90f90e", + "P01": "3a19da56-1379-e77d-0e65-7463b238a3b9", + "Q01": "3a19da56-1379-edf6-1472-802ddb628774", + "R01": "3a19da56-1379-f281-0273-e0ef78f0fd97", + "S01": "3a19da56-1379-f924-7f68-df1fa51489f4", + "T01": "3a19da56-1379-ff7c-1745-07e200b44ce2" + } } } From 9e850d8a81d1c34c5c175d508f7d18e80ff58d12 Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Thu, 23 Oct 2025 17:42:10 +0800 Subject: [PATCH 012/104] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=BD=BD=E6=9E=B6?= =?UTF-8?q?=E7=BD=91=E6=A0=BC=E5=B8=83=E5=B1=80=EF=BC=9A5ml/20ml/=E9=85=8D?= =?UTF-8?q?=E6=B6=B2=E7=93=B6(=E5=B0=8F)=E6=9D=BF=E6=94=B9=E4=B8=BA4x2?= =?UTF-8?q?=EF=BC=8C=E5=8A=A0=E6=A0=B7=E5=A4=B4(=E5=A4=A7)=E6=9D=BF?= =?UTF-8?q?=E6=94=B9=E4=B8=BA1x1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- unilabos/resources/bioyond/bottle_carriers.py | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/unilabos/resources/bioyond/bottle_carriers.py b/unilabos/resources/bioyond/bottle_carriers.py index 87f84df..ab0b656 100644 --- a/unilabos/resources/bioyond/bottle_carriers.py +++ b/unilabos/resources/bioyond/bottle_carriers.py @@ -283,7 +283,7 @@ def BIOYOND_PolymerStation_1FlaskCarrier(name: str) -> BottleCarrier: def BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier(name: str) -> BottleCarrier: - """5ml分液瓶板 - 2x3布局,6个位置""" + """5ml分液瓶板 - 4x2布局,8个位置""" # 载架尺寸 (mm) carrier_size_x = 127.8 @@ -296,12 +296,12 @@ def BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier(name: str) -> BottleCarri bottle_spacing_y = 35.0 # Y方向间距 # 计算起始位置 (居中排列) - start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2 + start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2 start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2 sites = create_ordered_items_2d( klass=ResourceHolder, - num_items_x=3, + num_items_x=4, num_items_y=2, dx=start_x, dy=start_y, @@ -323,17 +323,17 @@ def BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier(name: str) -> BottleCarri sites=sites, model="6x5ml_DispensingVialCarrier", ) - carrier.num_items_x = 3 + carrier.num_items_x = 4 carrier.num_items_y = 2 carrier.num_items_z = 1 - ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] - for i in range(6): + ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"] + for i in range(8): carrier[i] = BIOYOND_PolymerStation_5ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}") return carrier def BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier(name: str) -> BottleCarrier: - """20ml分液瓶板 - 2x3布局,6个位置""" + """20ml分液瓶板 - 4x2布局,8个位置""" # 载架尺寸 (mm) carrier_size_x = 127.8 @@ -346,12 +346,12 @@ def BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier(name: str) -> BottleCarr bottle_spacing_y = 35.0 # Y方向间距 # 计算起始位置 (居中排列) - start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2 + start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2 start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2 sites = create_ordered_items_2d( klass=ResourceHolder, - num_items_x=3, + num_items_x=4, num_items_y=2, dx=start_x, dy=start_y, @@ -373,17 +373,17 @@ def BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier(name: str) -> BottleCarr sites=sites, model="6x20ml_DispensingVialCarrier", ) - carrier.num_items_x = 3 + carrier.num_items_x = 4 carrier.num_items_y = 2 carrier.num_items_z = 1 - ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] - for i in range(6): + ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"] + for i in range(8): carrier[i] = BIOYOND_PolymerStation_20ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}") return carrier def BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier(name: str) -> BottleCarrier: - """配液瓶(小)板 - 2x3布局,6个位置""" + """配液瓶(小)板 - 4x2布局,8个位置""" # 载架尺寸 (mm) carrier_size_x = 127.8 @@ -396,12 +396,12 @@ def BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier(name: str) -> BottleCar bottle_spacing_y = 35.0 # Y方向间距 # 计算起始位置 (居中排列) - start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2 + start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2 start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2 sites = create_ordered_items_2d( klass=ResourceHolder, - num_items_x=3, + num_items_x=4, num_items_y=2, dx=start_x, dy=start_y, @@ -423,11 +423,11 @@ def BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier(name: str) -> BottleCar sites=sites, model="6x_SmallSolutionBottleCarrier", ) - carrier.num_items_x = 3 + carrier.num_items_x = 4 carrier.num_items_y = 2 carrier.num_items_z = 1 - ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] - for i in range(6): + ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"] + for i in range(8): carrier[i] = BIOYOND_PolymerStation_Small_Solution_Bottle(f"{name}_bottle_{ordering[i]}") return carrier @@ -483,7 +483,7 @@ def BIOYOND_PolymerStation_4x_LargeSolutionBottleCarrier(name: str) -> BottleCar def BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier(name: str) -> BottleCarrier: - """加样头(大)板 - 2x3布局,6个位置""" + """加样头(大)板 - 1x1布局,1个位置""" # 载架尺寸 (mm) carrier_size_x = 127.8 @@ -496,13 +496,13 @@ def BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier(name: str) -> BottleCarri bottle_spacing_y = 35.0 # Y方向间距 # 计算起始位置 (居中排列) - start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2 - start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2 + start_x = (carrier_size_x - (1 - 1) * bottle_spacing_x - bottle_diameter) / 2 + start_y = (carrier_size_y - (1 - 1) * bottle_spacing_y - bottle_diameter) / 2 sites = create_ordered_items_2d( klass=ResourceHolder, - num_items_x=3, - num_items_y=2, + num_items_x=1, + num_items_y=1, dx=start_x, dy=start_y, dz=5.0, @@ -523,12 +523,10 @@ def BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier(name: str) -> BottleCarri sites=sites, model="6x_LargeDispenseHeadCarrier", ) - carrier.num_items_x = 3 - carrier.num_items_y = 2 + carrier.num_items_x = 1 + carrier.num_items_y = 1 carrier.num_items_z = 1 - ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] - for i in range(6): - carrier[i] = BIOYOND_PolymerStation_Large_Dispense_Head(f"{name}_head_{ordering[i]}") + carrier[0] = BIOYOND_PolymerStation_Large_Dispense_Head(f"{name}_head_1") return carrier From fd737311308041fb5ecd0d2995448597c0a3eff2 Mon Sep 17 00:00:00 2001 From: calvincao Date: Thu, 23 Oct 2025 18:02:49 +0800 Subject: [PATCH 013/104] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=85=A5=E5=BA=93=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=89=A9=E6=96=99=E6=95=B0=E6=8D=AE=E5=90=8C=E6=AD=A5=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=9B=E4=BC=98=E5=8C=96=E6=97=A5=E5=BF=97=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E4=BB=A5=E6=8F=90=E4=BE=9B=E6=9B=B4=E8=AF=A6=E7=BB=86?= =?UTF-8?q?=E7=9A=84=E5=90=8C=E6=AD=A5=E7=8A=B6=E6=80=81=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bioyond_cell/bioyond_cell_workstation.py | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index 4a343e7..3e4fcce 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -1007,9 +1007,42 @@ class BioyondCellWorkstation(BioyondWorkstation): logger.info(f"\n正在执行批量入库,共 {len(inbound_items)} 条记录...") result = self.storage_batch_inbound(inbound_items) - if result.get("code") == 1: + inbound_success = result.get("code") == 1 + + if inbound_success: logger.info(f"✓ 批量入库成功!") logger.info(f" 响应数据: {result.get('data', {})}") + + # 步骤3: 同步物料数据 + logger.info(f"\n【步骤3/3】同步物料数据到系统...") + if hasattr(self, 'resource_synchronizer') and self.resource_synchronizer: + try: + # 尝试同步不同类型的物料 + # typeMode: 0=耗材, 1=样品, 2=试剂 + sync_success = False + for type_mode in [0, 1, 2]: + try: + logger.info(f" 尝试同步 typeMode={type_mode} 的物料...") + bioyond_data = self.hardware_interface.stock_material( + f'{{"typeMode": {type_mode}, "includeDetail": true}}' + ) + if bioyond_data: + logger.info(f" ✓ 获取到 {len(bioyond_data) if isinstance(bioyond_data, list) else 1} 条物料数据") + sync_success = True + except Exception as e: + logger.debug(f" typeMode={type_mode} 同步失败: {e}") + continue + + if sync_success: + logger.info(f"✓ 物料数据同步完成") + else: + logger.warning(f"⚠ 物料数据同步未获取到数据(这是正常的,新创建的物料可能需要时间才能查询到)") + + except Exception as e: + logger.warning(f"⚠ 物料数据同步出错: {e}") + logger.info(f" 提示:新创建的物料已成功入库,同步失败不影响使用") + else: + logger.warning("⚠ 资源同步器未初始化,跳过同步") else: logger.error(f"✗ 批量入库失败!") logger.error(f" 响应: {result}") @@ -1019,7 +1052,7 @@ class BioyondCellWorkstation(BioyondWorkstation): logger.info("=" * 60 + "\n") return { - "success": result.get("code") == 1, + "success": inbound_success, "created_materials": created_materials, "inbound_result": result, "total_created": len(created_materials), From e2097f0b22a6659388c3030beec2515a519d1176 Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Thu, 23 Oct 2025 21:41:28 +0800 Subject: [PATCH 014/104] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=B0=E7=89=A9?= =?UTF-8?q?=E6=96=99=E7=B1=BB=E5=9E=8B=E6=98=A0=E5=B0=84=EF=BC=9A=E5=8C=85?= =?UTF-8?q?=E6=8B=AC100ml=E6=B6=B2=E4=BD=93=E3=80=81=E6=B6=B2=E3=80=81?= =?UTF-8?q?=E9=AB=98=E7=B2=98=E6=B6=B2=E3=80=815ml/20ml=E5=88=86=E6=B6=B2?= =?UTF-8?q?=E7=93=B6=E3=80=81=E9=85=8D=E6=B6=B2=E7=93=B6=E3=80=81=E5=8A=A0?= =?UTF-8?q?=E6=A0=B7=E5=A4=B4=E3=80=81=E9=80=82=E9=85=8D=E5=99=A8=E5=9D=97?= =?UTF-8?q?=E3=80=81=E6=9E=AA=E5=A4=B4=E7=9B=92=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../workstation/bioyond_studio/config.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 9294300..dbf904b 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -26,7 +26,6 @@ BIOYOND_FULL_CONFIG = { "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "0.0.0.0"), # HTTP服务监听地址(0.0.0.0 表示监听所有网络接口) "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), "report_ip": os.getenv("BIOYOND_REPORT_IP", "172.21.32.172"), # 报送给 Bioyond 的本机IP地址(留空则自动检测) - # 调试模式 "debug_mode": os.getenv("BIOYOND_DEBUG_MODE", "False").lower() == "true", } @@ -149,6 +148,22 @@ MATERIAL_TYPE_MAPPINGS = { "样品瓶": ("BIOYOND_PolymerStation_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"), "90%分装小瓶": ("BIOYOND_PolymerStation_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"), "10%分装小瓶": ("BIOYOND_PolymerStation_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"), + "20ml分液瓶": ("BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), + "100ml液体": ("BIOYOND_PolymerStation_100ml_Liquid_Bottle", "d37166b3-ecaa-481e-bd84-3032b795ba07"), + "液": ("BIOYOND_PolymerStation_Liquid_Bottle", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), + "高粘液": ("BIOYOND_PolymerStation_High_Viscosity_Liquid_Bottle", "abe8df30-563d-43d2-85e0-cabec59ddc16"), + "加样头(大)": ("BIOYOND_PolymerStation_Large_Dispense_Head", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + "5ml分液瓶板": ("BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"), + "5ml分液瓶": ("BIOYOND_PolymerStation_5ml_Dispensing_Vial", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"), + "20ml分液瓶板": ("BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier", "3a192fa4-47db-3449-162a-eaf8aba57e27"), + "配液瓶(小)板": ("BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier", "3a190c8b-3284-af78-d29f-9a69463ad047"), + "配液瓶(小)": ("BIOYOND_PolymerStation_Small_Solution_Bottle", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"), + "配液瓶(大)板": ("BIOYOND_PolymerStation_4x_LargeSolutionBottleCarrier", "53e50377-32dc-4781-b3c0-5ce45bc7dc27"), + "配液瓶(大)": ("BIOYOND_PolymerStation_Large_Solution_Bottle", "19c52ad1-51c5-494f-8854-576f4ca9c6ca"), + "加样头(大)板": ("BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"), + "适配器块": ("BIOYOND_PolymerStation_AdapterBlock", "efc3bb32-d504-4890-91c0-b64ed3ac80cf"), + "枪头盒": ("BIOYOND_PolymerStation_TipBox", "3a192c2e-20f3-a44a-0334-c8301839d0b3"), + "枪头": ("BIOYOND_PolymerStation_Pipette_Tip", "b6196971-1050-46da-9927-333e8dea062d"), } # 步骤参数配置(各工作流的步骤UUID) From e92d93396809bd614876182e22c60fc9b942f990 Mon Sep 17 00:00:00 2001 From: calvincao Date: Thu, 23 Oct 2025 22:36:21 +0800 Subject: [PATCH 015/104] =?UTF-8?q?refactor(bioyond=5Fcell=5Fworkstation):?= =?UTF-8?q?=20=E9=87=8D=E6=9E=84=E7=89=A9=E6=96=99=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E4=B8=8E=E5=85=A5=E5=BA=93=E9=80=BB=E8=BE=91-=20=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E4=BB=8ECSV=E8=AF=BB=E5=8F=96=E7=89=A9=E6=96=99?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E7=9A=84=E5=8A=9F=E8=83=BD=20-=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E9=80=9A=E8=BF=87=E5=8F=82=E6=95=B0=E4=BC=A0=E9=80=92?= =?UTF-8?q?=E7=89=A9=E6=96=99=E5=90=8D=E7=A7=B0=E5=88=97=E8=A1=A8=E7=9A=84?= =?UTF-8?q?=E6=96=B9=E5=BC=8F-=20=E6=8A=BD=E7=A6=BB=E4=BB=93=E5=BA=93?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E5=8A=A0=E8=BD=BD=E9=80=BB=E8=BE=91=E8=87=B3?= =?UTF-8?q?=E7=8B=AC=E7=AB=8B=E6=96=B9=E6=B3=95=20-=20=E7=AE=80=E5=8C=96?= =?UTF-8?q?=E7=89=A9=E6=96=99=E5=88=9B=E5=BB=BA=E4=B8=8E=E5=85=A5=E5=BA=93?= =?UTF-8?q?=E6=B5=81=E7=A8=8B-=20=E7=BB=9F=E4=B8=80=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E5=90=8C=E6=AD=A5=E5=99=A8=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=90=8C=E6=AD=A5=20-=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E8=B0=83=E7=94=A8=E7=A4=BA=E4=BE=8B=E4=BB=A5=E9=80=82=E9=85=8D?= =?UTF-8?q?=E6=96=B0=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bioyond_cell/bioyond_cell_workstation.py | 280 ++++++++---------- 1 file changed, 116 insertions(+), 164 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index 3e4fcce..3c0ec08 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -14,7 +14,7 @@ import socket from urllib3 import response from unilabos.devices.workstation.workstation_base import WorkstationBase -from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation +from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation, BioyondResourceSynchronizer from unilabos.devices.workstation.bioyond_studio.config import ( BIOYOND_FULL_CONFIG, WORKFLOW_MAPPINGS, MATERIAL_TYPE_MAPPINGS, WAREHOUSE_MAPPING ) @@ -886,180 +886,132 @@ class BioyondCellWorkstation(BioyondWorkstation): logger.info(f"物料创建完成,成功创建 {len(created_materials)}/{total} 个固体物料") return created_materials - def create_and_inbound_materials_from_csv( - self, - csv_path: str = "solid_materials.csv", + def _sync_materials_safe(self) -> bool: + """仅使用 BioyondResourceSynchronizer 执行同步(与 station.py 保持一致)。""" + if hasattr(self, 'resource_synchronizer') and self.resource_synchronizer: + try: + return bool(self.resource_synchronizer.sync_from_external()) + except Exception as e: + logger.error(f"同步失败: {e}") + return False + logger.warning("资源同步器未初始化") + return False + + def _load_warehouse_locations(self, warehouse_name: str) -> tuple[List[str], List[str]]: + """从配置加载仓库位置信息 + + Args: + warehouse_name: 仓库名称 + + Returns: + (location_ids, position_names) 元组 + """ + warehouse_mapping = self.bioyond_config.get("warehouse_mapping", WAREHOUSE_MAPPING) + + if warehouse_name not in warehouse_mapping: + raise ValueError(f"配置中未找到仓库: {warehouse_name}。可用: {list(warehouse_mapping.keys())}") + + site_uuids = warehouse_mapping[warehouse_name].get("site_uuids", {}) + if not site_uuids: + raise ValueError(f"仓库 {warehouse_name} 没有配置位置") + + # 按顺序获取位置ID和名称 + location_ids = [] + position_names = [] + for key in sorted(site_uuids.keys()): + location_ids.append(site_uuids[key]) + position_names.append(key) + + return location_ids, position_names + + + def create_and_inbound_materials( + self, + material_names: Optional[List[str]] = None, type_id: str = "3a190ca0-b2f6-9aeb-8067-547e72c11469", warehouse_name: str = "粉末加样头堆栈" ) -> Dict[str, Any]: """ - 从CSV文件读取物料列表,创建物料并批量入库到指定堆栈 - + 传参与默认列表方式创建物料并入库(不使用CSV)。 + Args: - csv_path: CSV文件路径 - type_id: 物料类型ID(默认为固体物料类型) - warehouse_name: 仓库名称(默认为"粉末加样头堆栈") - + material_names: 物料名称列表;默认使用 [LiPF6, LiDFOB, DTD, LiFSI, LiPO2F2] + type_id: 物料类型ID + warehouse_name: 目标仓库名(用于取位置信息) + Returns: - 包含执行结果的字典 + 执行结果字典 """ logger.info("=" * 60) - logger.info(f"开始执行:从CSV读取物料列表并批量创建入库到 {warehouse_name}") + logger.info(f"开始执行:从参数创建物料并批量入库到 {warehouse_name}") logger.info("=" * 60) - - # 从配置中获取位置ID列表 - warehouse_mapping = self.bioyond_config.get("warehouse_mapping", WAREHOUSE_MAPPING) - - if warehouse_name not in warehouse_mapping: - error_msg = f"配置中未找到仓库: {warehouse_name}" - logger.error(error_msg) - logger.info(f"可用的仓库: {list(warehouse_mapping.keys())}") - return {"success": False, "error": error_msg} - - warehouse_config = warehouse_mapping[warehouse_name] - site_uuids = warehouse_config.get("site_uuids", {}) - - if not site_uuids: - error_msg = f"仓库 {warehouse_name} 没有配置位置" - logger.error(error_msg) - return {"success": False, "error": error_msg} - - # 按顺序获取位置ID(A01, B01, C01...) - all_location_ids = [] - position_names = [] - for key in sorted(site_uuids.keys()): - all_location_ids.append(site_uuids[key]) - position_names.append(key) - - logger.info(f"✓ 从配置文件加载 {len(all_location_ids)} 个位置") - logger.info(f" 仓库: {warehouse_name}") - logger.info(f" 位置范围: {position_names[0]} ~ {position_names[-1]}") - - # 读取CSV文件 - csv_file_path = Path(csv_path) - material_names = [] - + try: - df_materials = pd.read_csv(csv_file_path) - if 'material_name' in df_materials.columns: - material_names = df_materials['material_name'].dropna().astype(str).str.strip().tolist() - logger.info(f"✓ 成功从CSV文件读取 {len(material_names)} 个物料名称") - logger.info(f" 文件路径: {csv_file_path}") + # 1) 准备物料名称(默认值) + default_materials = ["LiPF6", "LiDFOB", "DTD", "LiFSI", "LiPO2F2"] + mat_names = [m.strip() for m in (material_names or default_materials) if str(m).strip()] + if not mat_names: + return {"success": False, "error": "物料名称列表为空"} + + # 2) 加载仓库位置信息 + all_location_ids, position_names = self._load_warehouse_locations(warehouse_name) + logger.info(f"✓ 加载 {len(all_location_ids)} 个位置 ({position_names[0]} ~ {position_names[-1]})") + + # 限制数量不超过可用位置 + if len(mat_names) > len(all_location_ids): + logger.warning(f"物料数量超出位置数量,仅处理前 {len(all_location_ids)} 个") + mat_names = mat_names[:len(all_location_ids)] + + # 3) 创建物料 + logger.info(f"\n【步骤1/3】创建 {len(mat_names)} 个固体物料...") + created_materials = self.create_solid_materials(mat_names, type_id) + if not created_materials: + return {"success": False, "error": "没有成功创建任何物料"} + + # 4) 批量入库 + logger.info(f"\n【步骤2/3】批量入库物料...") + location_ids = all_location_ids[:len(created_materials)] + selected_positions = position_names[:len(created_materials)] + + inbound_items = [ + {"materialId": mat["materialId"], "locationId": loc_id} + for mat, loc_id in zip(created_materials, location_ids) + ] + + for material, position in zip(created_materials, selected_positions): + logger.info(f" - {material['name']} → {position}") + + result = self.storage_batch_inbound(inbound_items) + if result.get("code") != 1: + logger.error(f"✗ 批量入库失败: {result}") + return {"success": False, "error": "批量入库失败", "created_materials": created_materials, "inbound_result": result} + + logger.info("✓ 批量入库成功") + + # 5) 同步 + logger.info(f"\n【步骤3/3】同步物料数据...") + if self._sync_materials_safe(): + logger.info("✓ 物料数据同步完成") else: - logger.error(f"✗ CSV文件缺少 'material_name' 列") - return {"success": False, "error": "CSV文件缺少 'material_name' 列"} - except FileNotFoundError: - logger.error(f"✗ 未找到CSV文件: {csv_file_path}") - logger.info("请创建CSV文件,格式:") - logger.info(" material_name") - logger.info(" LiPF6") - logger.info(" LiDFOB") - logger.info(" ...") - return {"success": False, "error": f"未找到CSV文件: {csv_file_path}"} + logger.warning("⚠ 物料数据同步未完成(可忽略,不影响已创建与入库的数据)") + + logger.info("\n" + "=" * 60) + logger.info("流程完成") + logger.info("=" * 60 + "\n") + + return { + "success": True, + "created_materials": created_materials, + "inbound_result": result, + "total_created": len(created_materials), + "total_inbound": len(inbound_items), + "warehouse": warehouse_name, + "positions": selected_positions + } + except Exception as e: - logger.error(f"✗ 读取CSV文件失败: {e}") - return {"success": False, "error": f"读取CSV文件失败: {e}"} - - if not material_names: - logger.error("CSV文件中没有有效的物料名称") - return {"success": False, "error": "CSV文件中没有有效的物料名称"} - - # 检查物料数量 - if len(material_names) > len(all_location_ids): - logger.warning(f"物料数量({len(material_names)})超过可用位置数量({len(all_location_ids)})!") - logger.warning(f"将仅创建前 {len(all_location_ids)} 个物料") - material_names = material_names[:len(all_location_ids)] - - # 准备位置信息 - location_ids = all_location_ids[:len(material_names)] - selected_positions = position_names[:len(material_names)] - - # 步骤1: 创建固体物料 - logger.info(f"\n【步骤1/2】创建 {len(material_names)} 个固体物料...") - logger.info(f"物料类型ID: {type_id}") - logger.info(f"物料列表: {', '.join(material_names)}") - - created_materials = self.create_solid_materials( - material_names=material_names, - type_id=type_id - ) - - if len(created_materials) != len(material_names): - logger.warning(f"创建的物料数量({len(created_materials)})与计划数量({len(material_names)})不匹配!") - logger.warning("将仅对成功创建的物料进行入库操作") - - if not created_materials: - logger.error("没有成功创建任何物料") - return {"success": False, "error": "没有成功创建任何物料"} - - # 步骤2: 批量入库到指定位置 - logger.info(f"\n【步骤2/2】批量入库物料到 {warehouse_name}...") - inbound_items = [] - - for idx, material in enumerate(created_materials): - if idx < len(location_ids): - inbound_items.append({ - "materialId": material["materialId"], - "locationId": location_ids[idx] - }) - logger.info(f" - {material['name']} (ID: {material['materialId'][:8]}...) → 位置 {selected_positions[idx]}") - - logger.info(f"\n正在执行批量入库,共 {len(inbound_items)} 条记录...") - result = self.storage_batch_inbound(inbound_items) - - inbound_success = result.get("code") == 1 - - if inbound_success: - logger.info(f"✓ 批量入库成功!") - logger.info(f" 响应数据: {result.get('data', {})}") - - # 步骤3: 同步物料数据 - logger.info(f"\n【步骤3/3】同步物料数据到系统...") - if hasattr(self, 'resource_synchronizer') and self.resource_synchronizer: - try: - # 尝试同步不同类型的物料 - # typeMode: 0=耗材, 1=样品, 2=试剂 - sync_success = False - for type_mode in [0, 1, 2]: - try: - logger.info(f" 尝试同步 typeMode={type_mode} 的物料...") - bioyond_data = self.hardware_interface.stock_material( - f'{{"typeMode": {type_mode}, "includeDetail": true}}' - ) - if bioyond_data: - logger.info(f" ✓ 获取到 {len(bioyond_data) if isinstance(bioyond_data, list) else 1} 条物料数据") - sync_success = True - except Exception as e: - logger.debug(f" typeMode={type_mode} 同步失败: {e}") - continue - - if sync_success: - logger.info(f"✓ 物料数据同步完成") - else: - logger.warning(f"⚠ 物料数据同步未获取到数据(这是正常的,新创建的物料可能需要时间才能查询到)") - - except Exception as e: - logger.warning(f"⚠ 物料数据同步出错: {e}") - logger.info(f" 提示:新创建的物料已成功入库,同步失败不影响使用") - else: - logger.warning("⚠ 资源同步器未初始化,跳过同步") - else: - logger.error(f"✗ 批量入库失败!") - logger.error(f" 响应: {result}") - - logger.info("\n" + "=" * 60) - logger.info("固体物料创建和入库流程完成") - logger.info("=" * 60 + "\n") - - return { - "success": inbound_success, - "created_materials": created_materials, - "inbound_result": result, - "total_created": len(created_materials), - "total_inbound": len(inbound_items), - "warehouse": warehouse_name, - "positions": selected_positions - } + logger.error(f"✗ 执行失败: {e}") + return {"success": False, "error": str(e)} # -------------------------------- @@ -1070,7 +1022,7 @@ if __name__ == "__main__": logger.info(ws.scheduler_start()) # 从CSV文件读取物料列表并批量创建入库 - result = ws.create_and_inbound_materials_from_csv() + result = ws.create_and_inbound_materials() # 继续后续流程 logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 From 7206e42bf1cf8df597487896724d193243fcf561 Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Fri, 24 Oct 2025 11:37:36 +0800 Subject: [PATCH 016/104] =?UTF-8?q?xinyu1024=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bioyond_cell/bioyond_cell_workstation.py | 600 ++++++------------ .../workstation/bioyond_studio/config.py | 222 +++---- .../workstation/workstation_http_service.py | 8 +- unilabos/registry/resources/bioyond/deck.yaml | 2 +- unilabos/resources/bioyond/bottles.py | 2 +- unilabos/resources/bioyond/warehouses.py | 2 +- unilabos/resources/graphio.py | 2 + 7 files changed, 282 insertions(+), 556 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index 3e4fcce..20dcaa9 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -9,14 +9,12 @@ import time from datetime import datetime, timedelta import re import threading -import os -import socket +import json from urllib3 import response -from unilabos.devices.workstation.workstation_base import WorkstationBase -from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation +from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation, BioyondResourceSynchronizer from unilabos.devices.workstation.bioyond_studio.config import ( - BIOYOND_FULL_CONFIG, WORKFLOW_MAPPINGS, MATERIAL_TYPE_MAPPINGS, WAREHOUSE_MAPPING + API_CONFIG, MATERIAL_TYPE_MAPPINGS, WAREHOUSE_MAPPING, SOLID_LIQUID_MAPPINGS ) from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService from unilabos.utils.log import logger @@ -43,32 +41,52 @@ class BioyondCellWorkstation(BioyondWorkstation): *args, **kwargs, ): - # 使用统一配置,支持自定义覆盖 - self.bioyond_config = bioyond_config or { - **BIOYOND_FULL_CONFIG, # 从 config.py 加载完整配置 - "workflow_mappings": WORKFLOW_MAPPINGS, + # 使用统一配置,支持自定义覆盖, 从 config.py 加载完整配置 + self.bioyond_config = bioyond_config or { + **API_CONFIG, "material_type_mappings": MATERIAL_TYPE_MAPPINGS, "warehouse_mapping": WAREHOUSE_MAPPING - } + } + + # "material_type_mappings": MATERIAL_TYPE_MAPPINGS + # "warehouse_mapping": WAREHOUSE_MAPPING + + print(self.bioyond_config) self.debug_mode = self.bioyond_config["debug_mode"] - self.http_service_started = False + self.http_service_started = self.debug_mode deck = kwargs.pop("deck", None) self.device_id = kwargs.pop("device_id", "bioyond_cell_workstation") super().__init__(bioyond_config=self.bioyond_config, deck=deck, station_resource=station_resource, *args, **kwargs) - # 步骤通量任务通知铃 - self._pending_events: dict[str, threading.Event] = {} + self.update_push_ip() #直接修改奔耀端的报送ip地址 + logger.info("已更新奔耀端推送 IP 地址") + + # 启动 HTTP 服务线程 + t = threading.Thread(target=self._start_http_service, daemon=True, name="unilab_http") + t.start() + logger.info("HTTP 服务线程已启动") + # 等到任务报送成功 + self.order_finish_event = threading.Event() + self.last_order_status = None + self.last_order_code = None logger.info(f"Bioyond工作站初始化完成 (debug_mode={self.debug_mode})") - # 实例化并在后台线程启动 HTTP 报送服务 - self.order_status = {} # 记录任务完成情况,用于接受bioyond post信息和反馈信息,尤其用于硬件查询和物料信息变化 + def _start_http_service(self): + """启动 HTTP 服务""" + host = self.bioyond_config.get("HTTP_host", "") + port = self.bioyond_config.get("HTTP_port", None) try: - logger.info("准备开始unilab_HTTP后台线程") - t = threading.Thread(target=self._start_http_service, daemon=True, name="unilab_http") - t.start() + self.service = WorkstationHTTPService(self, host=host, port=port) + self.service.start() + self.http_service_started = True + logger.info(f"WorkstationHTTPService 成功启动: {host}:{port}") + while True: + time.sleep(1) #一直挂着,直到进程退出 except Exception as e: - logger.error(f"unilab-HTTP后台线程启动失败: {e}") + self.http_service_started = False + logger.error(f"启动 WorkstationHTTPService 失败: {e}", exc_info=True) - # http报送服务 + + # http报送服务,返回数据部分 def process_step_finish_report(self, report_request): stepId = report_request.data.get("stepId") logger.info(f"步骤完成: stepId: {stepId}, stepName:{report_request.data.get('stepName')}") @@ -82,147 +100,65 @@ class BioyondCellWorkstation(BioyondWorkstation): order_code = report_request.data.get("orderCode") status = report_request.data.get("status") logger.info(f"report_request: {report_request}") - logger.info(f"任务完成: {order_code}, status={status}") - - # 记录订单状态码 - if order_code: - self.order_status[order_code] = status - - self._set_pending_event(order_code) + + # 保存完整报文 + self.last_order_report = report_request.data + # 如果是当前等待的订单,触发事件 + if self.last_order_code == order_code: + self.order_finish_event.set() + return {"status": "received"} - def _set_pending_event(self, taskname: Optional[str]) -> None: - if not taskname: - return - event = self._pending_events.get(taskname) - if event is None: - event = threading.Event() - self._pending_events[taskname] = event - event.set() - - def _wait_for_order_completion(self, order_code: Optional[str], timeout: int = 600) -> bool: + def wait_for_order_finish(self, order_code: str, timeout: int = 1800) -> Dict[str, Any]: + """ + 等待指定 orderCode 的 /report/order_finish 报送。 + Args: + order_code: 任务编号 + timeout: 超时时间(秒) + Returns: + 完整的报送数据 + 状态判断结果 + """ if not order_code: - logger.warning("无法等待任务完成:order_code 为空") - return False - event = self._pending_events.get(order_code) - if event is None: - event = threading.Event() - self._pending_events[order_code] = event - elif event.is_set(): - logger.info(f"任务 {order_code} 在等待之前已完成") - self._pending_events.pop(order_code, None) - return True - logger.info(f"等待任务 {order_code} 完成 (timeout={timeout}s)") - finished = event.wait(timeout) - if not finished: - logger.warning(f"等待任务 {order_code} 完成超时({timeout}s)") - self._pending_events.pop(order_code, None) - return finished + logger.error("wait_for_order_finish() 被调用,但 order_code 为空!") + return {"status": "error", "message": "empty order_code"} - def _wait_for_response_orders(self, response: Dict[str, Any], context: str, timeout: int = 600) -> None: - order_codes = self._extract_order_codes(response) - if not order_codes: - logger.warning(f"{context} 响应中未找到 orderCode,无法跟踪任务完成") - return - for code in order_codes: - finished = self._wait_for_order_completion(code, timeout=timeout) - if finished: - # 检查订单返回码是否为30(正常完成) - status = self.order_status.get(code) - if status == 30 or status == "30": - logger.info(f"订单 {code} 成功完成,状态码: {status}") - else: - logger.warning(f"订单 {code} 完成但状态码异常: {status} (期望: 30, -11=异常停止, -12=人工停止)") - # 清理状态记录 - self.order_status.pop(code, None) - else: - logger.error(f"订单 {code} 等待超时,未收到完成通知") + self.last_order_code = order_code + self.last_order_report = None + self.order_finish_event.clear() - @staticmethod - def _extract_order_codes(response: Dict[str, Any]) -> List[str]: - order_codes: List[str] = [] - if not isinstance(response, dict): - return order_codes - data = response.get("data") - keys = ["orderCode", "order_code", "orderId", "order_id"] - if isinstance(data, dict): - for key in keys: - if key in data and data[key]: - order_codes.append(str(data[key])) - if not order_codes and "orders" in data and isinstance(data["orders"], list): - for order in data["orders"]: - if isinstance(order, dict): - for key in keys: - if key in order and order[key]: - order_codes.append(str(order[key])) - elif isinstance(data, list): - for item in data: - if isinstance(item, dict): - for key in keys: - if key in item and item[key]: - order_codes.append(str(item[key])) - elif isinstance(data, str): - if data: - order_codes.append(data) - meta = response.get("orderCode") - if meta: - order_codes.append(str(meta)) - # 去重 - seen = set() - unique_codes: List[str] = [] - for code in order_codes: - if code not in seen: - seen.add(code) - unique_codes.append(code) - return unique_codes + logger.info(f"等待任务完成报送: orderCode={order_code} (timeout={timeout}s)") + if not self.order_finish_event.wait(timeout=timeout): + logger.error(f"等待任务超时: orderCode={order_code}") + return {"status": "timeout", "orderCode": order_code} - def _start_http_service(self, host: Optional[str] = None, port: Optional[int] = None) -> None: - host = host or self.bioyond_config.get("HTTP_host", "") - port = port or self.bioyond_config.get("HTTP_port", ) + # 报送数据匹配验证 + report = self.last_order_report or {} + report_code = report.get("orderCode") + status = str(report.get("status", "")) + + if report_code != order_code: + logger.warning(f"收到的报送 orderCode 不匹配: {report_code} ≠ {order_code}") + return {"status": "mismatch", "report": report} + + if status == "30": + logger.info(f"任务成功完成 (orderCode={order_code})") + return {"status": "success", "report": report} + elif status == "-11": + logger.error(f"任务异常停止 (orderCode={order_code})") + return {"status": "abnormal_stop", "report": report} + elif status == "-12": + logger.warning(f"任务人工停止 (orderCode={order_code})") + return {"status": "manual_stop", "report": report} + else: + logger.warning(f"任务未知状态 ({status}) (orderCode={order_code})") + return {"status": f"unknown_{status}", "report": report} - logger.info("准备开始unilab_HTTP服务") - try: - self.service = WorkstationHTTPService(self, host=host, port=port) - logger.info("WorkstationHTTPService 实例化完成") - self.service.start() - self.http_service_started = True - logger.info(f"WorkstationHTTPService成功启动: {host}:{port}") - - # 启动成功后,上报本机推送地址(3.36) - try: - # 优先使用配置中的 report_ip - report_ip = self.bioyond_config.get("report_ip", "").strip() - - # 如果配置中没有指定 report_ip,且监听地址是 0.0.0.0,则自动检测 - if not report_ip and host in ("0.0.0.0", ""): - # 从 Bioyond 配置中提取服务器地址 - bioyond_server = self.bioyond_config.get("base_url", "") - if bioyond_server: - import urllib.parse - parsed = urllib.parse.urlparse(bioyond_server) - - elif not report_ip: - # 如果没有配置 report_ip,使用监听地址 - report_ip = host - - r = self.update_push_ip(report_ip, port) - logger.info(f"向 Bioyond 报送推送地址: {report_ip}:{port}, 结果: {r}") - except Exception as e: - logger.warning(f"调用更新推送IP接口失败: {e}") - - #一直挂着,直到进程退出 - while True: - time.sleep(1) - except Exception as e: - self.http_service_started = False # 调试用 - logger.error(f"启动WorkstationHTTPService失败: {e}", exc_info=True) # -------------------- 基础HTTP封装 -------------------- def _url(self, path: str) -> str: - - return f"{self.bioyond_config['base_url'].rstrip('/')}/{path.lstrip('/')}" + return f"{self.bioyond_config['api_host'].rstrip('/')}/{path.lstrip('/')}" def _post_lims(self, path: str, data: Optional[Any] = None) -> Dict[str, Any]: """LIMS API:大多数接口用 {apiKey/requestTime,data} 包装""" @@ -236,9 +172,11 @@ class BioyondCellWorkstation(BioyondWorkstation): if self.debug_mode: # 模拟返回,不发真实请求 logger.info(f"[DEBUG] POST {path} with payload={payload}") + return {"debug": True, "url": self._url(path), "payload": payload, "status": "ok"} try: + logger.info(json.dumps(payload, ensure_ascii=False)) response = requests.post( self._url(path), json=payload, @@ -248,7 +186,7 @@ class BioyondCellWorkstation(BioyondWorkstation): response.raise_for_status() return response.json() except Exception as e: - logger.info(f"{self.bioyond_config['base_url'].rstrip('/')}/{path.lstrip('/')}") + logger.info(f"{self.bioyond_config['api_host'].rstrip('/')}/{path.lstrip('/')}") logger.error(f"POST {path} 失败: {e}") return {"error": str(e)} @@ -263,7 +201,7 @@ class BioyondCellWorkstation(BioyondWorkstation): if self.debug_mode: logger.info(f"[DEBUG] PUT {path} with payload={payload}") - return {"debug": True, "url": self._url(path), "payload": payload, "status": "ok"} + return {"debug_mode": True, "url": self._url(path), "payload": payload, "status": "ok"} try: response = requests.put( @@ -275,7 +213,7 @@ class BioyondCellWorkstation(BioyondWorkstation): response.raise_for_status() return response.json() except Exception as e: - logger.info(f"{self.bioyond_config['base_url'].rstrip('/')}/{path.lstrip('/')}") + logger.info(f"{self.bioyond_config['api_host'].rstrip('/')}/{path.lstrip('/')}") logger.error(f"PUT {path} 失败: {e}") return {"error": str(e)} @@ -310,11 +248,10 @@ class BioyondCellWorkstation(BioyondWorkstation): return self._post_lims("/api/lims/storage/batch-inbound", items) - def auto_feeding4to3( self, # ★ 修改点:默认模板路径 - xlsx_path: Optional[str] = "/Users/calvincao/Desktop/work/uni-lab-all/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/样品导入模板.xlsx", + xlsx_path: Optional[str] = "unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\样品导入模板.xlsx", # ---------------- WH4 - 加样头面 (Z=1, 12个点位) ---------------- WH4_x1_y1_z1_1_materialName: str = "", WH4_x1_y1_z1_1_quantity: float = 0.0, WH4_x2_y1_z1_2_materialName: str = "", WH4_x2_y1_z1_2_quantity: float = 0.0, @@ -443,9 +380,15 @@ class BioyondCellWorkstation(BioyondWorkstation): return {"code": 0, "message": "no valid items", "data": []} logger.info(items) response = self._post_lims("/api/lims/order/auto-feeding4to3", items) - self._wait_for_response_orders(response, "auto_feeding4to3") - return response + # 等待任务报送成功 + order_code = response.get("data", {}).get("orderCode") + if not order_code: + logger.error("上料任务未返回有效 orderCode!") + return response + # 等待完成报送 + result = self.wait_for_order_finish(order_code) + return result @@ -681,31 +624,17 @@ class BioyondCellWorkstation(BioyondWorkstation): } orders.append(order_data) - # print(orders) - while True: - time.sleep(5) - response = self._post_lims("/api/lims/order/orders", orders) - if response.get("data", []): - break - logger.info(f"等待配液实验创建完成") - - - # self.order_status[response["data"]["orderCode"]] = "running" - - # while True: - # time.sleep(5) - # if self.order_status.get(response["data"]["orderCode"], None) == "finished": - # logger.info(f"配液实验已完成 ,即将执行 3-2-1 转运") - # break - # logger.info(f"等待配液实验完成") - - # self.transfer_3_to_2_to_1() - # self.wait_for_transfer_task() - # logger.info(f"3-2-1 转运完成,返回结果") - # return r321 - self._wait_for_response_orders(response, "create_orders", timeout=1800) - return response + response = self._post_lims("/api/lims/order/orders", orders) + print(response) + # 等待任务报送成功 + order_code = response.get("data", {}).get("orderCode") + if not order_code: + logger.error("上料任务未返回有效 orderCode!") + return response + # 等待完成报送 + result = self.wait_for_order_finish(order_code) + return result # 2.7 启动调度 def scheduler_start(self) -> Dict[str, Any]: @@ -726,6 +655,13 @@ class BioyondCellWorkstation(BioyondWorkstation): 请求体只包含 apiKey 和 requestTime """ return self._post_lims("/api/lims/scheduler/continue") + def scheduler_reset(self) -> Dict[str, Any]: + """ + 复位调度 (2.11) + 请求体只包含 apiKey 和 requestTime + """ + return self._post_lims("/api/lims/scheduler/reset") + # 2.24 物料变更推送 def report_material_change(self, material_obj: Dict[str, Any]) -> Dict[str, Any]: @@ -744,7 +680,16 @@ class BioyondCellWorkstation(BioyondWorkstation): } if source_wh_id: payload["sourceWHID"] = source_wh_id - return self._post_lims("/api/lims/order/transfer-task3To2To1", payload) + + response = self._post_lims("/api/lims/order/transfer-task3To2To1", payload) + # 等待任务报送成功 + order_code = response.get("data", {}).get("orderCode") + if not order_code: + logger.error("上料任务未返回有效 orderCode!") + return response + # 等待完成报送 + result = self.wait_for_order_finish(order_code) + return result # 3.35 1→2 物料转运 def transfer_1_to_2(self) -> Dict[str, Any]: @@ -753,7 +698,15 @@ class BioyondCellWorkstation(BioyondWorkstation): URL: /api/lims/order/transfer-task1To2 只需要 apiKey 和 requestTime """ - return self._post_lims("/api/lims/order/transfer-task1To2") + response = self._post_lims("/api/lims/order/transfer-task1To2") + # 等待任务报送成功 + order_code = response.get("data", {}).get("orderCode") + if not order_code: + logger.error("上料任务未返回有效 orderCode!") + return response + # 等待完成报送 + result = self.wait_for_order_finish(order_code) + return result # 2.5 批量查询实验报告(post过滤关键字查询) def order_list_v2(self, @@ -825,241 +778,38 @@ class BioyondCellWorkstation(BioyondWorkstation): logger.warning("超时未找到成功的物料转移任务") return False - def create_solid_materials(self, material_names: List[str], type_id: str = "3a190ca0-b2f6-9aeb-8067-547e72c11469") -> List[Dict[str, Any]]: + def create_materials(self, mappings: Dict[str, Dict[str, Any]]) -> List[Dict[str, Any]]: """ - 批量创建固体物料 - - Args: - material_names: 物料名称列表 - type_id: 物料类型ID(默认为固体物料类型) - - Returns: - 创建的物料列表,每个元素包含物料信息和ID + 将 SOLID_LIQUID_MAPPINGS 中的所有物料逐个 POST 到 /api/lims/storage/material """ - created_materials = [] - total = len(material_names) - - for i, name in enumerate(material_names, 1): - # 根据接口文档构建完整的请求体 - material_data = { - "typeId": type_id, - "name": name, - "unit": "g", # 添加单位 - "quantity": 1, # 添加数量(默认1) - "parameters": "" # 参数字段(空字符串表示无参数) - } - - logger.info(f"正在创建第 {i}/{total} 个固体物料: {name}") - result = self._post_lims("/api/lims/storage/material", material_data) - - if result and result.get("code") == 1: - # data 字段可能是字符串(物料ID)或字典(包含id字段) - data = result.get("data") - if isinstance(data, str): - # data 直接是物料ID字符串 - material_id = data - elif isinstance(data, dict): - # data 是字典,包含id字段 - material_id = data.get("id") - else: - material_id = None - - if material_id: - created_materials.append({ - "name": name, - "materialId": material_id, - "typeId": type_id - }) - logger.info(f"✓ 成功创建物料: {name}, ID: {material_id}") - else: - logger.error(f"✗ 创建物料失败: {name}, 未返回ID") - logger.error(f" 响应数据: {result}") - else: - error_msg = result.get("error") or result.get("message", "未知错误") - logger.error(f"✗ 创建物料失败: {name}") - logger.error(f" 错误信息: {error_msg}") - logger.error(f" 完整响应: {result}") - - # 避免请求过快 - time.sleep(0.3) - - logger.info(f"物料创建完成,成功创建 {len(created_materials)}/{total} 个固体物料") - return created_materials + results = [] + + for name, data in mappings.items(): + data = { + "typeId": data["typeId"], + "code": data.get("code", ""), + "barCode": data.get("barCode", ""), + "name": data["name"], + "unit": data.get("unit", "g"), + "parameters": data.get("parameters", ""), + "quantity": data.get("quantity", ""), + "warningQuantity": data.get("warningQuantity", ""), + "details": data.get("details", []) + } + + logger.info(f"正在 POST 创建物料: {name}") + try: + # ✅ 真正执行 POST + result = self._post_lims("/api/lims/storage/material", data) + logger.info(f"响应: {result}") + except Exception as e: + logger.error(f"✗ 创建物料失败: {name}, 错误: {e}") + results.append({name: {"error": str(e)}}) + time.sleep(0.3) # 避免请求过快 + return results + + - def create_and_inbound_materials_from_csv( - self, - csv_path: str = "solid_materials.csv", - type_id: str = "3a190ca0-b2f6-9aeb-8067-547e72c11469", - warehouse_name: str = "粉末加样头堆栈" - ) -> Dict[str, Any]: - """ - 从CSV文件读取物料列表,创建物料并批量入库到指定堆栈 - - Args: - csv_path: CSV文件路径 - type_id: 物料类型ID(默认为固体物料类型) - warehouse_name: 仓库名称(默认为"粉末加样头堆栈") - - Returns: - 包含执行结果的字典 - """ - logger.info("=" * 60) - logger.info(f"开始执行:从CSV读取物料列表并批量创建入库到 {warehouse_name}") - logger.info("=" * 60) - - # 从配置中获取位置ID列表 - warehouse_mapping = self.bioyond_config.get("warehouse_mapping", WAREHOUSE_MAPPING) - - if warehouse_name not in warehouse_mapping: - error_msg = f"配置中未找到仓库: {warehouse_name}" - logger.error(error_msg) - logger.info(f"可用的仓库: {list(warehouse_mapping.keys())}") - return {"success": False, "error": error_msg} - - warehouse_config = warehouse_mapping[warehouse_name] - site_uuids = warehouse_config.get("site_uuids", {}) - - if not site_uuids: - error_msg = f"仓库 {warehouse_name} 没有配置位置" - logger.error(error_msg) - return {"success": False, "error": error_msg} - - # 按顺序获取位置ID(A01, B01, C01...) - all_location_ids = [] - position_names = [] - for key in sorted(site_uuids.keys()): - all_location_ids.append(site_uuids[key]) - position_names.append(key) - - logger.info(f"✓ 从配置文件加载 {len(all_location_ids)} 个位置") - logger.info(f" 仓库: {warehouse_name}") - logger.info(f" 位置范围: {position_names[0]} ~ {position_names[-1]}") - - # 读取CSV文件 - csv_file_path = Path(csv_path) - material_names = [] - - try: - df_materials = pd.read_csv(csv_file_path) - if 'material_name' in df_materials.columns: - material_names = df_materials['material_name'].dropna().astype(str).str.strip().tolist() - logger.info(f"✓ 成功从CSV文件读取 {len(material_names)} 个物料名称") - logger.info(f" 文件路径: {csv_file_path}") - else: - logger.error(f"✗ CSV文件缺少 'material_name' 列") - return {"success": False, "error": "CSV文件缺少 'material_name' 列"} - except FileNotFoundError: - logger.error(f"✗ 未找到CSV文件: {csv_file_path}") - logger.info("请创建CSV文件,格式:") - logger.info(" material_name") - logger.info(" LiPF6") - logger.info(" LiDFOB") - logger.info(" ...") - return {"success": False, "error": f"未找到CSV文件: {csv_file_path}"} - except Exception as e: - logger.error(f"✗ 读取CSV文件失败: {e}") - return {"success": False, "error": f"读取CSV文件失败: {e}"} - - if not material_names: - logger.error("CSV文件中没有有效的物料名称") - return {"success": False, "error": "CSV文件中没有有效的物料名称"} - - # 检查物料数量 - if len(material_names) > len(all_location_ids): - logger.warning(f"物料数量({len(material_names)})超过可用位置数量({len(all_location_ids)})!") - logger.warning(f"将仅创建前 {len(all_location_ids)} 个物料") - material_names = material_names[:len(all_location_ids)] - - # 准备位置信息 - location_ids = all_location_ids[:len(material_names)] - selected_positions = position_names[:len(material_names)] - - # 步骤1: 创建固体物料 - logger.info(f"\n【步骤1/2】创建 {len(material_names)} 个固体物料...") - logger.info(f"物料类型ID: {type_id}") - logger.info(f"物料列表: {', '.join(material_names)}") - - created_materials = self.create_solid_materials( - material_names=material_names, - type_id=type_id - ) - - if len(created_materials) != len(material_names): - logger.warning(f"创建的物料数量({len(created_materials)})与计划数量({len(material_names)})不匹配!") - logger.warning("将仅对成功创建的物料进行入库操作") - - if not created_materials: - logger.error("没有成功创建任何物料") - return {"success": False, "error": "没有成功创建任何物料"} - - # 步骤2: 批量入库到指定位置 - logger.info(f"\n【步骤2/2】批量入库物料到 {warehouse_name}...") - inbound_items = [] - - for idx, material in enumerate(created_materials): - if idx < len(location_ids): - inbound_items.append({ - "materialId": material["materialId"], - "locationId": location_ids[idx] - }) - logger.info(f" - {material['name']} (ID: {material['materialId'][:8]}...) → 位置 {selected_positions[idx]}") - - logger.info(f"\n正在执行批量入库,共 {len(inbound_items)} 条记录...") - result = self.storage_batch_inbound(inbound_items) - - inbound_success = result.get("code") == 1 - - if inbound_success: - logger.info(f"✓ 批量入库成功!") - logger.info(f" 响应数据: {result.get('data', {})}") - - # 步骤3: 同步物料数据 - logger.info(f"\n【步骤3/3】同步物料数据到系统...") - if hasattr(self, 'resource_synchronizer') and self.resource_synchronizer: - try: - # 尝试同步不同类型的物料 - # typeMode: 0=耗材, 1=样品, 2=试剂 - sync_success = False - for type_mode in [0, 1, 2]: - try: - logger.info(f" 尝试同步 typeMode={type_mode} 的物料...") - bioyond_data = self.hardware_interface.stock_material( - f'{{"typeMode": {type_mode}, "includeDetail": true}}' - ) - if bioyond_data: - logger.info(f" ✓ 获取到 {len(bioyond_data) if isinstance(bioyond_data, list) else 1} 条物料数据") - sync_success = True - except Exception as e: - logger.debug(f" typeMode={type_mode} 同步失败: {e}") - continue - - if sync_success: - logger.info(f"✓ 物料数据同步完成") - else: - logger.warning(f"⚠ 物料数据同步未获取到数据(这是正常的,新创建的物料可能需要时间才能查询到)") - - except Exception as e: - logger.warning(f"⚠ 物料数据同步出错: {e}") - logger.info(f" 提示:新创建的物料已成功入库,同步失败不影响使用") - else: - logger.warning("⚠ 资源同步器未初始化,跳过同步") - else: - logger.error(f"✗ 批量入库失败!") - logger.error(f" 响应: {result}") - - logger.info("\n" + "=" * 60) - logger.info("固体物料创建和入库流程完成") - logger.info("=" * 60 + "\n") - - return { - "success": inbound_success, - "created_materials": created_materials, - "inbound_result": result, - "total_created": len(created_materials), - "total_inbound": len(inbound_items), - "warehouse": warehouse_name, - "positions": selected_positions - } # -------------------------------- @@ -1067,20 +817,24 @@ class BioyondCellWorkstation(BioyondWorkstation): if __name__ == "__main__": ws = BioyondCellWorkstation() - logger.info(ws.scheduler_start()) + logger.info(ws.scheduler_stop()) + + results = ws.create_materials(SOLID_LIQUID_MAPPINGS) + for r in results: + logger.info(r) # 从CSV文件读取物料列表并批量创建入库 - result = ws.create_and_inbound_materials_from_csv() + # logger.info(ws.create_and_inbound_materials_from_csv()) # 继续后续流程 - logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 - # 使用正斜杠或 Path 对象来指定文件路径 - excel_path = Path("unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx") - logger.info(ws.create_orders(excel_path)) - logger.info(ws.transfer_3_to_2_to_1()) - - logger.info(ws.transfer_1_to_2()) + # logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 + # # 使用正斜杠或 Path 对象来指定文件路径 + # excel_path = Path("unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025092701.xlsx") + # logger.info(ws.create_orders(excel_path)) + # logger.info(ws.transfer_3_to_2_to_1()) + # logger.info(ws.transfer_1_to_2()) + # logger.info(ws.scheduler_start()) while True: diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 9294300..55080d4 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -5,17 +5,13 @@ import os # ==================== API 基础配置 ==================== -# 支持通过环境变量覆盖默认值 -API_CONFIG = { - "api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"), - "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.11.219:44388"), -} + # ==================== 完整的 Bioyond 配置 ==================== # BioyondCellWorkstation 默认配置(包含所有必需参数) -BIOYOND_FULL_CONFIG = { +API_CONFIG = { # API 连接配置 - "base_url": os.getenv("BIOYOND_API_HOST", "http://172.16.11.219:44388"), + "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.11.219:44388"), "api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"), "timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")), @@ -23,96 +19,15 @@ BIOYOND_FULL_CONFIG = { "report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"), # HTTP 服务配置 - "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "0.0.0.0"), # HTTP服务监听地址(0.0.0.0 表示监听所有网络接口) - "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), - "report_ip": os.getenv("BIOYOND_REPORT_IP", "172.21.32.172"), # 报送给 Bioyond 的本机IP地址(留空则自动检测) + "HTTP_host": os.getenv("unilab_HTTP_HOST", "172.21.32.164"), # HTTP服务监听地址(0.0.0.0 表示监听所有网络接口) + "HTTP_port": int(os.getenv("unilab_HTTP_PORT", "8080")), # 调试模式 - "debug_mode": os.getenv("BIOYOND_DEBUG_MODE", "False").lower() == "true", -} - -# 工作流映射配置 -WORKFLOW_MAPPINGS = { - "reactor_taken_out": "", - "reactor_taken_in": "", - "Solid_feeding_vials": "", - "Liquid_feeding_vials(non-titration)": "", - "Liquid_feeding_solvents": "", - "Liquid_feeding(titration)": "", - "liquid_feeding_beaker": "", - "Drip_back": "", -} - -# 工作流名称到DisplaySectionName的映射 -WORKFLOW_TO_SECTION_MAP = { - 'reactor_taken_in': '反应器放入', - 'liquid_feeding_beaker': '液体投料-烧杯', - 'Liquid_feeding_vials(non-titration)': '液体投料-小瓶(非滴定)', - 'Liquid_feeding_solvents': '液体投料-溶剂', - 'Solid_feeding_vials': '固体投料-小瓶', - 'Liquid_feeding(titration)': '液体投料-滴定', - 'reactor_taken_out': '反应器取出' + "debug_mode": False, } # 库位映射配置 WAREHOUSE_MAPPING = { - "粉末堆栈": { - "uuid": "", - "site_uuids": { - # 样品板 - "A1": "3a14198e-6929-31f0-8a22-0f98f72260df", - "A2": "3a14198e-6929-4379-affa-9a2935c17f99", - "A3": "3a14198e-6929-56da-9a1c-7f5fbd4ae8af", - "A4": "3a14198e-6929-5e99-2b79-80720f7cfb54", - "B1": "3a14198e-6929-f525-9a1b-1857552b28ee", - "B2": "3a14198e-6929-bf98-0fd5-26e1d68bf62d", - "B3": "3a14198e-6929-2d86-a468-602175a2b5aa", - "B4": "3a14198e-6929-1a98-ae57-e97660c489ad", - # 分装板 - "C1": "3a14198e-6929-46fe-841e-03dd753f1e4a", - "C2": "3a14198e-6929-1bc9-a9bd-3b7ca66e7f95", - "C3": "3a14198e-6929-72ac-32ce-9b50245682b8", - "C4": "3a14198e-6929-3bd8-e6c7-4a9fd93be118", - "D1": "3a14198e-6929-8a0b-b686-6f4a2955c4e2", - "D2": "3a14198e-6929-dde1-fc78-34a84b71afdf", - "D3": "3a14198e-6929-a0ec-5f15-c0f9f339f963", - "D4": "3a14198e-6929-7ac8-915a-fea51cb2e884" - } - }, - "溶液堆栈": { - "uuid": "", - "site_uuids": { - "A1": "3a14198e-d724-e036-afdc-2ae39a7f3383", - "A2": "3a14198e-d724-afa4-fc82-0ac8a9016791", - "A3": "3a14198e-d724-ca48-bb9e-7e85751e55b6", - "A4": "3a14198e-d724-df6d-5e32-5483b3cab583", - "B1": "3a14198e-d724-d818-6d4f-5725191a24b5", - "B2": "3a14198e-d724-be8a-5e0b-012675e195c6", - "B3": "3a14198e-d724-cc1e-5c2c-228a130f40a8", - "B4": "3a14198e-d724-1e28-c885-574c3df468d0", - "C1": "3a14198e-d724-b5bb-adf3-4c5a0da6fb31", - "C2": "3a14198e-d724-ab4e-48cb-817c3c146707", - "C3": "3a14198e-d724-7f18-1853-39d0c62e1d33", - "C4": "3a14198e-d724-28a2-a760-baa896f46b66", - "D1": "3a14198e-d724-d378-d266-2508a224a19f", - "D2": "3a14198e-d724-f56e-468b-0110a8feb36a", - "D3": "3a14198e-d724-0cf1-dea9-a1f40fe7e13c", - "D4": "3a14198e-d724-0ddd-9654-f9352a421de9" - } - }, - "试剂堆栈": { - "uuid": "", - "site_uuids": { - "A1": "3a14198c-c2cf-8b40-af28-b467808f1c36", - "A2": "3a14198c-c2d0-f3e7-871a-e470d144296f", - "A3": "3a14198c-c2d0-dc7d-b8d0-e1d88cee3094", - "A4": "3a14198c-c2d0-2070-efc8-44e245f10c6f", - "B1": "3a14198c-c2d0-354f-39ad-642e1a72fcb8", - "B2": "3a14198c-c2d0-1559-105d-0ea30682cab4", - "B3": "3a14198c-c2d0-725e-523d-34c037ac2440", - "B4": "3a14198c-c2d0-efce-0939-69ca5a7dfd39" - } - }, "粉末加样头堆栈": { "uuid": "", "site_uuids": { @@ -142,44 +57,99 @@ WAREHOUSE_MAPPING = { # 物料类型配置 MATERIAL_TYPE_MAPPINGS = { - "烧杯": ("BIOYOND_PolymerStation_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"), - "试剂瓶": ("BIOYOND_PolymerStation_1BottleCarrier", ""), - "样品板": ("BIOYOND_PolymerStation_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"), - "分装板": ("BIOYOND_PolymerStation_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), - "样品瓶": ("BIOYOND_PolymerStation_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"), - "90%分装小瓶": ("BIOYOND_PolymerStation_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"), - "10%分装小瓶": ("BIOYOND_PolymerStation_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"), +"20ml分液瓶": ("YB_6x20ml_DispensingVialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), + "100ml液体": ("BIOYOND_PolymerStation_100ml_Liquid_Bottle", "d37166b3-ecaa-481e-bd84-3032b795ba07"), + "液": ("BIOYOND_PolymerStation_Liquid_Bottle", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), + "高粘液": ("BIOYOND_PolymerStation_High_Viscosity_Liquid_Bottle", "abe8df30-563d-43d2-85e0-cabec59ddc16"), + "加样头(大)": ("BIOYOND_PolymerStation_Large_Dispense_Head", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + "5ml分液瓶板": ("BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"), + "5ml分液瓶": ("BIOYOND_PolymerStation_5ml_Dispensing_Vial", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"), + "20ml分液瓶板": ("BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier", "3a192fa4-47db-3449-162a-eaf8aba57e27"), + "配液瓶(小)板": ("BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier", "3a190c8b-3284-af78-d29f-9a69463ad047"), + "配液瓶(小)": ("BIOYOND_PolymerStation_Small_Solution_Bottle", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"), + "配液瓶(大)板": ("BIOYOND_PolymerStation_4x_LargeSolutionBottleCarrier", "53e50377-32dc-4781-b3c0-5ce45bc7dc27"), + "配液瓶(大)": ("BIOYOND_PolymerStation_Large_Solution_Bottle", "19c52ad1-51c5-494f-8854-576f4ca9c6ca"), + "加样头(大)板": ("BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"), + "适配器块": ("BIOYOND_PolymerStation_AdapterBlock", "efc3bb32-d504-4890-91c0-b64ed3ac80cf"), + "枪头盒": ("BIOYOND_PolymerStation_TipBox", "3a192c2e-20f3-a44a-0334-c8301839d0b3"), + "枪头": ("BIOYOND_PolymerStation_Pipette_Tip", "b6196971-1050-46da-9927-333e8dea062d"), + # YB信息 } -# 步骤参数配置(各工作流的步骤UUID) -WORKFLOW_STEP_IDS = { - "reactor_taken_in": { - "config": "" +SOLID_LIQUID_MAPPINGS = { + # 固体 + "LiDFOB": { + "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469", + "code": "", + "barCode": "", + "name": "LiDFOB", + "unit": "g", + "parameters": "", + "quantity": "2", + "warningQuantity": "1", + "details": [] }, - "liquid_feeding_beaker": { - "liquid": "", - "observe": "" - }, - "liquid_feeding_vials_non_titration": { - "liquid": "", - "observe": "" - }, - "liquid_feeding_solvents": { - "liquid": "", - "observe": "" - }, - "solid_feeding_vials": { - "feeding": "", - "observe": "" - }, - "liquid_feeding_titration": { - "liquid": "", - "observe": "" - }, - "drip_back": { - "liquid": "", - "observe": "" - } + # "LiPF6": { + # "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469", + # "code": "", + # "barCode": "", + # "name": "LiPF6", + # "unit": "g", + # "parameters": "", + # "quantity": 2, + # "warningQuantity": 1, + # "details": [] + # }, + # "LiFSI": { + # "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469", + # "code": "", + # "barCode": "", + # "name": "LiFSI", + # "unit": "g", + # "parameters": {"Density": "1.533"}, + # "quantity": 2, + # "warningQuantity": 1, + # "details": [{}] + # }, + # "DTC": { + # "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469", + # "code": "", + # "barCode": "", + # "name": "DTC", + # "unit": "g", + # "parameters": {"Density": "1.533"}, + # "quantity": 2, + # "warningQuantity": 1, + # "details": [{}] + # }, + # "LiPO2F2": { + # "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469", + # "code": "", + # "barCode": "", + # "name": "LiPO2F2", + # "unit": "g", + # "parameters": {"Density": "1.533"}, + # "quantity": 2, + # "warningQuantity": 1, + # "details": [{}] + # }, + # 液体 + # "SA": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + # "EC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + # "VC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + # "AND": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + # "HTCN": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + # "DENE": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + # "TMSP": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + # "TMSB": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + # "EP": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + # "DEC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + # "EMC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + # "SN": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + # "DMC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + # "FEC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), } +WORKFLOW_MAPPINGS = {} + LOCATION_MAPPING = {} \ No newline at end of file diff --git a/unilabos/devices/workstation/workstation_http_service.py b/unilabos/devices/workstation/workstation_http_service.py index 12eb926..4565ede 100644 --- a/unilabos/devices/workstation/workstation_http_service.py +++ b/unilabos/devices/workstation/workstation_http_service.py @@ -71,11 +71,11 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler): if content_length > 0: post_data = self.rfile.read(content_length) request_data = json.loads(post_data.decode('utf-8')) - else: request_data = {} - logger.info(f"收到工作站报送: {endpoint} 收到接受数据:{request_data}") - # logger.info(f"收到的json数据: {request_data}") + + logger.info(f"收到工作站报送: {endpoint} - {request_data.get('token', 'unknown')}") + # 统一的报送端点路由(基于LIMS协议规范) if endpoint == '/report/step_finish': response = self._handle_step_finish_report(request_data) @@ -668,7 +668,7 @@ __all__ = [ if __name__ == "__main__": # 简单测试HTTP服务 - class BioyondWorkstation: + class DummyWorkstation: device_id = "WS-001" def process_step_finish_report(self, report_request): diff --git a/unilabos/registry/resources/bioyond/deck.yaml b/unilabos/registry/resources/bioyond/deck.yaml index 140dc5f..ef93705 100644 --- a/unilabos/registry/resources/bioyond/deck.yaml +++ b/unilabos/registry/resources/bioyond/deck.yaml @@ -22,7 +22,7 @@ BIOYOND_PolymerReactionStation_Deck: init_param_schema: {} registry_type: resource version: 1.0.0 -YB_Deck15: +YB_Deck16: category: - deck class: diff --git a/unilabos/resources/bioyond/bottles.py b/unilabos/resources/bioyond/bottles.py index b5fb087..40cb9ef 100644 --- a/unilabos/resources/bioyond/bottles.py +++ b/unilabos/resources/bioyond/bottles.py @@ -12,7 +12,7 @@ def BIOYOND_PolymerStation_Solid_Stock( """创建粉末瓶""" return Bottle( name=name, - diameter=diameter, + diameter=diameter,# 未知 height=height, max_volume=max_volume, barcode=barcode, diff --git a/unilabos/resources/bioyond/warehouses.py b/unilabos/resources/bioyond/warehouses.py index 22be38b..c546759 100644 --- a/unilabos/resources/bioyond/warehouses.py +++ b/unilabos/resources/bioyond/warehouses.py @@ -48,7 +48,7 @@ def bioyond_warehouse_1x2x2(name: str) -> WareHouse: item_dx=137.0, item_dy=96.0, item_dz=120.0, - category="warehouse", + category="YB_warehouse", ) def bioyond_warehouse_10x1x1(name: str) -> WareHouse: """创建BioYond 4x1x4仓库""" diff --git a/unilabos/resources/graphio.py b/unilabos/resources/graphio.py index ada7e8d..bca92c9 100644 --- a/unilabos/resources/graphio.py +++ b/unilabos/resources/graphio.py @@ -624,6 +624,8 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[st Returns: pylabrobot 格式的物料列表 """ + print("1:bioyond_materials:",bioyond_materials) + # print("2:type_mapping:",type_mapping) plr_materials = [] for material in bioyond_materials: From 01adf7ca9265affef5faad4b908387f54f883210 Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Fri, 24 Oct 2025 13:49:48 +0800 Subject: [PATCH 017/104] =?UTF-8?q?refactor:=20=E5=B0=86=20BIOYOND=5FPolym?= =?UTF-8?q?erStation=5F=20=E5=89=8D=E7=BC=80=E7=BB=9F=E4=B8=80=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=20YB=5F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重命名 bottles.py 中所有工厂函数:BIOYOND_PolymerStation_* -> YB_* - 重命名 bottle_carriers.py 中所有载具工厂函数和导入 - 更新 registry YAML 文件中的 module 引用 - 更新 MATERIAL_TYPE_MAPPINGS 配置中的类型字符串 - 更新测试文件和样例 JSON 中的类型引用 - 添加 YB_* 别名条目到 registry 以支持双键访问 --- .../experiments/reaction_station_bioyond.json | 14 +- .../reaction_station_bioyond_test.json | 6 +- test/resources/test_bottle_carrier.py | 8 +- test/resources/test_converter_bioyond.py | 14 +- test/resources/test_resourcetreeset.py | 14 +- .../workstation/bioyond_studio/config.py | 31 +++- .../resources/bioyond/bottle_carriers.yaml | 22 +-- .../registry/resources/bioyond/bottles.yaml | 169 ++++++++++++++++-- unilabos/resources/bioyond/bottle_carriers.py | 70 ++++---- unilabos/resources/bioyond/bottles.py | 28 +-- 10 files changed, 266 insertions(+), 110 deletions(-) diff --git a/test/experiments/reaction_station_bioyond.json b/test/experiments/reaction_station_bioyond.json index 013855e..ca8bd68 100644 --- a/test/experiments/reaction_station_bioyond.json +++ b/test/experiments/reaction_station_bioyond.json @@ -24,13 +24,13 @@ "Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a" }, "material_type_mappings": { - "烧杯": ["BIOYOND_PolymerStation_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"], - "试剂瓶": ["BIOYOND_PolymerStation_1BottleCarrier", ""], - "样品板": ["BIOYOND_PolymerStation_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"], - "分装板": ["BIOYOND_PolymerStation_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"], - "样品瓶": ["BIOYOND_PolymerStation_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"], - "90%分装小瓶": ["BIOYOND_PolymerStation_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"], - "10%分装小瓶": ["BIOYOND_PolymerStation_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"] + "烧杯": ["YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"], + "试剂瓶": ["YB_1BottleCarrier", ""], + "样品板": ["YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"], + "分装板": ["YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"], + "样品瓶": ["YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"], + "90%分装小瓶": ["YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"], + "10%分装小瓶": ["YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"] } }, "deck": { diff --git a/test/experiments/reaction_station_bioyond_test.json b/test/experiments/reaction_station_bioyond_test.json index 8446373..67a0d46 100644 --- a/test/experiments/reaction_station_bioyond_test.json +++ b/test/experiments/reaction_station_bioyond_test.json @@ -24,9 +24,9 @@ "Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a" }, "material_type_mappings": { - "烧杯": "BIOYOND_PolymerStation_1FlaskCarrier", - "试剂瓶": "BIOYOND_PolymerStation_1BottleCarrier", - "样品板": "BIOYOND_PolymerStation_6VialCarrier" + "烧杯": "YB_1FlaskCarrier", + "试剂瓶": "YB_1BottleCarrier", + "样品板": "YB_6VialCarrier" } }, "deck": { diff --git a/test/resources/test_bottle_carrier.py b/test/resources/test_bottle_carrier.py index c981eee..f0a2749 100644 --- a/test/resources/test_bottle_carrier.py +++ b/test/resources/test_bottle_carrier.py @@ -1,7 +1,7 @@ import pytest from unilabos.resources.bioyond.bottle_carriers import BIOYOND_Electrolyte_6VialCarrier, BIOYOND_Electrolyte_1BottleCarrier -from unilabos.resources.bioyond.bottles import BIOYOND_PolymerStation_Solid_Vial, BIOYOND_PolymerStation_Solution_Beaker, BIOYOND_PolymerStation_Reagent_Bottle +from unilabos.resources.bioyond.bottles import YB_Solid_Vial, YB_Solution_Beaker, YB_Reagent_Bottle def test_bottle_carrier() -> "BottleCarrier": @@ -16,9 +16,9 @@ def test_bottle_carrier() -> "BottleCarrier": print(f"1烧杯载架: {beaker_carrier.name}, 位置数: {len(beaker_carrier.sites)}") # 创建瓶子和烧杯 - powder_bottle = BIOYOND_PolymerStation_Solid_Vial("powder_bottle_01") - solution_beaker = BIOYOND_PolymerStation_Solution_Beaker("solution_beaker_01") - reagent_bottle = BIOYOND_PolymerStation_Reagent_Bottle("reagent_bottle_01") + powder_bottle = YB_Solid_Vial("powder_bottle_01") + solution_beaker = YB_Solution_Beaker("solution_beaker_01") + reagent_bottle = YB_Reagent_Bottle("reagent_bottle_01") print(f"\n创建的物料:") print(f"粉末瓶: {powder_bottle.name} - {powder_bottle.diameter}mm x {powder_bottle.height}mm, {powder_bottle.max_volume}μL") diff --git a/test/resources/test_converter_bioyond.py b/test/resources/test_converter_bioyond.py index 068a053..d87415b 100644 --- a/test/resources/test_converter_bioyond.py +++ b/test/resources/test_converter_bioyond.py @@ -12,13 +12,13 @@ lab_registry.setup() type_mapping = { - "烧杯": ("BIOYOND_PolymerStation_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"), - "试剂瓶": ("BIOYOND_PolymerStation_1BottleCarrier", ""), - "样品板": ("BIOYOND_PolymerStation_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"), - "分装板": ("BIOYOND_PolymerStation_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), - "样品瓶": ("BIOYOND_PolymerStation_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"), - "90%分装小瓶": ("BIOYOND_PolymerStation_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"), - "10%分装小瓶": ("BIOYOND_PolymerStation_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"), + "烧杯": ("YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"), + "试剂瓶": ("YB_1BottleCarrier", ""), + "样品板": ("YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"), + "分装板": ("YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), + "样品瓶": ("YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"), + "90%分装小瓶": ("YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"), + "10%分装小瓶": ("YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"), } diff --git a/test/resources/test_resourcetreeset.py b/test/resources/test_resourcetreeset.py index 1ba9ab2..ff5cfd0 100644 --- a/test/resources/test_resourcetreeset.py +++ b/test/resources/test_resourcetreeset.py @@ -13,13 +13,13 @@ lab_registry.setup() type_mapping = { - "烧杯": ("BIOYOND_PolymerStation_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"), - "试剂瓶": ("BIOYOND_PolymerStation_1BottleCarrier", ""), - "样品板": ("BIOYOND_PolymerStation_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"), - "分装板": ("BIOYOND_PolymerStation_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), - "样品瓶": ("BIOYOND_PolymerStation_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"), - "90%分装小瓶": ("BIOYOND_PolymerStation_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"), - "10%分装小瓶": ("BIOYOND_PolymerStation_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"), + "烧杯": ("YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"), + "试剂瓶": ("YB_1BottleCarrier", ""), + "样品板": ("YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"), + "分装板": ("YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), + "样品瓶": ("YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"), + "90%分装小瓶": ("YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"), + "10%分装小瓶": ("YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"), } diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 9294300..9a27320 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -26,7 +26,6 @@ BIOYOND_FULL_CONFIG = { "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "0.0.0.0"), # HTTP服务监听地址(0.0.0.0 表示监听所有网络接口) "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), "report_ip": os.getenv("BIOYOND_REPORT_IP", "172.21.32.172"), # 报送给 Bioyond 的本机IP地址(留空则自动检测) - # 调试模式 "debug_mode": os.getenv("BIOYOND_DEBUG_MODE", "False").lower() == "true", } @@ -142,13 +141,29 @@ WAREHOUSE_MAPPING = { # 物料类型配置 MATERIAL_TYPE_MAPPINGS = { - "烧杯": ("BIOYOND_PolymerStation_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"), - "试剂瓶": ("BIOYOND_PolymerStation_1BottleCarrier", ""), - "样品板": ("BIOYOND_PolymerStation_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"), - "分装板": ("BIOYOND_PolymerStation_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), - "样品瓶": ("BIOYOND_PolymerStation_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"), - "90%分装小瓶": ("BIOYOND_PolymerStation_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"), - "10%分装小瓶": ("BIOYOND_PolymerStation_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"), + "烧杯": ("YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"), + "试剂瓶": ("YB_1BottleCarrier", ""), + "样品板": ("YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"), + "分装板": ("YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), + "样品瓶": ("YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"), + "90%分装小瓶": ("YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"), + "10%分装小瓶": ("YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"), + "20ml分液瓶": ("YB_20ml_Dispensing_Vial", "3a192c2b-19e8-f0a3-035e-041ca8ca1035"), + "100ml液体": ("YB_100ml_Liquid_Bottle", "d37166b3-ecaa-481e-bd84-3032b795ba07"), + "液": ("YB_Liquid_Bottle", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), + "高粘液": ("YB_High_Viscosity_Liquid_Bottle", "abe8df30-563d-43d2-85e0-cabec59ddc16"), + "加样头(大)": ("YB_Large_Dispense_Head", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + "5ml分液瓶板": ("YB_6x5ml_DispensingVialCarrier", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"), + "5ml分液瓶": ("YB_5ml_Dispensing_Vial", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"), + "20ml分液瓶板": ("YB_6x20ml_DispensingVialCarrier", "3a192fa4-47db-3449-162a-eaf8aba57e27"), + "配液瓶(小)板": ("YB_6x_SmallSolutionBottleCarrier", "3a190c8b-3284-af78-d29f-9a69463ad047"), + "配液瓶(小)": ("YB_Small_Solution_Bottle", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"), + "配液瓶(大)板": ("YB_4x_LargeSolutionBottleCarrier", "53e50377-32dc-4781-b3c0-5ce45bc7dc27"), + "配液瓶(大)": ("YB_Large_Solution_Bottle", "19c52ad1-51c5-494f-8854-576f4ca9c6ca"), + "加样头(大)板": ("YB_6x_LargeDispenseHeadCarrier", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"), + "适配器块": ("YB_AdapterBlock", "efc3bb32-d504-4890-91c0-b64ed3ac80cf"), + "枪头盒": ("YB_TipBox", "3a192c2e-20f3-a44a-0334-c8301839d0b3"), + "枪头": ("YB_Pipette_Tip", "b6196971-1050-46da-9927-333e8dea062d"), } # 步骤参数配置(各工作流的步骤UUID) diff --git a/unilabos/registry/resources/bioyond/bottle_carriers.yaml b/unilabos/registry/resources/bioyond/bottle_carriers.yaml index 5889bcc..0e40c9c 100644 --- a/unilabos/registry/resources/bioyond/bottle_carriers.yaml +++ b/unilabos/registry/resources/bioyond/bottle_carriers.yaml @@ -2,7 +2,7 @@ category: - bottle_carriers class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_1BottleCarrier + module: unilabos.resources.bioyond.bottle_carriers:YB_1BottleCarrier type: pylabrobot description: 1BottleCarrier handles: [] @@ -14,7 +14,7 @@ category: - bottle_carriers class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_1FlaskCarrier + module: unilabos.resources.bioyond.bottle_carriers:YB_1FlaskCarrier type: pylabrobot description: 1FlaskCarrier handles: [] @@ -26,7 +26,7 @@ category: - bottle_carriers class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6StockCarrier + module: unilabos.resources.bioyond.bottle_carriers:YB_6StockCarrier type: pylabrobot description: 6StockCarrier handles: [] @@ -38,7 +38,7 @@ category: - bottle_carriers class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6VialCarrier + module: unilabos.resources.bioyond.bottle_carriers:YB_6VialCarrier type: pylabrobot description: 6VialCarrier handles: [] @@ -50,7 +50,7 @@ category: - yb3 class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier + module: unilabos.resources.bioyond.bottle_carriers:YB_6x5ml_DispensingVialCarrier type: pylabrobot description: 6x5ml_DispensingVialCarrier handles: [] @@ -62,7 +62,7 @@ category: - yb3 class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier + module: unilabos.resources.bioyond.bottle_carriers:YB_6x20ml_DispensingVialCarrier type: pylabrobot description: 6x20ml_DispensingVialCarrier handles: [] @@ -74,7 +74,7 @@ category: - yb3 class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier + module: unilabos.resources.bioyond.bottle_carriers:YB_6x_SmallSolutionBottleCarrier type: pylabrobot description: 6x_SmallSolutionBottleCarrier handles: [] @@ -86,7 +86,7 @@ category: - yb3 class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_4x_LargeSolutionBottleCarrier + module: unilabos.resources.bioyond.bottle_carriers:YB_4x_LargeSolutionBottleCarrier type: pylabrobot description: 4x_LargeSolutionBottleCarrier handles: [] @@ -98,7 +98,7 @@ category: - yb3 class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier + module: unilabos.resources.bioyond.bottle_carriers:YB_6x_LargeDispenseHeadCarrier type: pylabrobot description: 6x_LargeDispenseHeadCarrier handles: [] @@ -110,7 +110,7 @@ AdapterBlock: category: - yb3 class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_AdapterBlock + module: unilabos.resources.bioyond.bottle_carriers:YB_AdapterBlock type: pylabrobot description: AdapterBlock handles: [] @@ -122,7 +122,7 @@ TipBox: category: - yb3 class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_TipBox + module: unilabos.resources.bioyond.bottle_carriers:YB_TipBox type: pylabrobot description: TipBox handles: [] diff --git a/unilabos/registry/resources/bioyond/bottles.yaml b/unilabos/registry/resources/bioyond/bottles.yaml index 8d71f28..af6718a 100644 --- a/unilabos/registry/resources/bioyond/bottles.yaml +++ b/unilabos/registry/resources/bioyond/bottles.yaml @@ -2,7 +2,7 @@ Liquid_Vial: category: - bottles class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Liquid_Vial + module: unilabos.resources.bioyond.bottles:YB_Liquid_Vial type: pylabrobot handles: [] icon: '' @@ -12,7 +12,7 @@ Reagent_Bottle: category: - bottles class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Reagent_Bottle + module: unilabos.resources.bioyond.bottles:YB_Reagent_Bottle type: pylabrobot handles: [] icon: '' @@ -22,7 +22,7 @@ Solid_Stock: category: - bottles class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Solid_Stock + module: unilabos.resources.bioyond.bottles:YB_Solid_Stock type: pylabrobot handles: [] icon: '' @@ -32,7 +32,7 @@ Solid_Vial: category: - bottles class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Solid_Vial + module: unilabos.resources.bioyond.bottles:YB_Solid_Vial type: pylabrobot handles: [] icon: '' @@ -42,7 +42,7 @@ Solution_Beaker: category: - bottles class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Solution_Beaker + module: unilabos.resources.bioyond.bottles:YB_Solution_Beaker type: pylabrobot handles: [] icon: '' @@ -52,7 +52,7 @@ Solution_Beaker: category: - yb3 class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_100ml_Liquid_Bottle + module: unilabos.resources.bioyond.bottles:YB_100ml_Liquid_Bottle type: pylabrobot handles: [] icon: '' @@ -62,7 +62,7 @@ Liquid_Bottle: category: - yb3 class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Liquid_Bottle + module: unilabos.resources.bioyond.bottles:YB_Liquid_Bottle type: pylabrobot handles: [] icon: '' @@ -72,7 +72,7 @@ High_Viscosity_Liquid_Bottle: category: - yb3 class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_High_Viscosity_Liquid_Bottle + module: unilabos.resources.bioyond.bottles:YB_High_Viscosity_Liquid_Bottle type: pylabrobot handles: [] icon: '' @@ -82,7 +82,7 @@ Large_Dispense_Head: category: - yb3 class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Large_Dispense_Head + module: unilabos.resources.bioyond.bottles:YB_Large_Dispense_Head type: pylabrobot handles: [] icon: '' @@ -92,7 +92,7 @@ Large_Dispense_Head: category: - yb3 class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_5ml_Dispensing_Vial + module: unilabos.resources.bioyond.bottles:YB_5ml_Dispensing_Vial type: pylabrobot handles: [] icon: '' @@ -102,7 +102,7 @@ Large_Dispense_Head: category: - yb3 class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_20ml_Dispensing_Vial + module: unilabos.resources.bioyond.bottles:YB_20ml_Dispensing_Vial type: pylabrobot handles: [] icon: '' @@ -112,7 +112,7 @@ Small_Solution_Bottle: category: - yb3 class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Small_Solution_Bottle + module: unilabos.resources.bioyond.bottles:YB_Small_Solution_Bottle type: pylabrobot handles: [] icon: '' @@ -122,7 +122,7 @@ Large_Solution_Bottle: category: - yb3 class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Large_Solution_Bottle + module: unilabos.resources.bioyond.bottles:YB_Large_Solution_Bottle type: pylabrobot handles: [] icon: '' @@ -132,9 +132,150 @@ Pipette_Tip: category: - yb3 class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Pipette_Tip + module: unilabos.resources.bioyond.bottles:YB_Pipette_Tip type: pylabrobot handles: [] icon: '' init_param_schema: {} version: 1.0.0 + +YB_Liquid_Vial: + category: + - bottles + class: + module: unilabos.resources.bioyond.bottles:YB_Liquid_Vial + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +YB_Reagent_Bottle: + category: + - bottles + class: + module: unilabos.resources.bioyond.bottles:YB_Reagent_Bottle + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +YB_Solid_Stock: + category: + - bottles + class: + module: unilabos.resources.bioyond.bottles:YB_Solid_Stock + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +YB_Solid_Vial: + category: + - bottles + class: + module: unilabos.resources.bioyond.bottles:YB_Solid_Vial + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +YB_Solution_Beaker: + category: + - bottles + class: + module: unilabos.resources.bioyond.bottles:YB_Solution_Beaker + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +YB_100ml_Liquid_Bottle: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottles:YB_100ml_Liquid_Bottle + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +YB_Liquid_Bottle: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottles:YB_Liquid_Bottle + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +YB_High_Viscosity_Liquid_Bottle: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottles:YB_High_Viscosity_Liquid_Bottle + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +YB_Large_Dispense_Head: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottles:YB_Large_Dispense_Head + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +YB_5ml_Dispensing_Vial: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottles:YB_5ml_Dispensing_Vial + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +YB_20ml_Dispensing_Vial: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottles:YB_20ml_Dispensing_Vial + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +YB_Small_Solution_Bottle: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottles:YB_Small_Solution_Bottle + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +YB_Large_Solution_Bottle: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottles:YB_Large_Solution_Bottle + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 +YB_Pipette_Tip: + category: + - yb3 + class: + module: unilabos.resources.bioyond.bottles:YB_Pipette_Tip + type: pylabrobot + handles: [] + icon: '' + init_param_schema: {} + version: 1.0.0 diff --git a/unilabos/resources/bioyond/bottle_carriers.py b/unilabos/resources/bioyond/bottle_carriers.py index ab0b656..76e3a93 100644 --- a/unilabos/resources/bioyond/bottle_carriers.py +++ b/unilabos/resources/bioyond/bottle_carriers.py @@ -2,17 +2,17 @@ from pylabrobot.resources import create_homogeneous_resources, Coordinate, Resou from unilabos.resources.itemized_carrier import Bottle, BottleCarrier from unilabos.resources.bioyond.bottles import ( - BIOYOND_PolymerStation_Solid_Stock, - BIOYOND_PolymerStation_Solid_Vial, - BIOYOND_PolymerStation_Liquid_Vial, - BIOYOND_PolymerStation_Solution_Beaker, - BIOYOND_PolymerStation_Reagent_Bottle, - BIOYOND_PolymerStation_5ml_Dispensing_Vial, - BIOYOND_PolymerStation_20ml_Dispensing_Vial, - BIOYOND_PolymerStation_Small_Solution_Bottle, - BIOYOND_PolymerStation_Large_Solution_Bottle, - BIOYOND_PolymerStation_Large_Dispense_Head, - BIOYOND_PolymerStation_Pipette_Tip + YB_Solid_Stock, + YB_Solid_Vial, + YB_Liquid_Vial, + YB_Solution_Beaker, + YB_Reagent_Bottle, + YB_5ml_Dispensing_Vial, + YB_20ml_Dispensing_Vial, + YB_Small_Solution_Bottle, + YB_Large_Solution_Bottle, + YB_Large_Dispense_Head, + YB_Pipette_Tip ) # 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial @@ -63,7 +63,7 @@ def BIOYOND_Electrolyte_6VialCarrier(name: str) -> BottleCarrier: carrier.num_items_y = 2 carrier.num_items_z = 1 for i in range(6): - carrier[i] = BIOYOND_PolymerStation_Solid_Vial(f"{name}_vial_{i+1}") + carrier[i] = YB_Solid_Vial(f"{name}_vial_{i+1}") return carrier @@ -100,11 +100,11 @@ def BIOYOND_Electrolyte_1BottleCarrier(name: str) -> BottleCarrier: carrier.num_items_x = 1 carrier.num_items_y = 1 carrier.num_items_z = 1 - carrier[0] = BIOYOND_PolymerStation_Solution_Beaker(f"{name}_beaker_1") + carrier[0] = YB_Solution_Beaker(f"{name}_beaker_1") return carrier -def BIOYOND_PolymerStation_6StockCarrier(name: str) -> BottleCarrier: +def YB_6StockCarrier(name: str) -> BottleCarrier: """6瓶载架 - 2x3布局""" # 载架尺寸 (mm) @@ -151,11 +151,11 @@ def BIOYOND_PolymerStation_6StockCarrier(name: str) -> BottleCarrier: carrier.num_items_z = 1 ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] # 自定义顺序 for i in range(6): - carrier[i] = BIOYOND_PolymerStation_Solid_Stock(f"{name}_vial_{ordering[i]}") + carrier[i] = YB_Solid_Stock(f"{name}_vial_{ordering[i]}") return carrier -def BIOYOND_PolymerStation_6VialCarrier(name: str) -> BottleCarrier: +def YB_6VialCarrier(name: str) -> BottleCarrier: """6瓶载架 - 2x3布局""" # 载架尺寸 (mm) @@ -202,13 +202,13 @@ def BIOYOND_PolymerStation_6VialCarrier(name: str) -> BottleCarrier: carrier.num_items_z = 1 ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] # 自定义顺序 for i in range(3): - carrier[i] = BIOYOND_PolymerStation_Solid_Vial(f"{name}_solidvial_{ordering[i]}") + carrier[i] = YB_Solid_Vial(f"{name}_solidvial_{ordering[i]}") for i in range(3, 6): - carrier[i] = BIOYOND_PolymerStation_Liquid_Vial(f"{name}_liquidvial_{ordering[i]}") + carrier[i] = YB_Liquid_Vial(f"{name}_liquidvial_{ordering[i]}") return carrier -def BIOYOND_PolymerStation_1BottleCarrier(name: str) -> BottleCarrier: +def YB_1BottleCarrier(name: str) -> BottleCarrier: """1瓶载架 - 单个中央位置""" # 载架尺寸 (mm) @@ -241,11 +241,11 @@ def BIOYOND_PolymerStation_1BottleCarrier(name: str) -> BottleCarrier: carrier.num_items_x = 1 carrier.num_items_y = 1 carrier.num_items_z = 1 - carrier[0] = BIOYOND_PolymerStation_Reagent_Bottle(f"{name}_flask_1") + carrier[0] = YB_Reagent_Bottle(f"{name}_flask_1") return carrier -def BIOYOND_PolymerStation_1FlaskCarrier(name: str) -> BottleCarrier: +def YB_1FlaskCarrier(name: str) -> BottleCarrier: """1瓶载架 - 单个中央位置""" # 载架尺寸 (mm) @@ -278,11 +278,11 @@ def BIOYOND_PolymerStation_1FlaskCarrier(name: str) -> BottleCarrier: carrier.num_items_x = 1 carrier.num_items_y = 1 carrier.num_items_z = 1 - carrier[0] = BIOYOND_PolymerStation_Reagent_Bottle(f"{name}_bottle_1") + carrier[0] = YB_Reagent_Bottle(f"{name}_bottle_1") return carrier -def BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier(name: str) -> BottleCarrier: +def YB_6x5ml_DispensingVialCarrier(name: str) -> BottleCarrier: """5ml分液瓶板 - 4x2布局,8个位置""" # 载架尺寸 (mm) @@ -328,11 +328,11 @@ def BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier(name: str) -> BottleCarri carrier.num_items_z = 1 ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"] for i in range(8): - carrier[i] = BIOYOND_PolymerStation_5ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}") + carrier[i] = YB_5ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}") return carrier -def BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier(name: str) -> BottleCarrier: +def YB_6x20ml_DispensingVialCarrier(name: str) -> BottleCarrier: """20ml分液瓶板 - 4x2布局,8个位置""" # 载架尺寸 (mm) @@ -378,11 +378,11 @@ def BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier(name: str) -> BottleCarr carrier.num_items_z = 1 ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"] for i in range(8): - carrier[i] = BIOYOND_PolymerStation_20ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}") + carrier[i] = YB_20ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}") return carrier -def BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier(name: str) -> BottleCarrier: +def YB_6x_SmallSolutionBottleCarrier(name: str) -> BottleCarrier: """配液瓶(小)板 - 4x2布局,8个位置""" # 载架尺寸 (mm) @@ -428,11 +428,11 @@ def BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier(name: str) -> BottleCar carrier.num_items_z = 1 ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"] for i in range(8): - carrier[i] = BIOYOND_PolymerStation_Small_Solution_Bottle(f"{name}_bottle_{ordering[i]}") + carrier[i] = YB_Small_Solution_Bottle(f"{name}_bottle_{ordering[i]}") return carrier -def BIOYOND_PolymerStation_4x_LargeSolutionBottleCarrier(name: str) -> BottleCarrier: +def YB_4x_LargeSolutionBottleCarrier(name: str) -> BottleCarrier: """配液瓶(大)板 - 2x2布局,4个位置""" # 载架尺寸 (mm) @@ -478,11 +478,11 @@ def BIOYOND_PolymerStation_4x_LargeSolutionBottleCarrier(name: str) -> BottleCar carrier.num_items_z = 1 ordering = ["A1", "A2", "B1", "B2"] for i in range(4): - carrier[i] = BIOYOND_PolymerStation_Large_Solution_Bottle(f"{name}_bottle_{ordering[i]}") + carrier[i] = YB_Large_Solution_Bottle(f"{name}_bottle_{ordering[i]}") return carrier -def BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier(name: str) -> BottleCarrier: +def YB_6x_LargeDispenseHeadCarrier(name: str) -> BottleCarrier: """加样头(大)板 - 1x1布局,1个位置""" # 载架尺寸 (mm) @@ -526,11 +526,11 @@ def BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier(name: str) -> BottleCarri carrier.num_items_x = 1 carrier.num_items_y = 1 carrier.num_items_z = 1 - carrier[0] = BIOYOND_PolymerStation_Large_Dispense_Head(f"{name}_head_1") + carrier[0] = YB_Large_Dispense_Head(f"{name}_head_1") return carrier -def BIOYOND_PolymerStation_AdapterBlock(name: str) -> BottleCarrier: +def YB_AdapterBlock(name: str) -> BottleCarrier: """适配器块 - 单个中央位置""" # 载架尺寸 (mm) @@ -567,7 +567,7 @@ def BIOYOND_PolymerStation_AdapterBlock(name: str) -> BottleCarrier: return carrier -def BIOYOND_PolymerStation_TipBox(name: str) -> BottleCarrier: +def YB_TipBox(name: str) -> BottleCarrier: """枪头盒 - 8x12布局,96个位置""" # 载架尺寸 (mm) @@ -615,6 +615,6 @@ def BIOYOND_PolymerStation_TipBox(name: str) -> BottleCarrier: for i in range(96): row = chr(65 + i // 12) # A-H col = (i % 12) + 1 # 1-12 - carrier[i] = BIOYOND_PolymerStation_Pipette_Tip(f"{name}_tip_{row}{col}") + carrier[i] = YB_Pipette_Tip(f"{name}_tip_{row}{col}") return carrier diff --git a/unilabos/resources/bioyond/bottles.py b/unilabos/resources/bioyond/bottles.py index b5fb087..efabea2 100644 --- a/unilabos/resources/bioyond/bottles.py +++ b/unilabos/resources/bioyond/bottles.py @@ -2,7 +2,7 @@ from unilabos.resources.itemized_carrier import Bottle, BottleCarrier # 工厂函数 -def BIOYOND_PolymerStation_Solid_Stock( +def YB_Solid_Stock( name: str, diameter: float = 20.0, height: float = 100.0, @@ -20,7 +20,7 @@ def BIOYOND_PolymerStation_Solid_Stock( ) -def BIOYOND_PolymerStation_Solid_Vial( +def YB_Solid_Vial( name: str, diameter: float = 25.0, height: float = 60.0, @@ -38,7 +38,7 @@ def BIOYOND_PolymerStation_Solid_Vial( ) -def BIOYOND_PolymerStation_Liquid_Vial( +def YB_Liquid_Vial( name: str, diameter: float = 25.0, height: float = 60.0, @@ -56,7 +56,7 @@ def BIOYOND_PolymerStation_Liquid_Vial( ) -def BIOYOND_PolymerStation_Solution_Beaker( +def YB_Solution_Beaker( name: str, diameter: float = 60.0, height: float = 70.0, @@ -74,7 +74,7 @@ def BIOYOND_PolymerStation_Solution_Beaker( ) -def BIOYOND_PolymerStation_Reagent_Bottle( +def YB_Reagent_Bottle( name: str, diameter: float = 70.0, height: float = 120.0, @@ -92,7 +92,7 @@ def BIOYOND_PolymerStation_Reagent_Bottle( ) -def BIOYOND_PolymerStation_100ml_Liquid_Bottle( +def YB_100ml_Liquid_Bottle( name: str, diameter: float = 50.0, height: float = 80.0, @@ -110,7 +110,7 @@ def BIOYOND_PolymerStation_100ml_Liquid_Bottle( ) -def BIOYOND_PolymerStation_Liquid_Bottle( +def YB_Liquid_Bottle( name: str, diameter: float = 40.0, height: float = 70.0, @@ -128,7 +128,7 @@ def BIOYOND_PolymerStation_Liquid_Bottle( ) -def BIOYOND_PolymerStation_High_Viscosity_Liquid_Bottle( +def YB_High_Viscosity_Liquid_Bottle( name: str, diameter: float = 45.0, height: float = 75.0, @@ -146,7 +146,7 @@ def BIOYOND_PolymerStation_High_Viscosity_Liquid_Bottle( ) -def BIOYOND_PolymerStation_Large_Dispense_Head( +def YB_Large_Dispense_Head( name: str, diameter: float = 35.0, height: float = 90.0, @@ -164,7 +164,7 @@ def BIOYOND_PolymerStation_Large_Dispense_Head( ) -def BIOYOND_PolymerStation_5ml_Dispensing_Vial( +def YB_5ml_Dispensing_Vial( name: str, diameter: float = 15.0, height: float = 45.0, @@ -182,7 +182,7 @@ def BIOYOND_PolymerStation_5ml_Dispensing_Vial( ) -def BIOYOND_PolymerStation_20ml_Dispensing_Vial( +def YB_20ml_Dispensing_Vial( name: str, diameter: float = 20.0, height: float = 65.0, @@ -200,7 +200,7 @@ def BIOYOND_PolymerStation_20ml_Dispensing_Vial( ) -def BIOYOND_PolymerStation_Small_Solution_Bottle( +def YB_Small_Solution_Bottle( name: str, diameter: float = 35.0, height: float = 60.0, @@ -218,7 +218,7 @@ def BIOYOND_PolymerStation_Small_Solution_Bottle( ) -def BIOYOND_PolymerStation_Large_Solution_Bottle( +def YB_Large_Solution_Bottle( name: str, diameter: float = 55.0, height: float = 90.0, @@ -236,7 +236,7 @@ def BIOYOND_PolymerStation_Large_Solution_Bottle( ) -def BIOYOND_PolymerStation_Pipette_Tip( +def YB_Pipette_Tip( name: str, diameter: float = 10.0, height: float = 50.0, From 55b678cd37cc287a9f2b1ee1a89883155c1131bf Mon Sep 17 00:00:00 2001 From: calvincao Date: Fri, 24 Oct 2025 14:22:39 +0800 Subject: [PATCH 018/104] fix: update report IP address in configuration and clean up parameters in SOLID_LIQUID_MAPPINGS --- .../devices/workstation/bioyond_studio/config.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 90e22bc..fb8ef92 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -21,7 +21,7 @@ API_CONFIG = { # HTTP 服务配置 "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "0.0.0.0"), # HTTP服务监听地址(0.0.0.0 表示监听所有网络接口) "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), - "report_ip": os.getenv("BIOYOND_REPORT_IP", "172.21.32.172"), # 报送给 Bioyond 的本机IP地址(留空则自动检测) + "report_ip": os.getenv("BIOYOND_REPORT_IP", "172.21.32.22"), # 报送给 Bioyond 的本机IP地址(留空则自动检测) # 调试模式 "debug_mode": False, } @@ -112,10 +112,10 @@ SOLID_LIQUID_MAPPINGS = { # "barCode": "", # "name": "LiFSI", # "unit": "g", - # "parameters": {"Density": "1.533"}, + # "parameters": "", # "quantity": 2, # "warningQuantity": 1, - # "details": [{}] + # "details": [] # }, # "DTC": { # "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469", @@ -123,10 +123,10 @@ SOLID_LIQUID_MAPPINGS = { # "barCode": "", # "name": "DTC", # "unit": "g", - # "parameters": {"Density": "1.533"}, + # "parameters": "", # "quantity": 2, # "warningQuantity": 1, - # "details": [{}] + # "details": [] # }, # "LiPO2F2": { # "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469", @@ -134,10 +134,10 @@ SOLID_LIQUID_MAPPINGS = { # "barCode": "", # "name": "LiPO2F2", # "unit": "g", - # "parameters": {"Density": "1.533"}, + # "parameters": "", # "quantity": 2, # "warningQuantity": 1, - # "details": [{}] + # "details": [] # }, # 液体 # "SA": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), From 6ad0157b50af933f5588612679bc3f2af25ec7b6 Mon Sep 17 00:00:00 2001 From: calvincao Date: Fri, 24 Oct 2025 16:37:11 +0800 Subject: [PATCH 019/104] feat: add new warehouse configurations and update site UUIDs in bioyond_studio config --- .../workstation/bioyond_studio/config.py | 86 ++++++++++++++++++- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index fb8ef92..4e2fddd 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -31,7 +31,7 @@ WAREHOUSE_MAPPING = { "粉末加样头堆栈": { "uuid": "", "site_uuids": { - "A01": "3a19da56-1379-20c8-5886-f7c4fbcb5733", + "A01": "3a19da56-1379-ff7c-1745-07e200b44ce2", "B01": "3a19da56-1379-2424-d751-fe6e94cef938", "C01": "3a19da56-1379-271c-03e3-6bdb590e395e", "D01": "3a19da56-1379-277f-2b1b-0d11f7cf92c6", @@ -52,7 +52,89 @@ WAREHOUSE_MAPPING = { "S01": "3a19da56-1379-f924-7f68-df1fa51489f4", "T01": "3a19da56-1379-ff7c-1745-07e200b44ce2" } - } + }, + "配液站内试剂仓库": { + "uuid": "", + "site_uuids": { + "A01": "3a19da43-57b5-294f-d663-154a1cc32270", + "B01": "3a19da43-57b5-7394-5f49-54efe2c9bef2", + "C01": "3a19da43-57b5-5e75-552f-8dbd0ad1075f", + "A02": "3a19da43-57b5-8441-db94-b4d3875a4b6c", + "B02": "3a19da43-57b5-3e41-c181-5119dddaf50c", + "C02": "3a19da43-57b5-269b-282d-fba61fe8ce96", + "A03": "3a19da43-57b5-7c1e-d02e-c40e8c33f8a1", + "B03": "3a19da43-57b5-659f-621f-1dcf3f640363", + "C03": "3a19da43-57b5-855a-6e71-f398e376dee1", + } + }, + "试剂替换仓库": { + "uuid": "", + "site_uuids": { + "A01": "3a19da51-8f4e-30f3-ea08-4f8498e9b097", + "B01": "3a19da51-8f4e-1da7-beb0-80a4a01e67a8", + "C01": "3a19da51-8f4e-337d-2675-bfac46880b06", + "D01": "3a19da51-8f4e-e514-b92c-9c44dc5e489d", + "E01": "3a19da51-8f4e-22d1-dd5b-9774ddc80402", + "F01": "3a19da51-8f4e-273a-4871-dff41c29bfd9", + "G01": "3a19da51-8f4e-b32f-454f-74bc1a665653", + "H01": "3a19da51-8f4e-8c93-68c9-0b4382320f59", + "I01": "3a19da51-8f4e-360c-0149-291b47c6089b", + "J01": "3a19da51-8f4e-4152-9bca-8d64df8c1af0" + } + }, + "自动堆栈-左": { + "uuid": "", + "site_uuids": { + "A01": "3a19debc-84b5-4c1c-d3a1-26830cf273ff", + "A02": "3a19debc-84b5-033b-b31f-6b87f7c2bf52", + "B01": "3a19debc-84b5-3924-172f-719ab01b125c", + "B02": "3a19debc-84b5-aad8-70c6-b8c6bb2d8750" + } + }, + "自动堆栈-右": { + "uuid": "", + "site_uuids": { + "A01": "3a19debe-5200-7df2-1dd9-7d202f158864", + "A02": "3a19debe-5200-573b-6120-8b51f50e1e50", + "B01": "3a19debe-5200-7cd8-7666-851b0a97e309", + "B02": "3a19debe-5200-e6d3-96a3-baa6e3d5e484" + } + }, + "手动堆栈": { + "uuid": "", + "site_uuids": { + "A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea", + "A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe", + "A03": "3a19deae-2c7a-5876-c454-6b7e224ca927", + "B01": "3a19deae-2c7a-2426-6d71-e9de3cb158b1", + "B02": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3", + "B03": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a", + "C01": "3a19deae-2c7a-32bc-768e-556647e292f3", + "C02": "3a19deae-2c7a-e97a-8484-f5a4599447c4", + "C03": "3a19deae-2c7a-3056-6504-10dc73fbc276", + "D01": "3a19deae-2c7a-ffad-875e-8c4cda61d440", + "D02": "3a19deae-2c7a-61be-601c-b6fb5610499a", + "D03": "3a19deae-2c7a-c0f7-05a7-e3fe2491e560", + "E01": "3a19deae-2c7a-a6f4-edd1-b436a7576363", + "E02": "3a19deae-2c7a-4367-96dd-1ca2186f4910", + "E03": "3a19deae-2c7a-b163-2219-23df15200311", + "F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a", + "F02": "3a19deae-2c7a-a194-ea63-8b342b8d8679", + "F03": "3a19deae-2c7a-f7c4-12bd-425799425698", + "G01": "3a19deae-2c7a-0b56-72f1-8ab86e53b955", + "G02": "3a19deae-2c7a-204e-95ed-1f1950f28343", + "G03": "3a19deae-2c7a-392b-62f1-4907c66343f8", + "H01": "3a19deae-2c7a-5602-e876-d27aca4e3201", + "H02": "3a19deae-2c7a-f15c-70e0-25b58a8c9702", + "H03": "3a19deae-2c7a-780b-8965-2e1345f7e834", + "I01": "3a19deae-2c7a-8849-e172-07de14ede928", + "I02": "3a19deae-2c7a-4772-a37f-ff99270bafc0", + "I03": "3a19deae-2c7a-cce7-6e4a-25ea4a2068c4", + "J01": "3a19deae-2c7a-1848-de92-b5d5ed054cc6", + "J02": "3a19deae-2c7a-1d45-b4f8-6f866530e205", + "J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9" + } + }, } # 物料类型配置 From 2901d72b4b730a7c4f0dd030a8d9d62d4dbc63f2 Mon Sep 17 00:00:00 2001 From: calvincao Date: Sat, 25 Oct 2025 13:50:41 +0800 Subject: [PATCH 020/104] feat: add button battery assembly station resources and configuration files - Introduced new Python modules for button battery assembly, including resource classes and configurations. - Added JSON and CSV files for resource definitions and device configurations. - Created initial setup for the coin cell assembly workstation, including material handling and resource management. --- .../coin_cell_assembly/__init__.py | 0 .../button_battery_station.py | 1006 ++ .../button_battery_station_big.py | 1412 ++ ..._battery_station_resources_unilab_big.json | 1895 ++ .../coin_cell_assembly/cellconfig.py | 114 + .../coin_cell_assembly/celljson.json | 1332 ++ .../coin_cell_assembly/coin_cell_assembly.py | 1163 ++ .../coin_cell_assembly_0910.csv | 44 + .../coin_cell_assembly_a.csv | 46 + .../coin_cell_assembly_old.py | 1149 ++ .../coin_cell_assembly_system_wuliao.py | 135 + .../coin_cell_assembly/new_cellconfig.json | 1925 ++ .../coin_cell_assembly/new_cellconfig2.json | 1925 ++ .../coin_cell_assembly/new_cellconfig3.json | 1925 ++ .../coin_cell_assembly/new_cellconfig3a.json | 1956 +++ .../coin_cell_assembly/new_cellconfig3b.json | 2589 +++ .../coin_cell_assembly/new_cellconfig3c.json | 691 + .../coin_cell_assembly/new_cellconfig4.json | 14472 ++++++++++++++++ .../workstation/coin_cell_assembly/test.ipynb | 750 + .../coin_cell_assembly/work_station.yaml | 6674 +++++++ 20 files changed, 41203 insertions(+) create mode 100644 unilabos/devices/workstation/coin_cell_assembly/__init__.py create mode 100644 unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py create mode 100644 unilabos/devices/workstation/coin_cell_assembly/button_battery_station_big.py create mode 100644 unilabos/devices/workstation/coin_cell_assembly/button_battery_station_resources_unilab_big.json create mode 100644 unilabos/devices/workstation/coin_cell_assembly/cellconfig.py create mode 100644 unilabos/devices/workstation/coin_cell_assembly/celljson.json create mode 100644 unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py create mode 100644 unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_0910.csv create mode 100644 unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv create mode 100644 unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_old.py create mode 100644 unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_system_wuliao.py create mode 100644 unilabos/devices/workstation/coin_cell_assembly/new_cellconfig.json create mode 100644 unilabos/devices/workstation/coin_cell_assembly/new_cellconfig2.json create mode 100644 unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3.json create mode 100644 unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3a.json create mode 100644 unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3b.json create mode 100644 unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json create mode 100644 unilabos/devices/workstation/coin_cell_assembly/new_cellconfig4.json create mode 100644 unilabos/devices/workstation/coin_cell_assembly/test.ipynb create mode 100644 unilabos/devices/workstation/coin_cell_assembly/work_station.yaml diff --git a/unilabos/devices/workstation/coin_cell_assembly/__init__.py b/unilabos/devices/workstation/coin_cell_assembly/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py b/unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py new file mode 100644 index 0000000..eae09b8 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py @@ -0,0 +1,1006 @@ +""" +纽扣电池组装工作站物料类定义 +Button Battery Assembly Station Resource Classes +""" + +from __future__ import annotations + +from collections import OrderedDict +from typing import Any, Dict, List, Optional, TypedDict, Union, cast + +from pylabrobot.resources.coordinate import Coordinate +from pylabrobot.resources.container import Container +from pylabrobot.resources.deck import Deck +from pylabrobot.resources.itemized_resource import ItemizedResource +from pylabrobot.resources.resource import Resource +from pylabrobot.resources.resource_stack import ResourceStack +from pylabrobot.resources.tip_rack import TipRack, TipSpot +from pylabrobot.resources.trash import Trash +from pylabrobot.resources.utils import create_ordered_items_2d + + +class ElectrodeSheetState(TypedDict): + diameter: float # 直径 (mm) + thickness: float # 厚度 (mm) + mass: float # 质量 (g) + material_type: str # 材料类型(正极、负极、隔膜、弹片、垫片、铝箔等) + height: float + electrolyte_name: str + data_electrolyte_code: str + open_circuit_voltage: float + assembly_pressure: float + electrolyte_volume: float + + info: Optional[str] # 附加信息 + +class ElectrodeSheet(Resource): + """极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料""" + + def __init__( + self, + name: str = "极片", + size_x=10, + size_y=10, + size_z=10, + category: str = "electrode_sheet", + model: Optional[str] = None, + ): + """初始化极片 + + Args: + name: 极片名称 + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + category=category, + model=model, + ) + self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState( + diameter=14, + thickness=0.1, + mass=0.5, + material_type="copper", + info=None + ) + + # TODO: 这个还要不要?给self._unilabos_state赋值的? + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + #序列化 + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + +# TODO: 这个应该只能放一个极片 +class MaterialHoleState(TypedDict): + diameter: int + depth: int + max_sheets: int + info: Optional[str] # 附加信息 + +class MaterialHole(Resource): + """料板洞位类""" + children: List[ElectrodeSheet] = [] + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + category: str = "material_hole", + **kwargs + ): + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + category=category, + ) + self._unilabos_state: MaterialHoleState = MaterialHoleState( + diameter=20, + depth=10, + max_sheets=1, + info=None + ) + + def get_all_sheet_info(self): + info_list = [] + for sheet in self.children: + info_list.append(sheet._unilabos_state["info"]) + return info_list + + #这个函数函数好像没用,一般不会集中赋值质量 + def set_all_sheet_mass(self): + for sheet in self.children: + sheet._unilabos_state["mass"] = 0.5 # 示例:设置质量为0.5g + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + #移动极片前先取出对象 + def get_sheet_with_name(self, name: str) -> Optional[ElectrodeSheet]: + for sheet in self.children: + if sheet.name == name: + return sheet + return None + + def has_electrode_sheet(self) -> bool: + """检查洞位是否有极片""" + return len(self.children) > 0 + + def assign_child_resource( + self, + resource: ElectrodeSheet, + location: Optional[Coordinate], + reassign: bool = True, + ): + """放置极片""" + # TODO: 这里要改,diameter找不到,加入._unilabos_state后应该没问题 + #if resource._unilabos_state["diameter"] > self._unilabos_state["diameter"]: + # raise ValueError(f"极片直径 {resource._unilabos_state['diameter']} 超过洞位直径 {self._unilabos_state['diameter']}") + #if len(self.children) >= self._unilabos_state["max_sheets"]: + # raise ValueError(f"洞位已满,无法放置更多极片") + super().assign_child_resource(resource, location, reassign) + + # 根据children的编号取物料对象。 + def get_electrode_sheet_info(self, index: int) -> ElectrodeSheet: + return self.children[index] + + + +class MaterialPlateState(TypedDict): + hole_spacing_x: float + hole_spacing_y: float + hole_diameter: float + info: Optional[str] # 附加信息 + +class MaterialPlate(ItemizedResource[MaterialHole]): + """料板类 - 4x4个洞位,每个洞位放1个极片""" + + children: List[MaterialHole] + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + ordered_items: Optional[Dict[str, MaterialHole]] = None, + ordering: Optional[OrderedDict[str, str]] = None, + category: str = "material_plate", + model: Optional[str] = None, + fill: bool = False + ): + """初始化料板 + + Args: + name: 料板名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + hole_diameter: 洞直径 (mm) + hole_depth: 洞深度 (mm) + hole_spacing_x: X方向洞位间距 (mm) + hole_spacing_y: Y方向洞位间距 (mm) + number: 编号 + category: 类别 + model: 型号 + """ + self._unilabos_state: MaterialPlateState = MaterialPlateState( + hole_spacing_x=24.0, + hole_spacing_y=24.0, + hole_diameter=20.0, + info="", + ) + # 创建4x4的洞位 + # TODO: 这里要改,对应不同形状 + holes = create_ordered_items_2d( + klass=MaterialHole, + num_items_x=4, + num_items_y=4, + dx=(size_x - 4 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中 + dy=(size_y - 4 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中 + dz=size_z, + item_dx=self._unilabos_state["hole_spacing_x"], + item_dy=self._unilabos_state["hole_spacing_y"], + size_x = 16, + size_y = 16, + size_z = 16, + ) + if fill: + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=holes, + category=category, + model=model, + ) + else: + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=ordered_items, + ordering=ordering, + category=category, + model=model, + ) + + def update_locations(self): + # TODO:调多次相加 + holes = create_ordered_items_2d( + klass=MaterialHole, + num_items_x=4, + num_items_y=4, + dx=(self._size_x - 3 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中 + dy=(self._size_y - 3 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中 + dz=self._size_z, + item_dx=self._unilabos_state["hole_spacing_x"], + item_dy=self._unilabos_state["hole_spacing_y"], + size_x = 1, + size_y = 1, + size_z = 1, + ) + for item, original_item in zip(holes.items(), self.children): + original_item.location = item[1].location + + +class PlateSlot(ResourceStack): + """板槽位类 - 1个槽上能堆放8个板,移板只能操作最上方的板""" + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + max_plates: int = 8, + category: str = "plate_slot", + model: Optional[str] = None + ): + """初始化板槽位 + + Args: + name: 槽位名称 + max_plates: 最大板数量 + category: 类别 + """ + super().__init__( + name=name, + direction="z", # Z方向堆叠 + resources=[], + ) + self.max_plates = max_plates + self.category = category + + def can_add_plate(self) -> bool: + """检查是否可以添加板""" + return len(self.children) < self.max_plates + + def add_plate(self, plate: MaterialPlate) -> None: + """添加料板""" + if not self.can_add_plate(): + raise ValueError(f"槽位 {self.name} 已满,无法添加更多板") + self.assign_child_resource(plate) + + def get_top_plate(self) -> MaterialPlate: + """获取最上方的板""" + if len(self.children) == 0: + raise ValueError(f"槽位 {self.name} 为空") + return cast(MaterialPlate, self.get_top_item()) + + def take_top_plate(self) -> MaterialPlate: + """取出最上方的板""" + top_plate = self.get_top_plate() + self.unassign_child_resource(top_plate) + return top_plate + + def can_access_for_picking(self) -> bool: + """检查是否可以进行取料操作(只有最上方的板能进行取料操作)""" + return len(self.children) > 0 + + def serialize(self) -> dict: + return { + **super().serialize(), + "max_plates": self.max_plates, + } + + +class ClipMagazineHole(Container): + """子弹夹洞位类""" + + def __init__( + self, + name: str, + diameter: float, + depth: float, + max_sheets: int = 100, + category: str = "clip_magazine_hole", + ): + """初始化子弹夹洞位 + + Args: + name: 洞位名称 + diameter: 洞直径 (mm) + depth: 洞深度 (mm) + max_sheets: 最大极片数量 + category: 类别 + """ + super().__init__( + name=name, + size_x=diameter, + size_y=diameter, + size_z=depth, + category=category, + ) + self.diameter = diameter + self.depth = depth + self.max_sheets = max_sheets + self._sheets: List[ElectrodeSheet] = [] + + def can_add_sheet(self, sheet: ElectrodeSheet) -> bool: + """检查是否可以添加极片""" + return (len(self._sheets) < self.max_sheets and + sheet.diameter <= self.diameter) + + def add_sheet(self, sheet: ElectrodeSheet) -> None: + """添加极片""" + if not self.can_add_sheet(sheet): + raise ValueError(f"无法向洞位 {self.name} 添加极片") + self._sheets.append(sheet) + + def take_sheet(self) -> ElectrodeSheet: + """取出极片""" + if len(self._sheets) == 0: + raise ValueError(f"洞位 {self.name} 没有极片") + return self._sheets.pop() + + def get_sheet_count(self) -> int: + """获取极片数量""" + return len(self._sheets) + + def serialize_state(self) -> Dict[str, Any]: + return { + "sheet_count": len(self._sheets), + "sheets": [sheet.serialize() for sheet in self._sheets], + } + +# TODO: 这个要改 +class ClipMagazine(Resource): + """子弹夹类 - 有6个洞位,每个洞位放多个极片""" + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + hole_spacing: float = 25.0, + max_sheets_per_hole: int = 100, + category: str = "clip_magazine", + model: Optional[str] = None, + ): + """初始化子弹夹 + + Args: + name: 子弹夹名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + hole_diameter: 洞直径 (mm) + hole_depth: 洞深度 (mm) + hole_spacing: 洞位间距 (mm) + max_sheets_per_hole: 每个洞位最大极片数量 + category: 类别 + model: 型号 + """ + # 创建6个洞位,排成2x3布局 + holes = create_ordered_items_2d( + klass=ClipMagazineHole, + num_items_x=3, + num_items_y=2, + dx=(size_x - 2 * hole_spacing) / 2, # 居中 + dy=(size_y - hole_spacing) / 2, # 居中 + dz=size_z - 0, + item_dx=hole_spacing, + item_dy=hole_spacing, + diameter=0, + depth=0, + ) + + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=holes, + category=category, + model=model, + ) + + self.hole_diameter = hole_diameter + self.hole_depth = hole_depth + self.max_sheets_per_hole = max_sheets_per_hole + + def serialize(self) -> dict: + return { + **super().serialize(), + "hole_diameter": self.hole_diameter, + "hole_depth": self.hole_depth, + "max_sheets_per_hole": self.max_sheets_per_hole, + } +#是一种类型注解,不用self +class BatteryState(TypedDict): + """电池状态字典""" + diameter: float + height: float + assembly_pressure: float + electrolyte_volume: float + electrolyte_name: str + +class Battery(Resource): + """电池类 - 可容纳极片""" + children: List[ElectrodeSheet] = [] + + def __init__( + self, + name: str, + size_x=1, + size_y=1, + size_z=1, + category: str = "battery", + ): + """初始化电池 + + Args: + name: 电池名称 + diameter: 直径 (mm) + height: 高度 (mm) + max_volume: 最大容量 (μL) + barcode: 二维码编号 + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=1, + size_y=1, + size_z=1, + category=category, + ) + self._unilabos_state: BatteryState = BatteryState( + diameter = 1.0, + height = 1.0, + assembly_pressure = 1.0, + electrolyte_volume = 1.0, + electrolyte_name = "DP001" + ) + + def add_electrolyte_with_bottle(self, bottle: Bottle) -> bool: + to_add_name = bottle._unilabos_state["electrolyte_name"] + if bottle.aspirate_electrolyte(10): + if self.add_electrolyte(to_add_name, 10): + pass + else: + bottle._unilabos_state["electrolyte_volume"] += 10 + + def set_electrolyte(self, name: str, volume: float) -> None: + """设置电解液信息""" + self._unilabos_state["electrolyte_name"] = name + self._unilabos_state["electrolyte_volume"] = volume + #这个应该没用,不会有加了后再加的事情 + def add_electrolyte(self, name: str, volume: float) -> bool: + """添加电解液信息""" + if name != self._unilabos_state["electrolyte_name"]: + return False + self._unilabos_state["electrolyte_volume"] += volume + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + +# 电解液作为属性放进去 + +class BatteryPressSlotState(TypedDict): + """电池状态字典""" + diameter: float =20.0 + depth: float = 4.0 + +class BatteryPressSlot(Resource): + """电池压制槽类 - 设备,可容纳一个电池""" + children: List[Battery] = [] + + def __init__( + self, + name: str = "BatteryPressSlot", + category: str = "battery_press_slot", + ): + """初始化电池压制槽 + + Args: + name: 压制槽名称 + diameter: 直径 (mm) + depth: 深度 (mm) + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=10, + size_y=12, + size_z=13, + category=category, + ) + self._unilabos_state: BatteryPressSlotState = BatteryPressSlotState() + + def has_battery(self) -> bool: + """检查是否有电池""" + return len(self.children) > 0 + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + + def assign_child_resource( + self, + resource: Battery, + location: Optional[Coordinate], + reassign: bool = True, + ): + """放置极片""" + # TODO: 让高京看下槽位只有一个电池时是否这么写。 + if self.has_battery(): + raise ValueError(f"槽位已含有一个电池,无法再放置其他电池") + super().assign_child_resource(resource, location, reassign) + + # 根据children的编号取物料对象。 + def get_battery_info(self, index: int) -> Battery: + return self.children[0] + +# TODO:这个移液枪架子看一下从哪继承 +class TipBox64State(TypedDict): + """电池状态字典""" + tip_diameter: float = 5.0 + tip_length: float = 50.0 + with_tips: bool = True + +class TipBox64(TipRack): + """64孔枪头盒类""" + + children: List[TipSpot] = [] + def __init__( + self, + name: str, + size_x: float = 127.8, + size_y: float = 85.5, + size_z: float = 60.0, + category: str = "tip_box_64", + model: Optional[str] = None, + ): + """初始化64孔枪头盒 + + Args: + name: 枪头盒名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + tip_diameter: 枪头直径 (mm) + tip_length: 枪头长度 (mm) + category: 类别 + model: 型号 + with_tips: 是否带枪头 + """ + from pylabrobot.resources.tip import Tip + + # 创建8x8=64个枪头位 + def make_tip(): + return Tip( + has_filter=False, + total_tip_length=20.0, + maximal_volume=1000, # 1mL + fitting_depth=8.0, + ) + + tip_spots = create_ordered_items_2d( + klass=TipSpot, + num_items_x=8, + num_items_y=8, + dx=8.0, + dy=8.0, + dz=0.0, + item_dx=9.0, + item_dy=9.0, + size_x=10, + size_y=10, + size_z=0.0, + make_tip=make_tip, + ) + self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate() + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=tip_spots, + category=category, + model=model, + with_tips=True, + ) + + + +class WasteTipBoxstate(TypedDict): + """"废枪头盒状态字典""" + max_tips: int = 100 + tip_count: int = 0 + +#枪头不是一次性的(同一溶液则反复使用),根据寄存器判断 +class WasteTipBox(Trash): + """废枪头盒类 - 100个枪头容量""" + + def __init__( + self, + name: str, + size_x: float = 127.8, + size_y: float = 85.5, + size_z: float = 60.0, + category: str = "waste_tip_box", + model: Optional[str] = None, + ): + """初始化废枪头盒 + + Args: + name: 废枪头盒名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + max_tips: 最大枪头容量 + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + category=category, + model=model, + ) + self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate() + + def add_tip(self) -> None: + """添加废枪头""" + if self._unilabos_state["tip_count"] >= self._unilabos_state["max_tips"]: + raise ValueError(f"废枪头盒 {self.name} 已满") + self._unilabos_state["tip_count"] += 1 + + def get_tip_count(self) -> int: + """获取枪头数量""" + return self._unilabos_state["tip_count"] + + def empty(self) -> None: + """清空废枪头盒""" + self._unilabos_state["tip_count"] = 0 + + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + + +class BottleRackState(TypedDict): + """ bottle_diameter: 瓶子直径 (mm) + bottle_height: 瓶子高度 (mm) + position_spacing: 位置间距 (mm)""" + bottle_diameter: float + bottle_height: float + name_to_index: dict + + + +class BottleRack(Resource): + """瓶架类 - 12个待配位置+12个已配位置""" + children: List[Bottle] = [] + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + category: str = "bottle_rack", + model: Optional[str] = None, + ): + """初始化瓶架 + + Args: + name: 瓶架名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + category=category, + model=model, + ) + # TODO: 添加瓶位坐标映射 + self.index_to_pos = { + 0: Coordinate.zero(), + 1: Coordinate(x=1, y=2, z=3) # 添加 + } + self.name_to_index = {} + self.name_to_pos = {} + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + + # TODO: 这里有些问题要重新写一下 + def assign_child_resource(self, resource: Bottle, location=Coordinate.zero(), reassign = True): + assert len(self.children) <= 12, "瓶架已满,无法添加更多瓶子" + index = len(self.children) + location = Coordinate(x=20 + (index % 4) * 15, y=20 + (index // 4) * 15, z=0) + self.name_to_pos[resource.name] = location + self.name_to_index[resource.name] = index + return super().assign_child_resource(resource, location, reassign) + + def assign_child_resource_by_index(self, resource: Bottle, index: int): + assert 0 <= index < 12, "无效的瓶子索引" + self.name_to_index[resource.name] = index + location = self.index_to_pos[index] + return super().assign_child_resource(resource, location) + + def unassign_child_resource(self, resource: Bottle): + super().unassign_child_resource(resource) + self.index_to_pos.pop(self.name_to_index.pop(resource.name, None), None) + + # def serialize(self): + # self.children.sort(key=lambda x: self.name_to_index.get(x.name, 0)) + # return super().serialize() + + +class BottleState(TypedDict): + diameter: float + height: float + electrolyte_name: str + electrolyte_volume: float + max_volume: float + +class Bottle(Resource): + """瓶子类 - 容纳电解液""" + + def __init__( + self, + name: str, + category: str = "bottle", + ): + """初始化瓶子 + + Args: + name: 瓶子名称 + diameter: 直径 (mm) + height: 高度 (mm) + max_volume: 最大体积 (μL) + barcode: 二维码 + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=1, + size_y=1, + size_z=1, + category=category, + ) + self._unilabos_state: BottleState = BottleState() + + def aspirate_electrolyte(self, volume: float) -> bool: + current_volume = self._unilabos_state["electrolyte_volume"] + assert current_volume > volume, f"Cannot aspirate {volume}μL, only {current_volume}μL available." + self._unilabos_state["electrolyte_volume"] -= volume + return True + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + +class CoincellDeck(Deck): + """纽扣电池组装工作站台面类""" + + def __init__( + self, + name: str = "coin_cell_deck", + size_x: float = 1620.0, # 3.66m + size_y: float = 1270.0, # 1.23m + size_z: float = 500.0, + origin: Coordinate = Coordinate(0, 0, 0), + category: str = "coin_cell_deck", + ): + """初始化纽扣电池组装工作站台面 + + Args: + name: 台面名称 + size_x: 长度 (mm) - 3.66m + size_y: 宽度 (mm) - 1.23m + size_z: 高度 (mm) + origin: 原点坐标 + category: 类别 + """ + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + origin=origin, + category=category, + ) + +#if __name__ == "__main__": +# # 转移极片的测试代码 +# deck = CoincellDeck("coin_cell_deck") +# ban_cao_wei = PlateSlot("ban_cao_wei", max_plates=8) +# deck.assign_child_resource(ban_cao_wei, Coordinate(x=0, y=0, z=0)) +# +# plate_1 = MaterialPlate("plate_1", 1,1,1, fill=True) +# for i, hole in enumerate(plate_1.children): +# sheet = ElectrodeSheet(f"hole_{i}_sheet_1") +# sheet._unilabos_state = { +# "diameter": 14, +# "info": "NMC", +# "mass": 5.0, +# "material_type": "positive_electrode", +# "thickness": 0.1 +# } +# hole._unilabos_state = { +# "depth": 1.0, +# "diameter": 14, +# "info": "", +# "max_sheets": 1 +# } +# hole.assign_child_resource(sheet, Coordinate.zero()) +# plate_1._unilabos_state = { +# "hole_spacing_x": 20.0, +# "hole_spacing_y": 20.0, +# "hole_diameter": 5, +# "info": "这是第一块料板" +# } +# plate_1.update_locations() +# ban_cao_wei.assign_child_resource(plate_1, Coordinate.zero()) +# # zi_dan_jia = ClipMagazine("zi_dan_jia", 1, 1, 1) +# # deck.assign_child_resource(ban_cao_wei, Coordinate(x=200, y=200, z=0)) +# +# from unilabos.resources.graphio import * +# A = tree_to_list([resource_plr_to_ulab(deck)]) +# with open("test.json", "w") as f: +# json.dump(A, f) +# +# +#def get_plate_with_14mm_hole(name=""): +# plate = MaterialPlate(name=name) +# for i in range(4): +# for j in range(4): +# hole = MaterialHole(f"{i+1}x{j+1}") +# hole._unilabos_state["diameter"] = 14 +# hole._unilabos_state["max_sheets"] = 1 +# plate.assign_child_resource(hole) +# return plate + +import json + +if __name__ == "__main__": + #electrode1 = BatteryPressSlot() + #print(electrode1.get_size_x()) + #print(electrode1.get_size_y()) + #print(electrode1.get_size_z()) + #jipian = ElectrodeSheet() + #jipian._unilabos_state["diameter"] = 18 + #print(jipian.serialize()) + #print(jipian.serialize_state()) + + deck = CoincellDeck(size_x=1000, + size_y=1000, + size_z=900) + + #liaopan = TipBox64(name="liaopan") + + #创建一个4*4的物料板 + liaopan1 = MaterialPlate(name="liaopan1", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) + #把物料板放到桌子上 + deck.assign_child_resource(liaopan1, Coordinate(x=0, y=0, z=0)) + #创建一个极片 + for i in range(16): + jipian = ElectrodeSheet(name=f"jipian1_{i}", size_x= 12, size_y=12, size_z=0.1) + liaopan1.children[i].assign_child_resource(jipian, location=None) +# + #创建一个4*4的物料板 + liaopan2 = MaterialPlate(name="liaopan2", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) + #把物料板放到桌子上 + deck.assign_child_resource(liaopan2, Coordinate(x=500, y=0, z=0)) + + #创建一个4*4的物料板 + liaopan3 = MaterialPlate(name="电池料盘", size_x=120.8, size_y=160.5, size_z=10.0, fill=True) + #把物料板放到桌子上 + deck.assign_child_resource(liaopan3, Coordinate(x=100, y=100, z=0)) + + + + #liaopan.children[3].assign_child_resource(jipian, location=None) + print(deck) + + + from unilabos.resources.graphio import convert_resources_from_type + from unilabos.config.config import BasicConfig + BasicConfig.ak = "4d5ce6ae-7234-4639-834e-93899b9caf94" + BasicConfig.sk = "505d3b0a-620e-459a-9905-1efcffce382a" + from unilabos.app.web.client import http_client + + resources = convert_resources_from_type([deck], [Resource]) + json.dump({"nodes": resources, "links": []}, open("button_battery_station_resources_unilab.json", "w"), indent=2) + + + #print(resources) + http_client.remote_addr = "https://uni-lab.test.bohrium.com/api/v1" + + http_client.resource_add(resources) \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/button_battery_station_big.py b/unilabos/devices/workstation/coin_cell_assembly/button_battery_station_big.py new file mode 100644 index 0000000..765328f --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/button_battery_station_big.py @@ -0,0 +1,1412 @@ +""" +纽扣电池组装工作站物料类定义 +Button Battery Assembly Station Resource Classes +""" + +from __future__ import annotations + +from collections import OrderedDict +from typing import Any, Dict, List, Optional, TypedDict, Union, cast + +from pylabrobot.resources.coordinate import Coordinate +from pylabrobot.resources.container import Container +from pylabrobot.resources.deck import Deck +from pylabrobot.resources.itemized_resource import ItemizedResource +from pylabrobot.resources.resource import Resource +from pylabrobot.resources.resource_stack import ResourceStack +from pylabrobot.resources.tip_rack import TipRack, TipSpot +from pylabrobot.resources.trash import Trash +from pylabrobot.resources.utils import create_ordered_items_2d + + +class ElectrodeSheetState(TypedDict): + diameter: float # 直径 (mm) + thickness: float # 厚度 (mm) + mass: float # 质量 (g) + material_type: str # 材料类型(正极、负极、隔膜、弹片、垫片、铝箔等) + info: Optional[str] # 附加信息 + +class ElectrodeSheet(Resource): + """极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料""" + + def __init__( + self, + name: str = "极片", + size_x=10, + size_y=10, + size_z=10, + category: str = "electrode_sheet", + model: Optional[str] = None, + ): + """初始化极片 + + Args: + name: 极片名称 + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + category=category, + model=model, + ) + self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState( + diameter=14, + thickness=0.1, + mass=0.5, + material_type="copper", + info=None + ) + + # TODO: 这个还要不要?给self._unilabos_state赋值的? + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + #序列化 + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + +# TODO: 这个应该只能放一个极片 +class MaterialHoleState(TypedDict): + diameter: int + depth: int + max_sheets: int + info: Optional[str] # 附加信息 + +class MaterialHole(Resource): + """料板洞位类""" + children: List[ElectrodeSheet] = [] + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + category: str = "material_hole", + **kwargs + ): + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + category=category, + ) + self._unilabos_state: MaterialHoleState = MaterialHoleState( + diameter=20, + depth=10, + max_sheets=1, + info=None + ) + + def get_all_sheet_info(self): + info_list = [] + for sheet in self.children: + info_list.append(sheet._unilabos_state["info"]) + return info_list + + #这个函数函数好像没用,一般不会集中赋值质量 + def set_all_sheet_mass(self): + for sheet in self.children: + sheet._unilabos_state["mass"] = 0.5 # 示例:设置质量为0.5g + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + #移动极片前先取出对象 + def get_sheet_with_name(self, name: str) -> Optional[ElectrodeSheet]: + for sheet in self.children: + if sheet.name == name: + return sheet + return None + + def has_electrode_sheet(self) -> bool: + """检查洞位是否有极片""" + return len(self.children) > 0 + + def assign_child_resource( + self, + resource: ElectrodeSheet, + location: Optional[Coordinate], + reassign: bool = True, + ): + """放置极片""" + # TODO: 这里要改,diameter找不到,加入._unilabos_state后应该没问题 + if resource._unilabos_state["diameter"] > self._unilabos_state["diameter"]: + raise ValueError(f"极片直径 {resource._unilabos_state['diameter']} 超过洞位直径 {self._unilabos_state['diameter']}") + if len(self.children) >= self._unilabos_state["max_sheets"]: + raise ValueError(f"洞位已满,无法放置更多极片") + super().assign_child_resource(resource, location, reassign) + + # 根据children的编号取物料对象。 + def get_electrode_sheet_info(self, index: int) -> ElectrodeSheet: + return self.children[index] + + + +class MaterialPlateState(TypedDict): + hole_spacing_x: float + hole_spacing_y: float + hole_diameter: float + info: Optional[str] # 附加信息 + + + +class MaterialPlate(ItemizedResource[MaterialHole]): + """料板类 - 4x4个洞位,每个洞位放1个极片""" + + children: List[MaterialHole] + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + ordered_items: Optional[Dict[str, MaterialHole]] = None, + ordering: Optional[OrderedDict[str, str]] = None, + category: str = "material_plate", + model: Optional[str] = None, + fill: bool = False + ): + """初始化料板 + + Args: + name: 料板名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + hole_diameter: 洞直径 (mm) + hole_depth: 洞深度 (mm) + hole_spacing_x: X方向洞位间距 (mm) + hole_spacing_y: Y方向洞位间距 (mm) + number: 编号 + category: 类别 + model: 型号 + """ + self._unilabos_state: MaterialPlateState = MaterialPlateState( + hole_spacing_x=24.0, + hole_spacing_y=24.0, + hole_diameter=20.0, + info="", + ) + # 创建4x4的洞位 + # TODO: 这里要改,对应不同形状 + holes = create_ordered_items_2d( + klass=MaterialHole, + num_items_x=4, + num_items_y=4, + dx=(size_x - 4 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中 + dy=(size_y - 4 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中 + dz=size_z, + item_dx=self._unilabos_state["hole_spacing_x"], + item_dy=self._unilabos_state["hole_spacing_y"], + size_x = 16, + size_y = 16, + size_z = 16, + ) + if fill: + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=holes, + category=category, + model=model, + ) + else: + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=ordered_items, + ordering=ordering, + category=category, + model=model, + ) + + def update_locations(self): + # TODO:调多次相加 + holes = create_ordered_items_2d( + klass=MaterialHole, + num_items_x=4, + num_items_y=4, + dx=(self._size_x - 3 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中 + dy=(self._size_y - 3 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中 + dz=self._size_z, + item_dx=self._unilabos_state["hole_spacing_x"], + item_dy=self._unilabos_state["hole_spacing_y"], + size_x = 1, + size_y = 1, + size_z = 1, + ) + for item, original_item in zip(holes.items(), self.children): + original_item.location = item[1].location + + +class PlateSlot(ResourceStack): + """板槽位类 - 1个槽上能堆放8个板,移板只能操作最上方的板""" + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + max_plates: int = 8, + category: str = "plate_slot", + model: Optional[str] = None + ): + """初始化板槽位 + + Args: + name: 槽位名称 + max_plates: 最大板数量 + category: 类别 + """ + super().__init__( + name=name, + direction="z", # Z方向堆叠 + resources=[], + ) + self.max_plates = max_plates + self.category = category + + def can_add_plate(self) -> bool: + """检查是否可以添加板""" + return len(self.children) < self.max_plates + + def add_plate(self, plate: MaterialPlate) -> None: + """添加料板""" + if not self.can_add_plate(): + raise ValueError(f"槽位 {self.name} 已满,无法添加更多板") + self.assign_child_resource(plate) + + def get_top_plate(self) -> MaterialPlate: + """获取最上方的板""" + if len(self.children) == 0: + raise ValueError(f"槽位 {self.name} 为空") + return cast(MaterialPlate, self.get_top_item()) + + def take_top_plate(self) -> MaterialPlate: + """取出最上方的板""" + top_plate = self.get_top_plate() + self.unassign_child_resource(top_plate) + return top_plate + + def can_access_for_picking(self) -> bool: + """检查是否可以进行取料操作(只有最上方的板能进行取料操作)""" + return len(self.children) > 0 + + def serialize(self) -> dict: + return { + **super().serialize(), + "max_plates": self.max_plates, + } + + +class ClipMagazineHole(Container): + """子弹夹洞位类""" + children: List[ElectrodeSheet] = [] + def __init__( + self, + name: str, + diameter: float, + depth: float, + category: str = "clip_magazine_hole", + ): + """初始化子弹夹洞位 + + Args: + name: 洞位名称 + diameter: 洞直径 (mm) + depth: 洞深度 (mm) + category: 类别 + """ + super().__init__( + name=name, + size_x=diameter, + size_y=diameter, + size_z=depth, + category=category, + ) + self.diameter = diameter + self.depth = depth + + def can_add_sheet(self, sheet: ElectrodeSheet) -> bool: + """检查是否可以添加极片 + + 根据洞的深度和极片的厚度来判断是否可以添加极片 + """ + # 检查极片直径是否适合洞的直径 + if sheet._unilabos_state["diameter"] > self.diameter: + return False + + # 计算当前已添加极片的总厚度 + current_thickness = sum(s._unilabos_state["thickness"] for s in self.children) + + # 检查添加新极片后总厚度是否超过洞的深度 + if current_thickness + sheet._unilabos_state["thickness"] > self.depth: + return False + + return True + + + def assign_child_resource( + self, + resource: ElectrodeSheet, + location: Optional[Coordinate] = None, + reassign: bool = True, + ): + """放置极片到洞位中 + + Args: + resource: 要放置的极片 + location: 极片在洞位中的位置(对于洞位,通常为None) + reassign: 是否允许重新分配 + """ + # 检查是否可以添加极片 + if not self.can_add_sheet(resource): + raise ValueError(f"无法向洞位 {self.name} 添加极片:直径或厚度不匹配") + + # 调用父类方法实际执行分配 + super().assign_child_resource(resource, location, reassign) + + def unassign_child_resource(self, resource: ElectrodeSheet): + """从洞位中移除极片 + + Args: + resource: 要移除的极片 + """ + if resource not in self.children: + raise ValueError(f"极片 {resource.name} 不在洞位 {self.name} 中") + + # 调用父类方法实际执行移除 + super().unassign_child_resource(resource) + + + + def serialize_state(self) -> Dict[str, Any]: + return { + "sheet_count": len(self.children), + "sheets": [sheet.serialize() for sheet in self.children], + } +class ClipMagazine_four(ItemizedResource[ClipMagazineHole]): + """子弹夹类 - 有4个洞位,每个洞位放多个极片""" + children: List[ClipMagazineHole] + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + hole_diameter: float = 14.0, + hole_depth: float = 10.0, + hole_spacing: float = 25.0, + max_sheets_per_hole: int = 100, + category: str = "clip_magazine_four", + model: Optional[str] = None, + ): + """初始化子弹夹 + + Args: + name: 子弹夹名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + hole_diameter: 洞直径 (mm) + hole_depth: 洞深度 (mm) + hole_spacing: 洞位间距 (mm) + max_sheets_per_hole: 每个洞位最大极片数量 + category: 类别 + model: 型号 + """ + # 创建4个洞位,排成2x2布局 + holes = create_ordered_items_2d( + klass=ClipMagazineHole, + num_items_x=2, + num_items_y=2, + dx=(size_x - 2 * hole_spacing) / 2, # 居中 + dy=(size_y - hole_spacing) / 2, # 居中 + dz=size_z - 0, + item_dx=hole_spacing, + item_dy=hole_spacing, + diameter=hole_diameter, + depth=hole_depth, + ) + + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=holes, + category=category, + model=model, + ) + + # 保存洞位的直径和深度 + self.hole_diameter = hole_diameter + self.hole_depth = hole_depth + self.max_sheets_per_hole = max_sheets_per_hole + + def serialize(self) -> dict: + return { + **super().serialize(), + "hole_diameter": self.hole_diameter, + "hole_depth": self.hole_depth, + "max_sheets_per_hole": self.max_sheets_per_hole, + } +# TODO: 这个要改 +class ClipMagazine(ItemizedResource[ClipMagazineHole]): + """子弹夹类 - 有6个洞位,每个洞位放多个极片""" + children: List[ClipMagazineHole] + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + hole_diameter: float = 14.0, + hole_depth: float = 10.0, + hole_spacing: float = 25.0, + max_sheets_per_hole: int = 100, + category: str = "clip_magazine", + model: Optional[str] = None, + ): + """初始化子弹夹 + + Args: + name: 子弹夹名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + hole_diameter: 洞直径 (mm) + hole_depth: 洞深度 (mm) + hole_spacing: 洞位间距 (mm) + max_sheets_per_hole: 每个洞位最大极片数量 + category: 类别 + model: 型号 + """ + # 创建6个洞位,排成2x3布局 + holes = create_ordered_items_2d( + klass=ClipMagazineHole, + num_items_x=3, + num_items_y=2, + dx=(size_x - 2 * hole_spacing) / 2, # 居中 + dy=(size_y - hole_spacing) / 2, # 居中 + dz=size_z - 0, + item_dx=hole_spacing, + item_dy=hole_spacing, + diameter=hole_diameter, + depth=hole_depth, + ) + + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=holes, + category=category, + model=model, + ) + + # 保存洞位的直径和深度 + self.hole_diameter = hole_diameter + self.hole_depth = hole_depth + self.max_sheets_per_hole = max_sheets_per_hole + + def serialize(self) -> dict: + return { + **super().serialize(), + "hole_diameter": self.hole_diameter, + "hole_depth": self.hole_depth, + "max_sheets_per_hole": self.max_sheets_per_hole, + } +#是一种类型注解,不用self +class BatteryState(TypedDict): + """电池状态字典""" + diameter: float + height: float + + electrolyte_name: str + electrolyte_volume: float + +class Battery(Resource): + """电池类 - 可容纳极片""" + children: List[ElectrodeSheet] = [] + + def __init__( + self, + name: str, + category: str = "battery", + ): + """初始化电池 + + Args: + name: 电池名称 + diameter: 直径 (mm) + height: 高度 (mm) + max_volume: 最大容量 (μL) + barcode: 二维码编号 + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=1, + size_y=1, + size_z=1, + category=category, + ) + self._unilabos_state: BatteryState = BatteryState() + + def add_electrolyte_with_bottle(self, bottle: Bottle) -> bool: + to_add_name = bottle._unilabos_state["electrolyte_name"] + if bottle.aspirate_electrolyte(10): + if self.add_electrolyte(to_add_name, 10): + pass + else: + bottle._unilabos_state["electrolyte_volume"] += 10 + + def set_electrolyte(self, name: str, volume: float) -> None: + """设置电解液信息""" + self._unilabos_state["electrolyte_name"] = name + self._unilabos_state["electrolyte_volume"] = volume + #这个应该没用,不会有加了后再加的事情 + def add_electrolyte(self, name: str, volume: float) -> bool: + """添加电解液信息""" + if name != self._unilabos_state["electrolyte_name"]: + return False + self._unilabos_state["electrolyte_volume"] += volume + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + +# 电解液作为属性放进去 + +class BatteryPressSlotState(TypedDict): + """电池状态字典""" + diameter: float =20.0 + depth: float = 4.0 + +class BatteryPressSlot(Resource): + """电池压制槽类 - 设备,可容纳一个电池""" + children: List[Battery] = [] + + def __init__( + self, + name: str = "BatteryPressSlot", + category: str = "battery_press_slot", + ): + """初始化电池压制槽 + + Args: + name: 压制槽名称 + diameter: 直径 (mm) + depth: 深度 (mm) + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=10, + size_y=12, + size_z=13, + category=category, + ) + self._unilabos_state: BatteryPressSlotState = BatteryPressSlotState() + + def has_battery(self) -> bool: + """检查是否有电池""" + return len(self.children) > 0 + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + + def assign_child_resource( + self, + resource: Battery, + location: Optional[Coordinate], + reassign: bool = True, + ): + """放置极片""" + # TODO: 让高京看下槽位只有一个电池时是否这么写。 + if self.has_battery(): + raise ValueError(f"槽位已含有一个电池,无法再放置其他电池") + super().assign_child_resource(resource, location, reassign) + + # 根据children的编号取物料对象。 + def get_battery_info(self, index: int) -> Battery: + return self.children[0] + +# TODO:这个移液枪架子看一下从哪继承 +class TipBox64State(TypedDict): + """电池状态字典""" + tip_diameter: float = 5.0 + tip_length: float = 50.0 + with_tips: bool = True + +class TipBox64(TipRack): + """64孔枪头盒类""" + + children: List[TipSpot] = [] + def __init__( + self, + name: str, + size_x: float = 127.8, + size_y: float = 85.5, + size_z: float = 60.0, + category: str = "tip_box_64", + model: Optional[str] = None, + ): + """初始化64孔枪头盒 + + Args: + name: 枪头盒名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + tip_diameter: 枪头直径 (mm) + tip_length: 枪头长度 (mm) + category: 类别 + model: 型号 + with_tips: 是否带枪头 + """ + from pylabrobot.resources.tip import Tip + + # 创建8x8=64个枪头位 + def make_tip(): + return Tip( + has_filter=False, + total_tip_length=20.0, + maximal_volume=1000, # 1mL + fitting_depth=8.0, + ) + + tip_spots = create_ordered_items_2d( + klass=TipSpot, + num_items_x=8, + num_items_y=8, + dx=8.0, + dy=8.0, + dz=0.0, + item_dx=9.0, + item_dy=9.0, + size_x=10, + size_y=10, + size_z=0.0, + make_tip=make_tip, + ) + self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate() + # 记录网格参数用于前端渲染 + self._grid_params = { + "num_items_x": 8, + "num_items_y": 8, + "dx": 8.0, + "dy": 8.0, + "item_dx": 9.0, + "item_dy": 9.0, + } + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=tip_spots, + category=category, + model=model, + with_tips=True, + ) + + def serialize(self) -> dict: + return { + **super().serialize(), + **self._grid_params, + } + + + +class WasteTipBoxstate(TypedDict): + """"废枪头盒状态字典""" + max_tips: int = 100 + tip_count: int = 0 + +#枪头不是一次性的(同一溶液则反复使用),根据寄存器判断 +class WasteTipBox(Trash): + """废枪头盒类 - 100个枪头容量""" + + def __init__( + self, + name: str, + size_x: float = 127.8, + size_y: float = 85.5, + size_z: float = 60.0, + category: str = "waste_tip_box", + model: Optional[str] = None, + ): + """初始化废枪头盒 + + Args: + name: 废枪头盒名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + max_tips: 最大枪头容量 + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + category=category, + model=model, + ) + self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate() + + def add_tip(self) -> None: + """添加废枪头""" + if self._unilabos_state["tip_count"] >= self._unilabos_state["max_tips"]: + raise ValueError(f"废枪头盒 {self.name} 已满") + self._unilabos_state["tip_count"] += 1 + + def get_tip_count(self) -> int: + """获取枪头数量""" + return self._unilabos_state["tip_count"] + + def empty(self) -> None: + """清空废枪头盒""" + self._unilabos_state["tip_count"] = 0 + + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + +class BottleRackState(TypedDict): + """ bottle_diameter: 瓶子直径 (mm) + bottle_height: 瓶子高度 (mm) + position_spacing: 位置间距 (mm)""" + bottle_diameter: float + bottle_height: float + position_spacing: float + name_to_index: dict + + +class BottleRack(Resource): + """瓶架类 - 12个待配位置+12个已配位置""" + children: List[Resource] = [] + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + category: str = "bottle_rack", + model: Optional[str] = None, + num_items_x: int = 3, + num_items_y: int = 4, + position_spacing: float = 35.0, + orientation: str = "horizontal", + padding_x: float = 20.0, + padding_y: float = 20.0, + ): + """初始化瓶架 + + Args: + name: 瓶架名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + category=category, + model=model, + ) + # 初始化状态 + self._unilabos_state: BottleRackState = BottleRackState( + bottle_diameter=30.0, + bottle_height=100.0, + position_spacing=position_spacing, + name_to_index={}, + ) + # 基于网格生成瓶位坐标映射(居中摆放) + # 使用内边距,避免点跑到容器外(前端渲染不按mm等比缩放时更稳妥) + origin_x = padding_x + origin_y = padding_y + self.index_to_pos = {} + for j in range(num_items_y): + for i in range(num_items_x): + idx = j * num_items_x + i + if orientation == "vertical": + # 纵向:沿 y 方向优先排列 + self.index_to_pos[idx] = Coordinate( + x=origin_x + j * position_spacing, + y=origin_y + i * position_spacing, + z=0, + ) + else: + # 横向(默认):沿 x 方向优先排列 + self.index_to_pos[idx] = Coordinate( + x=origin_x + i * position_spacing, + y=origin_y + j * position_spacing, + z=0, + ) + self.name_to_index = {} + self.name_to_pos = {} + self.num_items_x = num_items_x + self.num_items_y = num_items_y + self.orientation = orientation + self.padding_x = padding_x + self.padding_y = padding_y + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update( + self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + + # TODO: 这里有些问题要重新写一下 + def assign_child_resource_old(self, resource: Resource, location=Coordinate.zero(), reassign=True): + capacity = self.num_items_x * self.num_items_y + assert len(self.children) < capacity, "瓶架已满,无法添加更多瓶子" + index = len(self.children) + location = self.index_to_pos.get(index, Coordinate.zero()) + self.name_to_pos[resource.name] = location + self.name_to_index[resource.name] = index + return super().assign_child_resource(resource, location, reassign) + + def assign_child_resource(self, resource: Resource, index: int): + capacity = self.num_items_x * self.num_items_y + assert 0 <= index < capacity, "无效的瓶子索引" + self.name_to_index[resource.name] = index + location = self.index_to_pos[index] + return super().assign_child_resource(resource, location) + + def unassign_child_resource(self, resource: Bottle): + super().unassign_child_resource(resource) + self.index_to_pos.pop(self.name_to_index.pop(resource.name, None), None) + + def serialize(self) -> dict: + return { + **super().serialize(), + "num_items_x": self.num_items_x, + "num_items_y": self.num_items_y, + "position_spacing": self._unilabos_state.get("position_spacing", 35.0), + "orientation": self.orientation, + "padding_x": self.padding_x, + "padding_y": self.padding_y, + } + + +class BottleState(TypedDict): + diameter: float + height: float + electrolyte_name: str + electrolyte_volume: float + max_volume: float + +class Bottle(Resource): + """瓶子类 - 容纳电解液""" + + def __init__( + self, + name: str, + category: str = "bottle", + ): + """初始化瓶子 + + Args: + name: 瓶子名称 + diameter: 直径 (mm) + height: 高度 (mm) + max_volume: 最大体积 (μL) + barcode: 二维码 + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=1, + size_y=1, + size_z=1, + category=category, + ) + self._unilabos_state: BottleState = BottleState() + + def aspirate_electrolyte(self, volume: float) -> bool: + current_volume = self._unilabos_state["electrolyte_volume"] + assert current_volume > volume, f"Cannot aspirate {volume}μL, only {current_volume}μL available." + self._unilabos_state["electrolyte_volume"] -= volume + return True + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + +class CoincellDeck(Deck): + """纽扣电池组装工作站台面类""" + + def __init__( + self, + name: str = "coin_cell_deck", + size_x: float = 1620.0, # 3.66m + size_y: float = 1270.0, # 1.23m + size_z: float = 500.0, + origin: Coordinate = Coordinate(0, 0, 0), + category: str = "coin_cell_deck", + ): + """初始化纽扣电池组装工作站台面 + + Args: + name: 台面名称 + size_x: 长度 (mm) - 3.66m + size_y: 宽度 (mm) - 1.23m + size_z: 高度 (mm) + origin: 原点坐标 + category: 类别 + """ + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + origin=origin, + category=category, + ) + +#if __name__ == "__main__": +# # 转移极片的测试代码 +# deck = CoincellDeck("coin_cell_deck") +# ban_cao_wei = PlateSlot("ban_cao_wei", max_plates=8) +# deck.assign_child_resource(ban_cao_wei, Coordinate(x=0, y=0, z=0)) +# +# plate_1 = MaterialPlate("plate_1", 1,1,1, fill=True) +# for i, hole in enumerate(plate_1.children): +# sheet = ElectrodeSheet(f"hole_{i}_sheet_1") +# sheet._unilabos_state = { +# "diameter": 14, +# "info": "NMC", +# "mass": 5.0, +# "material_type": "positive_electrode", +# "thickness": 0.1 +# } +# hole._unilabos_state = { +# "depth": 1.0, +# "diameter": 14, +# "info": "", +# "max_sheets": 1 +# } +# hole.assign_child_resource(sheet, Coordinate.zero()) +# plate_1._unilabos_state = { +# "hole_spacing_x": 20.0, +# "hole_spacing_y": 20.0, +# "hole_diameter": 5, +# "info": "这是第一块料板" +# } +# plate_1.update_locations() +# ban_cao_wei.assign_child_resource(plate_1, Coordinate.zero()) +# # zi_dan_jia = ClipMagazine("zi_dan_jia", 1, 1, 1) +# # deck.assign_child_resource(ban_cao_wei, Coordinate(x=200, y=200, z=0)) +# +# from unilabos.resources.graphio import * +# A = tree_to_list([resource_plr_to_ulab(deck)]) +# with open("test.json", "w") as f: +# json.dump(A, f) +# +# +#def get_plate_with_14mm_hole(name=""): +# plate = MaterialPlate(name=name) +# for i in range(4): +# for j in range(4): +# hole = MaterialHole(f"{i+1}x{j+1}") +# hole._unilabos_state["diameter"] = 14 +# hole._unilabos_state["max_sheets"] = 1 +# plate.assign_child_resource(hole) +# return plate + +def create_a_liaopan(): + liaopan = MaterialPlate(name="liaopan", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) + for i in range(16): + jipian = ElectrodeSheet(name=f"jipian_{i}", size_x= 12, size_y=12, size_z=0.1) + liaopan1.children[i].assign_child_resource(jipian, location=None) + return liaopan + +def create_a_coin_cell_deck(): + deck = Deck(size_x=1200, + size_y=800, + size_z=900) + + #liaopan = TipBox64(name="liaopan") + + #创建一个4*4的物料板 + liaopan1 = MaterialPlate(name="liaopan1", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) + #把物料板放到桌子上 + deck.assign_child_resource(liaopan1, Coordinate(x=0, y=0, z=0)) + #创建一个极片 + for i in range(16): + jipian = ElectrodeSheet(name=f"jipian_{i}", size_x= 12, size_y=12, size_z=0.1) + liaopan1.children[i].assign_child_resource(jipian, location=None) + #创建一个4*4的物料板 + liaopan2 = MaterialPlate(name="liaopan2", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) + #把物料板放到桌子上 + deck.assign_child_resource(liaopan2, Coordinate(x=500, y=0, z=0)) + + #创建一个4*4的物料板 + liaopan3 = MaterialPlate(name="liaopan3", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) + #把物料板放到桌子上 + deck.assign_child_resource(liaopan3, Coordinate(x=1000, y=0, z=0)) + + print(deck) + + return deck + + +def create_a_full_coin_cell_deck(): + deck = CoincellDeck() + """======================================子弹夹============================================""" + zip_dan_jia = ClipMagazine_four("zi_dan_jia", 80, 80, 10) + deck.assign_child_resource(zip_dan_jia, Coordinate(x=1400, y=50, z=0)) + zip_dan_jia2 = ClipMagazine_four("zi_dan_jia2", 80, 80, 10) + deck.assign_child_resource(zip_dan_jia2, Coordinate(x=1600, y=200, z=0)) + zip_dan_jia3 = ClipMagazine("zi_dan_jia3", 80, 80, 10) + deck.assign_child_resource(zip_dan_jia3, Coordinate(x=1500, y=200, z=0)) + zip_dan_jia4 = ClipMagazine("zi_dan_jia4", 80, 80, 10) + deck.assign_child_resource(zip_dan_jia4, Coordinate(x=1500, y=300, z=0)) + zip_dan_jia5 = ClipMagazine("zi_dan_jia5", 80, 80, 10) + deck.assign_child_resource(zip_dan_jia5, Coordinate(x=1600, y=300, z=0)) + zip_dan_jia6 = ClipMagazine("zi_dan_jia6", 80, 80, 10) + deck.assign_child_resource(zip_dan_jia6, Coordinate(x=1530, y=500, z=0)) + zip_dan_jia7 = ClipMagazine("zi_dan_jia7", 80, 80, 10) + deck.assign_child_resource(zip_dan_jia7, Coordinate(x=1180, y=400, z=0)) + zip_dan_jia8 = ClipMagazine("zi_dan_jia8", 80, 80, 10) + deck.assign_child_resource(zip_dan_jia8, Coordinate(x=1280, y=400, z=0)) + for i in range(4): + jipian = ElectrodeSheet(name=f"zi_dan_jia_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia2.children[i].assign_child_resource(jipian, location=None) + for i in range(4): + jipian2 = ElectrodeSheet(name=f"zi_dan_jia2_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia.children[i].assign_child_resource(jipian2, location=None) + for i in range(6): + jipian3 = ElectrodeSheet(name=f"zi_dan_jia3_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia3.children[i].assign_child_resource(jipian3, location=None) + for i in range(6): + jipian4 = ElectrodeSheet(name=f"zi_dan_jia4_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia4.children[i].assign_child_resource(jipian4, location=None) + for i in range(6): + jipian5 = ElectrodeSheet(name=f"zi_dan_jia5_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia5.children[i].assign_child_resource(jipian5, location=None) + for i in range(6): + jipian6 = ElectrodeSheet(name=f"zi_dan_jia6_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia6.children[i].assign_child_resource(jipian6, location=None) + for i in range(6): + jipian7 = ElectrodeSheet(name=f"zi_dan_jia7_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia7.children[i].assign_child_resource(jipian7, location=None) + for i in range(6): + jipian8 = ElectrodeSheet(name=f"zi_dan_jia8_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia8.children[i].assign_child_resource(jipian8, location=None) + """======================================子弹夹============================================""" + #liaopan = TipBox64(name="liaopan") + """======================================物料板============================================""" + #创建一个4*4的物料板 + liaopan1 = MaterialPlate(name="liaopan1", size_x=120, size_y=100, size_z=10.0, fill=True) + deck.assign_child_resource(liaopan1, Coordinate(x=1010, y=50, z=0)) + for i in range(16): + jipian_1 = ElectrodeSheet(name=f"{liaopan1.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + liaopan1.children[i].assign_child_resource(jipian_1, location=None) + + liaopan2 = MaterialPlate(name="liaopan2", size_x=120, size_y=100, size_z=10.0, fill=True) + deck.assign_child_resource(liaopan2, Coordinate(x=1130, y=50, z=0)) + + liaopan3 = MaterialPlate(name="liaopan3", size_x=120, size_y=100, size_z=10.0, fill=True) + deck.assign_child_resource(liaopan3, Coordinate(x=1250, y=50, z=0)) + + liaopan4 = MaterialPlate(name="liaopan4", size_x=120, size_y=100, size_z=10.0, fill=True) + deck.assign_child_resource(liaopan4, Coordinate(x=1010, y=150, z=0)) + for i in range(16): + jipian_4 = ElectrodeSheet(name=f"{liaopan4.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + liaopan4.children[i].assign_child_resource(jipian_4, location=None) + liaopan5 = MaterialPlate(name="liaopan5", size_x=120, size_y=100, size_z=10.0, fill=True) + deck.assign_child_resource(liaopan5, Coordinate(x=1130, y=150, z=0)) + liaopan6 = MaterialPlate(name="liaopan6", size_x=120, size_y=100, size_z=10.0, fill=True) + deck.assign_child_resource(liaopan6, Coordinate(x=1250, y=150, z=0)) + #liaopan.children[3].assign_child_resource(jipian, location=None) + """======================================物料板============================================""" + """======================================瓶架,移液枪============================================""" + # 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒 + bottle_rack_3x4 = BottleRack( + name="bottle_rack_3x4", + size_x=210.0, + size_y=140.0, + size_z=100.0, + num_items_x=3, + num_items_y=4, + position_spacing=35.0, + orientation="vertical", + ) + deck.assign_child_resource(bottle_rack_3x4, Coordinate(x=100, y=200, z=0)) + + bottle_rack_6x2 = BottleRack( + name="bottle_rack_6x2", + size_x=120.0, + size_y=250.0, + size_z=100.0, + num_items_x=6, + num_items_y=2, + position_spacing=35.0, + orientation="vertical", + ) + deck.assign_child_resource(bottle_rack_6x2, Coordinate(x=300, y=300, z=0)) + + bottle_rack_6x2_2 = BottleRack( + name="bottle_rack_6x2_2", + size_x=120.0, + size_y=250.0, + size_z=100.0, + num_items_x=6, + num_items_y=2, + position_spacing=35.0, + orientation="vertical", + ) + deck.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=430, y=300, z=0)) + + + # 将 ElectrodeSheet 放满 3x4 与 6x2 的所有孔位 + for idx in range(bottle_rack_3x4.num_items_x * bottle_rack_3x4.num_items_y): + sheet = ElectrodeSheet(name=f"sheet_3x4_{idx}", size_x=12, size_y=12, size_z=0.1) + bottle_rack_3x4.assign_child_resource(sheet, index=idx) + + for idx in range(bottle_rack_6x2.num_items_x * bottle_rack_6x2.num_items_y): + sheet = ElectrodeSheet(name=f"sheet_6x2_{idx}", size_x=12, size_y=12, size_z=0.1) + bottle_rack_6x2.assign_child_resource(sheet, index=idx) + + tip_box = TipBox64(name="tip_box_64") + deck.assign_child_resource(tip_box, Coordinate(x=300, y=100, z=0)) + + waste_tip_box = WasteTipBox(name="waste_tip_box") + deck.assign_child_resource(waste_tip_box, Coordinate(x=300, y=200, z=0)) + """======================================瓶架,移液枪============================================""" + print(deck) + return deck + + + + +import json + +if __name__ == "__main__": + electrode1 = BatteryPressSlot() + #print(electrode1.get_size_x()) + #print(electrode1.get_size_y()) + #print(electrode1.get_size_z()) + #jipian = ElectrodeSheet() + #jipian._unilabos_state["diameter"] = 18 + #print(jipian.serialize()) + #print(jipian.serialize_state()) + + deck = CoincellDeck() + """======================================子弹夹============================================""" + zip_dan_jia = ClipMagazine_four("zi_dan_jia", 80, 80, 10) + deck.assign_child_resource(zip_dan_jia, Coordinate(x=1400, y=50, z=0)) + zip_dan_jia2 = ClipMagazine_four("zi_dan_jia2", 80, 80, 10) + deck.assign_child_resource(zip_dan_jia2, Coordinate(x=1600, y=200, z=0)) + zip_dan_jia3 = ClipMagazine("zi_dan_jia3", 80, 80, 10) + deck.assign_child_resource(zip_dan_jia3, Coordinate(x=1500, y=200, z=0)) + zip_dan_jia4 = ClipMagazine("zi_dan_jia4", 80, 80, 10) + deck.assign_child_resource(zip_dan_jia4, Coordinate(x=1500, y=300, z=0)) + zip_dan_jia5 = ClipMagazine("zi_dan_jia5", 80, 80, 10) + deck.assign_child_resource(zip_dan_jia5, Coordinate(x=1600, y=300, z=0)) + zip_dan_jia6 = ClipMagazine("zi_dan_jia6", 80, 80, 10) + deck.assign_child_resource(zip_dan_jia6, Coordinate(x=1530, y=500, z=0)) + zip_dan_jia7 = ClipMagazine("zi_dan_jia7", 80, 80, 10) + deck.assign_child_resource(zip_dan_jia7, Coordinate(x=1180, y=400, z=0)) + zip_dan_jia8 = ClipMagazine("zi_dan_jia8", 80, 80, 10) + deck.assign_child_resource(zip_dan_jia8, Coordinate(x=1280, y=400, z=0)) + for i in range(4): + jipian = ElectrodeSheet(name=f"zi_dan_jia_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia2.children[i].assign_child_resource(jipian, location=None) + for i in range(4): + jipian2 = ElectrodeSheet(name=f"zi_dan_jia2_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia.children[i].assign_child_resource(jipian2, location=None) + for i in range(6): + jipian3 = ElectrodeSheet(name=f"zi_dan_jia3_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia3.children[i].assign_child_resource(jipian3, location=None) + for i in range(6): + jipian4 = ElectrodeSheet(name=f"zi_dan_jia4_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia4.children[i].assign_child_resource(jipian4, location=None) + for i in range(6): + jipian5 = ElectrodeSheet(name=f"zi_dan_jia5_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia5.children[i].assign_child_resource(jipian5, location=None) + for i in range(6): + jipian6 = ElectrodeSheet(name=f"zi_dan_jia6_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia6.children[i].assign_child_resource(jipian6, location=None) + for i in range(6): + jipian7 = ElectrodeSheet(name=f"zi_dan_jia7_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia7.children[i].assign_child_resource(jipian7, location=None) + for i in range(6): + jipian8 = ElectrodeSheet(name=f"zi_dan_jia8_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia8.children[i].assign_child_resource(jipian8, location=None) + """======================================子弹夹============================================""" + #liaopan = TipBox64(name="liaopan") + """======================================物料板============================================""" + #创建一个4*4的物料板 + liaopan1 = MaterialPlate(name="liaopan1", size_x=120, size_y=100, size_z=10.0, fill=True) + deck.assign_child_resource(liaopan1, Coordinate(x=1010, y=50, z=0)) + for i in range(16): + jipian_1 = ElectrodeSheet(name=f"{liaopan1.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + liaopan1.children[i].assign_child_resource(jipian_1, location=None) + + liaopan2 = MaterialPlate(name="liaopan2", size_x=120, size_y=100, size_z=10.0, fill=True) + deck.assign_child_resource(liaopan2, Coordinate(x=1130, y=50, z=0)) + + liaopan3 = MaterialPlate(name="liaopan3", size_x=120, size_y=100, size_z=10.0, fill=True) + deck.assign_child_resource(liaopan3, Coordinate(x=1250, y=50, z=0)) + + liaopan4 = MaterialPlate(name="liaopan4", size_x=120, size_y=100, size_z=10.0, fill=True) + deck.assign_child_resource(liaopan4, Coordinate(x=1010, y=150, z=0)) + for i in range(16): + jipian_4 = ElectrodeSheet(name=f"{liaopan4.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + liaopan4.children[i].assign_child_resource(jipian_4, location=None) + liaopan5 = MaterialPlate(name="liaopan5", size_x=120, size_y=100, size_z=10.0, fill=True) + deck.assign_child_resource(liaopan5, Coordinate(x=1130, y=150, z=0)) + liaopan6 = MaterialPlate(name="liaopan6", size_x=120, size_y=100, size_z=10.0, fill=True) + deck.assign_child_resource(liaopan6, Coordinate(x=1250, y=150, z=0)) + #liaopan.children[3].assign_child_resource(jipian, location=None) + """======================================物料板============================================""" + """======================================瓶架,移液枪============================================""" + # 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒 + bottle_rack_3x4 = BottleRack( + name="bottle_rack_3x4", + size_x=210.0, + size_y=140.0, + size_z=100.0, + num_items_x=3, + num_items_y=4, + position_spacing=35.0, + orientation="vertical", + ) + deck.assign_child_resource(bottle_rack_3x4, Coordinate(x=100, y=200, z=0)) + + bottle_rack_6x2 = BottleRack( + name="bottle_rack_6x2", + size_x=120.0, + size_y=250.0, + size_z=100.0, + num_items_x=6, + num_items_y=2, + position_spacing=35.0, + orientation="vertical", + ) + deck.assign_child_resource(bottle_rack_6x2, Coordinate(x=300, y=300, z=0)) + + bottle_rack_6x2_2 = BottleRack( + name="bottle_rack_6x2_2", + size_x=120.0, + size_y=250.0, + size_z=100.0, + num_items_x=6, + num_items_y=2, + position_spacing=35.0, + orientation="vertical", + ) + deck.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=430, y=300, z=0)) + + + # 将 ElectrodeSheet 放满 3x4 与 6x2 的所有孔位 + for idx in range(bottle_rack_3x4.num_items_x * bottle_rack_3x4.num_items_y): + sheet = ElectrodeSheet(name=f"sheet_3x4_{idx}", size_x=12, size_y=12, size_z=0.1) + bottle_rack_3x4.assign_child_resource(sheet, index=idx) + + for idx in range(bottle_rack_6x2.num_items_x * bottle_rack_6x2.num_items_y): + sheet = ElectrodeSheet(name=f"sheet_6x2_{idx}", size_x=12, size_y=12, size_z=0.1) + bottle_rack_6x2.assign_child_resource(sheet, index=idx) + + tip_box = TipBox64(name="tip_box_64") + deck.assign_child_resource(tip_box, Coordinate(x=300, y=100, z=0)) + + waste_tip_box = WasteTipBox(name="waste_tip_box") + deck.assign_child_resource(waste_tip_box, Coordinate(x=300, y=200, z=0)) + """======================================瓶架,移液枪============================================""" + print(deck) + + + from unilabos.resources.graphio import convert_resources_from_type + from unilabos.config.config import BasicConfig + BasicConfig.ak = "beb0c15f-2279-46a1-aba5-00eaf89aef55" + BasicConfig.sk = "15d4f25e-3512-4f9c-9bfb-43ab85e7b561" + from unilabos.app.web.client import http_client + + resources = convert_resources_from_type([deck], [Resource]) + + # 检查序列化后的资源 + + json.dump({"nodes": resources, "links": []}, open("button_battery_station_resources_unilab.json", "w"), indent=2) + + + #print(resources) + http_client.remote_addr = "https://uni-lab.test.bohrium.com/api/v1" + + http_client.resource_add(resources) \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/button_battery_station_resources_unilab_big.json b/unilabos/devices/workstation/coin_cell_assembly/button_battery_station_resources_unilab_big.json new file mode 100644 index 0000000..9468817 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/button_battery_station_resources_unilab_big.json @@ -0,0 +1,1895 @@ +{ + "nodes": [ + { + "id": "coin_cell_deck", + "name": "coin_cell_deck", + "sample_id": null, + "children": [ + "liaopan1", + "liaopan2" + ], + "parent": null, + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "CoincellDeck", + "size_x": 1620.0, + "size_y": 1270.0, + "size_z": 500.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "coin_cell_deck", + "barcode": null + }, + "data": {} + }, + { + "id": "liaopan1", + "name": "liaopan1", + "sample_id": null, + "children": [ + "liaopan1_materialhole_0_0", + "liaopan1_materialhole_0_1", + "liaopan1_materialhole_0_2", + "liaopan1_materialhole_0_3", + "liaopan1_materialhole_1_0", + "liaopan1_materialhole_1_1", + "liaopan1_materialhole_1_2", + "liaopan1_materialhole_1_3", + "liaopan1_materialhole_2_0", + "liaopan1_materialhole_2_1", + "liaopan1_materialhole_2_2", + "liaopan1_materialhole_2_3", + "liaopan1_materialhole_3_0", + "liaopan1_materialhole_3_1", + "liaopan1_materialhole_3_2", + "liaopan1_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120.8, + "size_y": 120.5, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan1_materialhole_0_0", + "B1": "liaopan1_materialhole_0_1", + "C1": "liaopan1_materialhole_0_2", + "D1": "liaopan1_materialhole_0_3", + "A2": "liaopan1_materialhole_1_0", + "B2": "liaopan1_materialhole_1_1", + "C2": "liaopan1_materialhole_1_2", + "D2": "liaopan1_materialhole_1_3", + "A3": "liaopan1_materialhole_2_0", + "B3": "liaopan1_materialhole_2_1", + "C3": "liaopan1_materialhole_2_2", + "D3": "liaopan1_materialhole_2_3", + "A4": "liaopan1_materialhole_3_0", + "B4": "liaopan1_materialhole_3_1", + "C4": "liaopan1_materialhole_3_2", + "D4": "liaopan1_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan1_materialhole_0_0", + "name": "liaopan1_materialhole_0_0", + "sample_id": null, + "children": [ + "jipian_0" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_0", + "name": "jipian_0", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_1", + "name": "liaopan1_materialhole_0_1", + "sample_id": null, + "children": [], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_2", + "name": "liaopan1_materialhole_0_2", + "sample_id": null, + "children": [ + "jipian_2" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_2", + "name": "jipian_2", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_3", + "name": "liaopan1_materialhole_0_3", + "sample_id": null, + "children": [ + "jipian_3" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_3", + "name": "jipian_3", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_0", + "name": "liaopan1_materialhole_1_0", + "sample_id": null, + "children": [ + "jipian_4" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_4", + "name": "jipian_4", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_1", + "name": "liaopan1_materialhole_1_1", + "sample_id": null, + "children": [ + "jipian_5" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_5", + "name": "jipian_5", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_2", + "name": "liaopan1_materialhole_1_2", + "sample_id": null, + "children": [ + "jipian_6" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_6", + "name": "jipian_6", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_3", + "name": "liaopan1_materialhole_1_3", + "sample_id": null, + "children": [ + "jipian_7" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_7", + "name": "jipian_7", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_0", + "name": "liaopan1_materialhole_2_0", + "sample_id": null, + "children": [ + "jipian_8" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_8", + "name": "jipian_8", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_1", + "name": "liaopan1_materialhole_2_1", + "sample_id": null, + "children": [ + "jipian_9" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_9", + "name": "jipian_9", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_2", + "name": "liaopan1_materialhole_2_2", + "sample_id": null, + "children": [ + "jipian_10" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_10", + "name": "jipian_10", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_3", + "name": "liaopan1_materialhole_2_3", + "sample_id": null, + "children": [ + "jipian_11" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_11", + "name": "jipian_11", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_0", + "name": "liaopan1_materialhole_3_0", + "sample_id": null, + "children": [ + "jipian_12" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_12", + "name": "jipian_12", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_1", + "name": "liaopan1_materialhole_3_1", + "sample_id": null, + "children": [ + "jipian_13" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_13", + "name": "jipian_13", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_2", + "name": "liaopan1_materialhole_3_2", + "sample_id": null, + "children": [ + "jipian_14" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_14", + "name": "jipian_14", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_3", + "name": "liaopan1_materialhole_3_3", + "sample_id": null, + "children": [ + "jipian_15" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_15", + "name": "jipian_15", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan2", + "name": "liaopan2", + "sample_id": null, + "children": [ + "liaopan2_materialhole_0_0", + "liaopan2_materialhole_0_1", + "liaopan2_materialhole_0_2", + "liaopan2_materialhole_0_3", + "liaopan2_materialhole_1_0", + "liaopan2_materialhole_1_1", + "liaopan2_materialhole_1_2", + "liaopan2_materialhole_1_3", + "liaopan2_materialhole_2_0", + "liaopan2_materialhole_2_1", + "liaopan2_materialhole_2_2", + "liaopan2_materialhole_2_3", + "liaopan2_materialhole_3_0", + "liaopan2_materialhole_3_1", + "liaopan2_materialhole_3_2", + "liaopan2_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 500, + "y": 0, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120.8, + "size_y": 120.5, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan2_materialhole_0_0", + "B1": "liaopan2_materialhole_0_1", + "C1": "liaopan2_materialhole_0_2", + "D1": "liaopan2_materialhole_0_3", + "A2": "liaopan2_materialhole_1_0", + "B2": "liaopan2_materialhole_1_1", + "C2": "liaopan2_materialhole_1_2", + "D2": "liaopan2_materialhole_1_3", + "A3": "liaopan2_materialhole_2_0", + "B3": "liaopan2_materialhole_2_1", + "C3": "liaopan2_materialhole_2_2", + "D3": "liaopan2_materialhole_2_3", + "A4": "liaopan2_materialhole_3_0", + "B4": "liaopan2_materialhole_3_1", + "C4": "liaopan2_materialhole_3_2", + "D4": "liaopan2_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan2_materialhole_0_0", + "name": "liaopan2_materialhole_0_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_1", + "name": "liaopan2_materialhole_0_1", + "sample_id": null, + "children": [ + "jipian_1" + ], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_1", + "name": "jipian_1", + "sample_id": null, + "children": [], + "parent": "liaopan2_materialhole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_2", + "name": "liaopan2_materialhole_0_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_3", + "name": "liaopan2_materialhole_0_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_0", + "name": "liaopan2_materialhole_1_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_1", + "name": "liaopan2_materialhole_1_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_2", + "name": "liaopan2_materialhole_1_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_3", + "name": "liaopan2_materialhole_1_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_0", + "name": "liaopan2_materialhole_2_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_1", + "name": "liaopan2_materialhole_2_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_2", + "name": "liaopan2_materialhole_2_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_3", + "name": "liaopan2_materialhole_2_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_0", + "name": "liaopan2_materialhole_3_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_1", + "name": "liaopan2_materialhole_3_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_2", + "name": "liaopan2_materialhole_3_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_3", + "name": "liaopan2_materialhole_3_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + } + ], + "links": [] +} \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/cellconfig.py b/unilabos/devices/workstation/coin_cell_assembly/cellconfig.py new file mode 100644 index 0000000..957f62d --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/cellconfig.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +#coding=utf-8 + +ENV = 'pro'#'test' +class MQConfig: + """MQTT 配置类""" + lab_id: str = '9F05593C' + instance_id: str = 'mqtt-cn-dsr48m6jy02' + + group_id: str = 'GID_prod' + broker_url: str = 'mqtt-cn-dsr48m6jy02.mqtt.aliyuncs.com' + port: int = 8883 + ca_content: str = '''-----BEGIN CERTIFICATE----- +MIID3jCCAsagAwIBAgIUDyIgmg4qZtMPa8r2Vvn1b1fgJ+YwDQYJKoZIhvcNAQEL +BQAwSzELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkJKMQswCQYDVQQHDAJCSjEQMA4G +A1UECgwHZHAudGVjaDEQMA4GA1UECwwHdW5pLWxhYjAeFw0yNTA1MDYxNTE0Mjda +Fw0zNTA1MDQxNTE0MjdaMEsxCzAJBgNVBAYTAkNOMQswCQYDVQQIDAJCSjELMAkG +A1UEBwwCQkoxEDAOBgNVBAoMB2RwLnRlY2gxEDAOBgNVBAsMB3VuaS1sYWIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDnTBywX+6DJ2n+prNKvylBBJF6 +NHQrCt2cztZfswHsW4QhAbDddp4PRzNVzKtIfHX5ZrXGbxNT1/TqQYXKiFjKbfPC +VHTrS6+95LP3MxNTlBWHP6d2uI45KwrGgQ7D1uPDG1wZsfuJxvOkfAIxZRCDUMJr +erYYK/p2/GVMAO5YKE7wENUMN+iLfVQRqQJRgte9z0B35DxUeOUblJDun0Dpl/6L +0km/YRrjUKA/5+u/h+Ko9+36L1DAi+9rm3eyp+BQHBy5aiVhAG6uAJeMjbZMxwxz +ixg9cWNxP1BW+aQQzixbEQ+YlO9+w/soJkLstiK7jF8uIg2QvmNUKNlqab0pAgMB +AAGjgbkwgbYwHQYDVR0OBBYEFAqg0r7f6ngWODyVxVWHWM06b8wDMIGGBgNVHSME +fzB9gBQKoNK+3+p4Fjg8lcVVh1jNOm/MA6FPpE0wSzELMAkGA1UEBhMCQ04xCzAJ +BgNVBAgMAkJKMQswCQYDVQQHDAJCSjEQMA4GA1UECgwHZHAudGVjaDEQMA4GA1UE +CwwHdW5pLWxhYoIUDyIgmg4qZtMPa8r2Vvn1b1fgJ+YwDAYDVR0TBAUwAwEB/zAN +BgkqhkiG9w0BAQsFAAOCAQEAMylGHHhRCI8JLTizxqk2HaOFkF/WfnYC3XyNx3bK +9KqwVcvaqES+C058lits5nCV1qjjSnKt6xU11S8C6E28Kazh+wMqnSw63fz4UOY5 +4cekPCPy8XcWlOY6UW2N27GR0c9JDo9ovruOn1Y4KjATpAQI4W2tPAQ2gCVSNpu1 +bw5uw35yJSRzdQIHlsVbslvj2wcugK3GZHmmxJK+q9ww7G6xXtE2Y0+vl6AZRj+I +lcTy5TNNDZiiboIlAt+K3m4hxzSgGPbmFPJX3Lw3i+YMR/0PrWfXqxZgicO/V6/d +SgGBqq/tH1caiaEjCFudSZcOiZvHIlb09O4qL7mCtWEiEQ== +-----END CERTIFICATE----- +''' + cert_content: str = '''-----BEGIN CERTIFICATE----- +MIIDlTCCAn2gAwIBAgIUa/ce6dpJ8K7XNvT0LknVmLgfJMIwDQYJKoZIhvcNAQEL +BQAwSzELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkJKMQswCQYDVQQHDAJCSjEQMA4G +A1UECgwHZHAudGVjaDEQMA4GA1UECwwHdW5pLWxhYjAeFw0yNTA4MTMwNDQ0MDVa +Fw0yNjEyMjYwNDQ0MDVaMHoxCzAJBgNVBAYTAkNOMQswCQYDVQQIDAJCSjELMAkG +A1UEBwwCQkoxDDAKBgNVBAoMA0RQVDENMAsGA1UECwwETVFUVDEhMB8GCSqGSIb3 +DQEJARYSaHVhaGFpbWluZ0BkcC50ZWNoMREwDwYDVQQDDAg0NzJBMjZBQzCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPc4NHXcwaFX3jSN6DnDYoY7ON6c +AeVIlcQp3CMHnulh4t3I3Fnsyelpc809s7l5vEpAjMIuZ40DJKZQmV9ckmeylMiY +bAk851+i8YcRQPeYYY7Ggt0sfkY3TWIIqptZtlIhXtkTCWw6xpHAPkYxqNTiUN/0 +vwQWwiBS7WqD8NVjNhhHootYLsMjnQYc162L8nUwzG2pjB3UYqOldC3FkHXvBkG2 +Oeex8VM8Urblv0huCmoFRyuMmNol0QWqp+6nwAgdvf89Z38NJByPI9VHaBB/VV1F +HiAZe3H8Ph7wzgUSXBuVHJ4BaeJbg4+ax6BccpaQn26jgpJGUEj+YR+NwdMCAwEA +AaNCMEAwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCAvQwIAYDVR0lAQH/BBYw +FAYIKwYBBQUHAwIGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4IBAQAZaF8puP0/ +OcRM7Gcd4LrF8H/WG0Q7WM0T9BWGvee6A+Fcd4ajBC0S0tIfdsfYat0+g4U57jrr +vaQeZGFKc4YKVui8vSuth82fcsFk5fpyhz4JJRggzeoby+0gNx9eYDJwLIvbVy4Y +2LKGq+rsO07QF54jtwB4WpDNFnEIadXyjPBsMy/0Ssbetp827WYZygXYyAcUlCfN +Wns7K0phfZJwIMQgPs3d4mGwCC+xaRIB3GGjUGFXV1sFItjkTUHCvm+phw/MTpRp +pauplyDcWYux7z1dKhbuHElzCEqxZNwyI0nGJlRFP13Oo+jnuDO7gQh2lyz/AFyX +KyTA3xFZduHO +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID3jCCAsagAwIBAgIUDyIgmg4qZtMPa8r2Vvn1b1fgJ+YwDQYJKoZIhvcNAQEL +BQAwSzELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkJKMQswCQYDVQQHDAJCSjEQMA4G +A1UECgwHZHAudGVjaDEQMA4GA1UECwwHdW5pLWxhYjAeFw0yNTA1MDYxNTE0Mjda +Fw0zNTA1MDQxNTE0MjdaMEsxCzAJBgNVBAYTAkNOMQswCQYDVQQIDAJCSjELMAkG +A1UEBwwCQkoxEDAOBgNVBAoMB2RwLnRlY2gxEDAOBgNVBAsMB3VuaS1sYWIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDnTBywX+6DJ2n+prNKvylBBJF6 +NHQrCt2cztZfswHsW4QhAbDddp4PRzNVzKtIfHX5ZrXGbxNT1/TqQYXKiFjKbfPC +VHTrS6+95LP3MxNTlBWHP6d2uI45KwrGgQ7D1uPDG1wZsfuJxvOkfAIxZRCDUMJr +erYYK/p2/GVMAO5YKE7wENUMN+iLfVQRqQJRgte9z0B35DxUeOUblJDun0Dpl/6L +0km/YRrjUKA/5+u/h+Ko9+36L1DAi+9rm3eyp+BQHBy5aiVhAG6uAJeMjbZMxwxz +ixg9cWNxP1BW+aQQzixbEQ+YlO9+w/soJkLstiK7jF8uIg2QvmNUKNlqab0pAgMB +AAGjgbkwgbYwHQYDVR0OBBYEFAqg0r7f6ngWODyVxVWHWM06b8wDMIGGBgNVHSME +fzB9gBQKoNK+3+p4Fjg8lcVVh1jNOm/MA6FPpE0wSzELMAkGA1UEBhMCQ04xCzAJ +BgNVBAgMAkJKMQswCQYDVQQHDAJCSjEQMA4GA1UECgwHZHAudGVjaDEQMA4GA1UE +CwwHdW5pLWxhYoIUDyIgmg4qZtMPa8r2Vvn1b1fgJ+YwDAYDVR0TBAUwAwEB/zAN +BgkqhkiG9w0BAQsFAAOCAQEAMylGHHhRCI8JLTizxqk2HaOFkF/WfnYC3XyNx3bK +9KqwVcvaqES+C058lits5nCV1qjjSnKt6xU11S8C6E28Kazh+wMqnSw63fz4UOY5 +4cekPCPy8XcWlOY6UW2N27GR0c9JDo9ovruOn1Y4KjATpAQI4W2tPAQ2gCVSNpu1 +bw5uw35yJSRzdQIHlsVbslvj2wcugK3GZHmmxJK+q9ww7G6xXtE2Y0+vl6AZRj+I +lcTy5TNNDZiiboIlAt+K3m4hxzSgGPbmFPJX3Lw3i+YMR/0PrWfXqxZgicO/V6/d +SgGBqq/tH1caiaEjCFudSZcOiZvHIlb09O4qL7mCtWEiEQ== +-----END CERTIFICATE----- +''' + key_content: str = '''-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA9zg0ddzBoVfeNI3oOcNihjs43pwB5UiVxCncIwee6WHi3cjc +WezJ6WlzzT2zuXm8SkCMwi5njQMkplCZX1ySZ7KUyJhsCTznX6LxhxFA95hhjsaC +3Sx+RjdNYgiqm1m2UiFe2RMJbDrGkcA+RjGo1OJQ3/S/BBbCIFLtaoPw1WM2GEei +i1guwyOdBhzXrYvydTDMbamMHdRio6V0LcWQde8GQbY557HxUzxStuW/SG4KagVH +K4yY2iXRBaqn7qfACB29/z1nfw0kHI8j1UdoEH9VXUUeIBl7cfw+HvDOBRJcG5Uc +ngFp4luDj5rHoFxylpCfbqOCkkZQSP5hH43B0wIDAQABAoIBAAPzz0ZUcqmR1Eva +5PH98gQzp2wB9snLY86HY3Z/JVAPf5Ht9sbAUWHhT8PVoWpIasSmFbuJxz6DRk3S +M8VVVipxxgcTWqo/JOD4HZiCNfcRru6+5dHxZ4p2B/n4EWfoy+KyEZkgd5jQFONj +jIX+rDR3qZzFqoBRhQSHLuD+i66eZ7l1LOqsnk51r3nTCnGmdyV8fll56MMB5D6+ +8LN2rwbmSYX/UIBBqHUthgEt2onFNaetTLgSa3RSNGZ3xEZt4N32vw1SARxItuso +npAAY77POMUwWe3666fETI+yr/gJuppvTF4sQUXy7I4iz7I18n2SYivHabdgnk6H +7y1TcGECgYEA/NbBLFz1YPOQiT6TsuitIlfWcFWXYI4yHh/Mwwm/heHV683HrUti +RSHWbFxggW70BYJbGAQprEe9UIRVdP9YNi3aPeN5WNfnTFHlN2HRiKenlETM1tw9 +yaSWjNbAyc2ka+l1EblMJy92xoCkErS8riEPW83o+3+LqJwnjsJ8tVECgYEA+k93 +AyNpXuOZldAoqSHF3wHgzgd2jhfVdQcNlz9sLfT8TAdoR01mdBtdytcYAH+FHplW +wlkCfpT1RPf3fEd0Asy727pJnL9v/QfY/BB+vfgWKUQg9CWNIevwItCaTNOSekis +lKl5dxNGOyouU7rPbTj9BC26OHA50Z3vLMKmi+MCgYEAy0Sb6N6TJ26pNK1qcNs+ +1e1oKMem+6lWAYHvTJ35q9jz8q9taJTCXHHnwRZDP8vDwuoZ8iTmm+rQ+HprebQP +Zv9WBYtrc1GgUmtErFGn8wVWZI0rYVGPGx2HK5M7SwJYvajixW0DHD28b7ncLm2/ +gv5xKo1QUWEpFlT0OIGDYQECgYEA8WRlH6+s1Iel++ZM8B7T1ibXh5mG6a1ue3eb +0bqmNwPFtASIugqYvWwO3ajlSsWvuTyjgLWaRDye9C42i7HU3UZX/KUAjJvKAjjp +Nt0pfUadCJrdNNZp7sa8RLbrtx9qaWdgl9WAgCckWbZqCvFjTK/iwX7f0cHY4J/w +ojftqYUCgYARM6YaEJuBJEBmZV1I0rweiguqWssZz2j1awSlsfYxckwnci4VtSMI +D/sp0Wp0yn2N4cgqp49BFD0rCQCTsASVICEf9HWdMQXsUhzWsz4SjVEhjWWC1VAk +sEL+BOcbsHy3qMbV2uKBHrhuZShDdy5KtCm9TB+7zTWyDVHwE24nig== +-----END RSA PRIVATE KEY----- +''' + +# HTTP配置 +class HTTPConfig: + remote_addr = "https://uni-lab.test.bohrium.com/api/v1" diff --git a/unilabos/devices/workstation/coin_cell_assembly/celljson.json b/unilabos/devices/workstation/coin_cell_assembly/celljson.json new file mode 100644 index 0000000..ad90417 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/celljson.json @@ -0,0 +1,1332 @@ +{ + "nodes": [ + { + "id": "BatteryStation", + "name": "扣电工作站", + "children": [ + "coin_cell_deck" + ], + "parent": null, + "type": "device", + "class": "bettery_station_registry", + "position": { + "x": 600, + "y": 400, + "z": 0 + }, + "config": { + "debug_mode": false, + "_comment": "protocol_type接外部工站固定写法字段,一般为空,station_resource写法也固定", + "protocol_type": [], + "station_resource": { + "data": { + "_resource_child_name": "coin_cell_deck", + "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" + } + }, + + "address": "192.168.1.20", + "port": 502 + }, + "data": {} + }, + { + "id": "coin_cell_deck", + "name": "coin_cell_deck", + "sample_id": null, + "children": [ + "ban_cao_wei" + ], + "parent": "BatteryStation", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "CoincellDeck", + "size_x": 1620.0, + "size_y": 1270.0, + "size_z": 500.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "coin_cell_deck", + "barcode": null + }, + "data": {} + }, + { + "id": "ban_cao_wei", + "name": "ban_cao_wei", + "sample_id": null, + "children": [ + "plate_1" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "PlateSlot", + "size_x": 0, + "size_y": 0, + "size_z": 0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "plate_slot", + "model": null, + "barcode": null, + "max_plates": 8 + }, + "data": {} + }, + { + "id": "plate_1", + "name": "plate_1", + "sample_id": null, + "children": [ + "plate_1_materialhole_0_0", + "plate_1_materialhole_0_1", + "plate_1_materialhole_0_2", + "plate_1_materialhole_0_3", + "plate_1_materialhole_1_0", + "plate_1_materialhole_1_1", + "plate_1_materialhole_1_2", + "plate_1_materialhole_1_3", + "plate_1_materialhole_2_0", + "plate_1_materialhole_2_1", + "plate_1_materialhole_2_2", + "plate_1_materialhole_2_3", + "plate_1_materialhole_3_0", + "plate_1_materialhole_3_1", + "plate_1_materialhole_3_2", + "plate_1_materialhole_3_3" + ], + "parent": "ban_cao_wei", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "plate_1_materialhole_0_0", + "B1": "plate_1_materialhole_0_1", + "C1": "plate_1_materialhole_0_2", + "D1": "plate_1_materialhole_0_3", + "A2": "plate_1_materialhole_1_0", + "B2": "plate_1_materialhole_1_1", + "C2": "plate_1_materialhole_1_2", + "D2": "plate_1_materialhole_1_3", + "A3": "plate_1_materialhole_2_0", + "B3": "plate_1_materialhole_2_1", + "C3": "plate_1_materialhole_2_2", + "D3": "plate_1_materialhole_2_3", + "A4": "plate_1_materialhole_3_0", + "B4": "plate_1_materialhole_3_1", + "C4": "plate_1_materialhole_3_2", + "D4": "plate_1_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "plate_1_materialhole_0_0", + "name": "plate_1_materialhole_0_0", + "sample_id": null, + "children": [ + "hole_0_sheet_1" + ], + "parent": "plate_1", + "type": "container", + "class": "", + "position": { + "x": -29.5, + "y": 30.5, + "z": 1 + }, + "config": { + "type": "MaterialHole", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "depth": 1.0, + "diameter": 14, + "info": "", + "max_sheets": 1 + } + }, + { + "id": "hole_0_sheet_1", + "name": "hole_0_sheet_1", + "sample_id": null, + "children": [], + "parent": "plate_1_materialhole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "info": "NMC", + "mass": 5.0, + "material_type": "positive_electrode", + "thickness": 0.1 + } + }, + { + "id": "plate_1_materialhole_0_1", + "name": "plate_1_materialhole_0_1", + "sample_id": null, + "children": [ + "hole_1_sheet_1" + ], + "parent": "plate_1", + "type": "container", + "class": "", + "position": { + "x": -29.5, + "y": 10.5, + "z": 1 + }, + "config": { + "type": "MaterialHole", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "depth": 1.0, + "diameter": 14, + "info": "", + "max_sheets": 1 + } + }, + { + "id": "hole_1_sheet_1", + "name": "hole_1_sheet_1", + "sample_id": null, + "children": [], + "parent": "plate_1_materialhole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "info": "NMC", + "mass": 5.0, + "material_type": "positive_electrode", + "thickness": 0.1 + } + }, + { + "id": "plate_1_materialhole_0_2", + "name": "plate_1_materialhole_0_2", + "sample_id": null, + "children": [ + "hole_2_sheet_1" + ], + "parent": "plate_1", + "type": "container", + "class": "", + "position": { + "x": -29.5, + "y": -9.5, + "z": 1 + }, + "config": { + "type": "MaterialHole", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "depth": 1.0, + "diameter": 14, + "info": "", + "max_sheets": 1 + } + }, + { + "id": "hole_2_sheet_1", + "name": "hole_2_sheet_1", + "sample_id": null, + "children": [], + "parent": "plate_1_materialhole_0_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "info": "NMC", + "mass": 5.0, + "material_type": "positive_electrode", + "thickness": 0.1 + } + }, + { + "id": "plate_1_materialhole_0_3", + "name": "plate_1_materialhole_0_3", + "sample_id": null, + "children": [ + "hole_3_sheet_1" + ], + "parent": "plate_1", + "type": "container", + "class": "", + "position": { + "x": -29.5, + "y": -29.5, + "z": 1 + }, + "config": { + "type": "MaterialHole", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "depth": 1.0, + "diameter": 14, + "info": "", + "max_sheets": 1 + } + }, + { + "id": "hole_3_sheet_1", + "name": "hole_3_sheet_1", + "sample_id": null, + "children": [], + "parent": "plate_1_materialhole_0_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "info": "NMC", + "mass": 5.0, + "material_type": "positive_electrode", + "thickness": 0.1 + } + }, + { + "id": "plate_1_materialhole_1_0", + "name": "plate_1_materialhole_1_0", + "sample_id": null, + "children": [ + "hole_4_sheet_1" + ], + "parent": "plate_1", + "type": "container", + "class": "", + "position": { + "x": -9.5, + "y": 30.5, + "z": 1 + }, + "config": { + "type": "MaterialHole", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "depth": 1.0, + "diameter": 14, + "info": "", + "max_sheets": 1 + } + }, + { + "id": "hole_4_sheet_1", + "name": "hole_4_sheet_1", + "sample_id": null, + "children": [], + "parent": "plate_1_materialhole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "info": "NMC", + "mass": 5.0, + "material_type": "positive_electrode", + "thickness": 0.1 + } + }, + { + "id": "plate_1_materialhole_1_1", + "name": "plate_1_materialhole_1_1", + "sample_id": null, + "children": [ + "hole_5_sheet_1" + ], + "parent": "plate_1", + "type": "container", + "class": "", + "position": { + "x": -9.5, + "y": 10.5, + "z": 1 + }, + "config": { + "type": "MaterialHole", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "depth": 1.0, + "diameter": 14, + "info": "", + "max_sheets": 1 + } + }, + { + "id": "hole_5_sheet_1", + "name": "hole_5_sheet_1", + "sample_id": null, + "children": [], + "parent": "plate_1_materialhole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "info": "NMC", + "mass": 5.0, + "material_type": "positive_electrode", + "thickness": 0.1 + } + }, + { + "id": "plate_1_materialhole_1_2", + "name": "plate_1_materialhole_1_2", + "sample_id": null, + "children": [ + "hole_6_sheet_1" + ], + "parent": "plate_1", + "type": "container", + "class": "", + "position": { + "x": -9.5, + "y": -9.5, + "z": 1 + }, + "config": { + "type": "MaterialHole", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "depth": 1.0, + "diameter": 14, + "info": "", + "max_sheets": 1 + } + }, + { + "id": "hole_6_sheet_1", + "name": "hole_6_sheet_1", + "sample_id": null, + "children": [], + "parent": "plate_1_materialhole_1_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "info": "NMC", + "mass": 5.0, + "material_type": "positive_electrode", + "thickness": 0.1 + } + }, + { + "id": "plate_1_materialhole_1_3", + "name": "plate_1_materialhole_1_3", + "sample_id": null, + "children": [ + "hole_7_sheet_1" + ], + "parent": "plate_1", + "type": "container", + "class": "", + "position": { + "x": -9.5, + "y": -29.5, + "z": 1 + }, + "config": { + "type": "MaterialHole", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "depth": 1.0, + "diameter": 14, + "info": "", + "max_sheets": 1 + } + }, + { + "id": "hole_7_sheet_1", + "name": "hole_7_sheet_1", + "sample_id": null, + "children": [], + "parent": "plate_1_materialhole_1_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "info": "NMC", + "mass": 5.0, + "material_type": "positive_electrode", + "thickness": 0.1 + } + }, + { + "id": "plate_1_materialhole_2_0", + "name": "plate_1_materialhole_2_0", + "sample_id": null, + "children": [ + "hole_8_sheet_1" + ], + "parent": "plate_1", + "type": "container", + "class": "", + "position": { + "x": 10.5, + "y": 30.5, + "z": 1 + }, + "config": { + "type": "MaterialHole", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "depth": 1.0, + "diameter": 14, + "info": "", + "max_sheets": 1 + } + }, + { + "id": "hole_8_sheet_1", + "name": "hole_8_sheet_1", + "sample_id": null, + "children": [], + "parent": "plate_1_materialhole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "info": "NMC", + "mass": 5.0, + "material_type": "positive_electrode", + "thickness": 0.1 + } + }, + { + "id": "plate_1_materialhole_2_1", + "name": "plate_1_materialhole_2_1", + "sample_id": null, + "children": [ + "hole_9_sheet_1" + ], + "parent": "plate_1", + "type": "container", + "class": "", + "position": { + "x": 10.5, + "y": 10.5, + "z": 1 + }, + "config": { + "type": "MaterialHole", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "depth": 1.0, + "diameter": 14, + "info": "", + "max_sheets": 1 + } + }, + { + "id": "hole_9_sheet_1", + "name": "hole_9_sheet_1", + "sample_id": null, + "children": [], + "parent": "plate_1_materialhole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "info": "NMC", + "mass": 5.0, + "material_type": "positive_electrode", + "thickness": 0.1 + } + }, + { + "id": "plate_1_materialhole_2_2", + "name": "plate_1_materialhole_2_2", + "sample_id": null, + "children": [ + "hole_10_sheet_1" + ], + "parent": "plate_1", + "type": "container", + "class": "", + "position": { + "x": 10.5, + "y": -9.5, + "z": 1 + }, + "config": { + "type": "MaterialHole", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "depth": 1.0, + "diameter": 14, + "info": "", + "max_sheets": 1 + } + }, + { + "id": "hole_10_sheet_1", + "name": "hole_10_sheet_1", + "sample_id": null, + "children": [], + "parent": "plate_1_materialhole_2_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "info": "NMC", + "mass": 5.0, + "material_type": "positive_electrode", + "thickness": 0.1 + } + }, + { + "id": "plate_1_materialhole_2_3", + "name": "plate_1_materialhole_2_3", + "sample_id": null, + "children": [ + "hole_11_sheet_1" + ], + "parent": "plate_1", + "type": "container", + "class": "", + "position": { + "x": 10.5, + "y": -29.5, + "z": 1 + }, + "config": { + "type": "MaterialHole", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "depth": 1.0, + "diameter": 14, + "info": "", + "max_sheets": 1 + } + }, + { + "id": "hole_11_sheet_1", + "name": "hole_11_sheet_1", + "sample_id": null, + "children": [], + "parent": "plate_1_materialhole_2_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "info": "NMC", + "mass": 5.0, + "material_type": "positive_electrode", + "thickness": 0.1 + } + }, + { + "id": "plate_1_materialhole_3_0", + "name": "plate_1_materialhole_3_0", + "sample_id": null, + "children": [ + "hole_12_sheet_1" + ], + "parent": "plate_1", + "type": "container", + "class": "", + "position": { + "x": 30.5, + "y": 30.5, + "z": 1 + }, + "config": { + "type": "MaterialHole", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "depth": 1.0, + "diameter": 14, + "info": "", + "max_sheets": 1 + } + }, + { + "id": "hole_12_sheet_1", + "name": "hole_12_sheet_1", + "sample_id": null, + "children": [], + "parent": "plate_1_materialhole_3_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "info": "NMC", + "mass": 5.0, + "material_type": "positive_electrode", + "thickness": 0.1 + } + }, + { + "id": "plate_1_materialhole_3_1", + "name": "plate_1_materialhole_3_1", + "sample_id": null, + "children": [ + "hole_13_sheet_1" + ], + "parent": "plate_1", + "type": "container", + "class": "", + "position": { + "x": 30.5, + "y": 10.5, + "z": 1 + }, + "config": { + "type": "MaterialHole", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "depth": 1.0, + "diameter": 14, + "info": "", + "max_sheets": 1 + } + }, + { + "id": "hole_13_sheet_1", + "name": "hole_13_sheet_1", + "sample_id": null, + "children": [], + "parent": "plate_1_materialhole_3_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "info": "NMC", + "mass": 5.0, + "material_type": "positive_electrode", + "thickness": 0.1 + } + }, + { + "id": "plate_1_materialhole_3_2", + "name": "plate_1_materialhole_3_2", + "sample_id": null, + "children": [ + "hole_14_sheet_1" + ], + "parent": "plate_1", + "type": "container", + "class": "", + "position": { + "x": 30.5, + "y": -9.5, + "z": 1 + }, + "config": { + "type": "MaterialHole", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "depth": 1.0, + "diameter": 14, + "info": "", + "max_sheets": 1 + } + }, + { + "id": "hole_14_sheet_1", + "name": "hole_14_sheet_1", + "sample_id": null, + "children": [], + "parent": "plate_1_materialhole_3_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "info": "NMC", + "mass": 5.0, + "material_type": "positive_electrode", + "thickness": 0.1 + } + }, + { + "id": "plate_1_materialhole_3_3", + "name": "plate_1_materialhole_3_3", + "sample_id": null, + "children": [ + "hole_15_sheet_1" + ], + "parent": "plate_1", + "type": "container", + "class": "", + "position": { + "x": 30.5, + "y": -29.5, + "z": 1 + }, + "config": { + "type": "MaterialHole", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "depth": 1.0, + "diameter": 14, + "info": "", + "max_sheets": 1 + } + }, + { + "id": "hole_15_sheet_1", + "name": "hole_15_sheet_1", + "sample_id": null, + "children": [], + "parent": "plate_1_materialhole_3_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 1, + "size_y": 1, + "size_z": 1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "info": "NMC", + "mass": 5.0, + "material_type": "positive_electrode", + "thickness": 0.1 + } + } + ], + "links": [] +} \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py new file mode 100644 index 0000000..eb23be5 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -0,0 +1,1163 @@ +import csv +import json +import os +import threading +import time +from datetime import datetime +from typing import Any, Dict, Optional +from pylabrobot.resources import Resource as PLRResource +from unilabos_msgs.msg import Resource +from unilabos.device_comms.modbus_plc.client import ModbusTcpClient +from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import MaterialHole, MaterialPlate +from unilabos.devices.workstation.workstation_base import WorkstationBase +from unilabos.device_comms.modbus_plc.client import TCPClient, ModbusNode, PLCWorkflow, ModbusWorkflow, WorkflowAction, BaseClient +from unilabos.device_comms.modbus_plc.modbus import DeviceType, Base as ModbusNodeBase, DataType, WorderOrder +from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import * +from unilabos.ros.nodes.base_device_node import ROS2DeviceNode, BaseROS2DeviceNode +from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode + +#构建物料系统 + +class CoinCellAssemblyWorkstation(WorkstationBase): + def __init__( + self, + station_resource: CoincellDeck, + address: str = "192.168.1.20", + port: str = "502", + debug_mode: bool = True, + *args, + **kwargs, + ): + super().__init__( + #桌子 + station_resource=station_resource, + *args, + **kwargs, + ) + self.debug_mode = debug_mode + self.station_resource = station_resource + """ 连接初始化 """ + modbus_client = TCPClient(addr=address, port=port) + print("modbus_client", modbus_client) + if not debug_mode: + modbus_client.client.connect() + count = 100 + while count >0: + count -=1 + if modbus_client.client.is_socket_open(): + break + time.sleep(2) + if not modbus_client.client.is_socket_open(): + raise ValueError('modbus tcp connection failed') + else: + print("测试模式,跳过连接") + + """ 工站的配置 """ + self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_a.csv')) + self.client = modbus_client.register_node_list(self.nodes) + self.success = False + self.allow_data_read = False #允许读取函数运行标志位 + self.csv_export_thread = None + self.csv_export_running = False + self.csv_export_file = None + self.coin_num_N = 0 #已组装电池数量 + #创建一个物料台面,包含两个极片板 + #self.deck = create_a_coin_cell_deck() + + #self._ros_node.update_resource(self.deck) + + #ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ + # "resources": [self.deck] + #}) + + + def post_init(self, ros_node: ROS2WorkstationNode): + self._ros_node = ros_node + #self.deck = create_a_coin_cell_deck() + ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ + "resources": [self.station_resource] + }) + + # 批量操作在这里写 + async def change_hole_sheet_to_2(self, hole: MaterialHole): + hole._unilabos_state["max_sheets"] = 2 + return await self._ros_node.update_resource(hole) + + + async def fill_plate(self): + plate_1: MaterialPlate = self.station_resource.children[0].children[0] + #plate_1 + return await self._ros_node.update_resource(plate_1) + + #def run_assembly(self, wf_name: str, resource: PLRResource, params: str = "\{\}"): + # """启动工作流""" + # self.current_workflow_status = WorkflowStatus.RUNNING + # logger.info(f"工作站 {self.device_id} 启动工作流: {wf_name}") +# + # # TODO: 实现工作流逻辑 +# + # anode_sheet = self.deck.get_resource("anode_sheet") + + """ Action逻辑代码 """ + def _sys_start_cmd(self, cmd=None): + """设备启动命令 (可读写)""" + if cmd is not None: # 写入模式 + self.success = False + node = self.client.use_node('COIL_SYS_START_CMD') + ret = node.write(cmd) + print(ret) + self.success = True + return self.success + else: # 读取模式 + cmd_feedback, read_err = self.client.use_node('COIL_SYS_START_CMD').read(1) + return cmd_feedback[0] + + def _sys_stop_cmd(self, cmd=None): + """设备停止命令 (可读写)""" + if cmd is not None: # 写入模式 + self.success = False + node = self.client.use_node('COIL_SYS_STOP_CMD') + node.write(cmd) + self.success = True + return self.success + else: # 读取模式 + cmd_feedback, read_err = self.client.use_node('COIL_SYS_STOP_CMD').read(1) + return cmd_feedback[0] + + def _sys_reset_cmd(self, cmd=None): + """设备复位命令 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_SYS_RESET_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_SYS_RESET_CMD').read(1) + return cmd_feedback[0] + + def _sys_hand_cmd(self, cmd=None): + """手动模式命令 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_SYS_HAND_CMD').write(cmd) + self.success = True + print("步骤0") + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_SYS_HAND_CMD').read(1) + return cmd_feedback[0] + + def _sys_auto_cmd(self, cmd=None): + """自动模式命令 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_SYS_AUTO_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_SYS_AUTO_CMD').read(1) + return cmd_feedback[0] + + def _sys_init_cmd(self, cmd=None): + """初始化命令 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_SYS_INIT_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_SYS_INIT_CMD').read(1) + return cmd_feedback[0] + + def _unilab_send_msg_succ_cmd(self, cmd=None): + """UNILAB发送配方完毕 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_UNILAB_SEND_MSG_SUCC_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_UNILAB_SEND_MSG_SUCC_CMD').read(1) + return cmd_feedback[0] + + def _unilab_rec_msg_succ_cmd(self, cmd=None): + """UNILAB接收测试电池数据完毕 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_UNILAB_REC_MSG_SUCC_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_UNILAB_REC_MSG_SUCC_CMD').read(1) + return cmd_feedback + + + # ====================== 命令类指令(REG_x_) ====================== + def _unilab_send_msg_electrolyte_num(self, num=None): + """UNILAB写电解液使用瓶数(可读写)""" + if num is not None: + self.success = False + ret = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').write(num) + print(ret) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').read(1) + return cmd_feedback[0] + + def _unilab_send_msg_electrolyte_use_num(self, use_num=None): + """UNILAB写单次电解液使用瓶数(可读写)""" + if use_num is not None: + self.success = False + self.client.use_node('REG_MSG_ELECTROLYTE_USE_NUM').write(use_num) + self.success = True + return self.success + else: + return False + + def _unilab_send_msg_assembly_type(self, num=None): + """UNILAB写组装参数""" + if num is not None: + self.success = False + self.client.use_node('REG_MSG_ASSEMBLY_TYPE').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ASSEMBLY_TYPE').read(1) + return cmd_feedback[0] + + def _unilab_send_msg_electrolyte_vol(self, vol=None): + """UNILAB写电解液吸取量参数""" + if vol is not None: + self.success = False + self.client.use_node('REG_MSG_ELECTROLYTE_VOLUME').write(vol, data_type=DataType.FLOAT32, word_order=WorderOrder.LITTLE) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_VOLUME').read(2, word_order=WorderOrder.LITTLE) + return cmd_feedback[0] + + def _unilab_send_msg_assembly_pressure(self, vol=None): + """UNILAB写电池压制力""" + if vol is not None: + self.success = False + self.client.use_node('REG_MSG_ASSEMBLY_PRESSURE').write(vol, data_type=DataType.FLOAT32, word_order=WorderOrder.LITTLE) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ASSEMBLY_PRESSURE').read(2, word_order=WorderOrder.LITTLE) + return cmd_feedback[0] + + # ==================== 0905新增内容(COIL_x_STATUS) ==================== + def _unilab_send_electrolyte_bottle_num(self, num=None): + """UNILAB发送电解液瓶数完毕""" + if num is not None: + self.success = False + self.client.use_node('UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM').read(1) + return cmd_feedback[0] + + def _unilab_rece_electrolyte_bottle_num(self, num=None): + """设备请求接受电解液瓶数""" + if num is not None: + self.success = False + self.client.use_node('UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM').read(1) + return cmd_feedback[0] + + def _reg_msg_electrolyte_num(self, num=None): + """电解液已使用瓶数""" + if num is not None: + self.success = False + self.client.use_node('REG_MSG_ELECTROLYTE_NUM').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').read(1) + return cmd_feedback[0] + + def _reg_data_electrolyte_use_num(self, num=None): + """单瓶电解液完成组装数""" + if num is not None: + self.success = False + self.client.use_node('REG_DATA_ELECTROLYTE_USE_NUM').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_USE_NUM').read(1) + return cmd_feedback[0] + + def _unilab_send_finished_cmd(self, num=None): + """Unilab发送已知一组组装完成信号""" + if num is not None: + self.success = False + self.client.use_node('UNILAB_SEND_FINISHED_CMD').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('UNILAB_SEND_FINISHED_CMD').read(1) + return cmd_feedback[0] + + def _unilab_rece_finished_cmd(self, num=None): + """Unilab接收已知一组组装完成信号""" + if num is not None: + self.success = False + self.client.use_node('UNILAB_RECE_FINISHED_CMD').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('UNILAB_RECE_FINISHED_CMD').read(1) + return cmd_feedback[0] + + + + # ==================== 状态类属性(COIL_x_STATUS) ==================== + def _sys_start_status(self) -> bool: + """设备启动中( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_START_STATUS').read(1) + return status[0] + + def _sys_stop_status(self) -> bool: + """设备停止中( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_STOP_STATUS').read(1) + return status[0] + + def _sys_reset_status(self) -> bool: + """设备复位中( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_RESET_STATUS').read(1) + return status[0] + + def _sys_init_status(self) -> bool: + """设备初始化完成( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_INIT_STATUS').read(1) + return status[0] + + # 查找资源 + def modify_deck_name(self, resource_name: str): + # figure_res = self._ros_node.resource_tracker.figure_resource({"name": resource_name}) + # print(f"!!! figure_res: {type(figure_res)}") + self.station_resource.children[1] + return + + @property + def sys_status(self) -> str: + if self.debug_mode: + return "设备调试模式" + if self._sys_start_status(): + return "设备启动中" + elif self._sys_stop_status(): + return "设备停止中" + elif self._sys_reset_status(): + return "设备复位中" + elif self._sys_init_status(): + return "设备初始化中" + else: + return "未知状态" + + def _sys_hand_status(self) -> bool: + """设备手动模式( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_HAND_STATUS').read(1) + return status[0] + + def _sys_auto_status(self) -> bool: + """设备自动模式( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_AUTO_STATUS').read(1) + return status[0] + + @property + def sys_mode(self) -> str: + if self.debug_mode: + return "设备调试模式" + if self._sys_hand_status(): + return "设备手动模式" + elif self._sys_auto_status(): + return "设备自动模式" + else: + return "未知模式" + + @property + def request_rec_msg_status(self) -> bool: + """设备请求接受配方( BOOL)""" + if self.debug_mode: + return True + status, read_err = self.client.use_node('COIL_REQUEST_REC_MSG_STATUS').read(1) + return status[0] + + @property + def request_send_msg_status(self) -> bool: + """设备请求发送测试数据( BOOL)""" + if self.debug_mode: + return True + status, read_err = self.client.use_node('COIL_REQUEST_SEND_MSG_STATUS').read(1) + return status[0] + + # ======================= 其他属性(特殊功能) ======================== + ''' + @property + def warning_1(self) -> bool: + status, read_err = self.client.use_node('COIL_WARNING_1').read(1) + return status[0] + ''' + # ===================== 生产数据区 ====================== + + @property + def data_assembly_coin_cell_num(self) -> int: + """已完成电池数量 (INT16)""" + if self.debug_mode: + return 0 + num, read_err = self.client.use_node('REG_DATA_ASSEMBLY_COIN_CELL_NUM').read(1) + return num + + @property + def data_assembly_time(self) -> float: + """单颗电池组装时间 (秒, REAL/FLOAT32)""" + if self.debug_mode: + return 0 + time, read_err = self.client.use_node('REG_DATA_ASSEMBLY_PER_TIME').read(2, word_order=WorderOrder.LITTLE) + return time + + @property + def data_open_circuit_voltage(self) -> float: + """开路电压值 (FLOAT32)""" + if self.debug_mode: + return 0 + vol, read_err = self.client.use_node('REG_DATA_OPEN_CIRCUIT_VOLTAGE').read(2, word_order=WorderOrder.LITTLE) + return vol + + @property + def data_axis_x_pos(self) -> float: + """分液X轴当前位置 (FLOAT32)""" + if self.debug_mode: + return 0 + pos, read_err = self.client.use_node('REG_DATA_AXIS_X_POS').read(2, word_order=WorderOrder.LITTLE) + return pos + + @property + def data_axis_y_pos(self) -> float: + """分液Y轴当前位置 (FLOAT32)""" + if self.debug_mode: + return 0 + pos, read_err = self.client.use_node('REG_DATA_AXIS_Y_POS').read(2, word_order=WorderOrder.LITTLE) + return pos + + @property + def data_axis_z_pos(self) -> float: + """分液Z轴当前位置 (FLOAT32)""" + if self.debug_mode: + return 0 + pos, read_err = self.client.use_node('REG_DATA_AXIS_Z_POS').read(2, word_order=WorderOrder.LITTLE) + return pos + + @property + def data_pole_weight(self) -> float: + """当前电池正极片称重数据 (FLOAT32)""" + if self.debug_mode: + return 0 + weight, read_err = self.client.use_node('REG_DATA_POLE_WEIGHT').read(2, word_order=WorderOrder.LITTLE) + return weight + + @property + def data_assembly_pressure(self) -> int: + """当前电池压制力 (INT16)""" + if self.debug_mode: + return 0 + pressure, read_err = self.client.use_node('REG_DATA_ASSEMBLY_PRESSURE').read(1) + return pressure + + @property + def data_electrolyte_volume(self) -> int: + """当前电解液加注量 (INT16)""" + if self.debug_mode: + return 0 + vol, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_VOLUME').read(1) + return vol + + @property + def data_coin_num(self) -> int: + """当前电池数量 (INT16)""" + if self.debug_mode: + return 0 + num, read_err = self.client.use_node('REG_DATA_COIN_NUM').read(1) + return num + + @property + def data_coin_cell_code(self) -> str: + """电池二维码序列号 (STRING)""" + try: + # 尝试不同的字节序读取 + code_little, read_err = self.client.use_node('REG_DATA_COIN_CELL_CODE').read(10, word_order=WorderOrder.LITTLE) + print(code_little) + clean_code = code_little[-8:][::-1] + return clean_code + except Exception as e: + print(f"读取电池二维码失败: {e}") + return "N/A" + + + @property + def data_electrolyte_code(self) -> str: + try: + # 尝试不同的字节序读取 + code_little, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_CODE').read(10, word_order=WorderOrder.LITTLE) + print(code_little) + clean_code = code_little[-8:][::-1] + return clean_code + except Exception as e: + print(f"读取电解液二维码失败: {e}") + return "N/A" + + # ===================== 环境监控区 ====================== + @property + def data_glove_box_pressure(self) -> float: + """手套箱压力 (bar, FLOAT32)""" + if self.debug_mode: + return 0 + status, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_PRESSURE').read(2, word_order=WorderOrder.LITTLE) + return status + + @property + def data_glove_box_o2_content(self) -> float: + """手套箱氧含量 (ppm, FLOAT32)""" + if self.debug_mode: + return 0 + value, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_O2_CONTENT').read(2, word_order=WorderOrder.LITTLE) + return value + + @property + def data_glove_box_water_content(self) -> float: + """手套箱水含量 (ppm, FLOAT32)""" + if self.debug_mode: + return 0 + value, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_WATER_CONTENT').read(2, word_order=WorderOrder.LITTLE) + return value + +# @property +# def data_stack_vision_code(self) -> int: +# """物料堆叠复检图片编码 (INT16)""" +# if self.debug_mode: +# return 0 +# code, read_err = self.client.use_node('REG_DATA_STACK_VISON_CODE').read(1) +# #code, _ = self.client.use_node('REG_DATA_STACK_VISON_CODE').read(1).type +# print(f"读取物料堆叠复检图片编码", {code}, "error", type(code)) +# #print(code.type) +# # print(read_err) +# return int(code) + + def func_pack_device_init(self): + #切换手动模式 + print("切换手动模式") + self._sys_hand_cmd(True) + time.sleep(1) + while (self._sys_hand_status()) == False: + print("waiting for hand_cmd") + time.sleep(1) + #设备初始化 + self._sys_init_cmd(True) + time.sleep(1) + #sys_init_status为bool值,不加括号 + while (self._sys_init_status())== False: + print("waiting for init_cmd") + time.sleep(1) + #手动按钮置回False + self._sys_hand_cmd(False) + time.sleep(1) + while (self._sys_hand_cmd()) == True: + print("waiting for hand_cmd to False") + time.sleep(1) + #初始化命令置回False + self._sys_init_cmd(False) + time.sleep(1) + while (self._sys_init_cmd()) == True: + print("waiting for init_cmd to False") + time.sleep(1) + + def func_pack_device_auto(self): + #切换自动 + print("切换自动模式") + self._sys_auto_cmd(True) + time.sleep(1) + while (self._sys_auto_status()) == False: + print("waiting for auto_status") + time.sleep(1) + #自动按钮置False + self._sys_auto_cmd(False) + time.sleep(1) + while (self._sys_auto_cmd()) == True: + print("waiting for auto_cmd") + time.sleep(1) + + def func_pack_device_start(self): + #切换自动 + print("启动") + self._sys_start_cmd(True) + time.sleep(1) + while (self._sys_start_status()) == False: + print("waiting for start_status") + time.sleep(1) + #自动按钮置False + self._sys_start_cmd(False) + time.sleep(1) + while (self._sys_start_cmd()) == True: + print("waiting for start_cmd") + time.sleep(1) + + def func_pack_send_bottle_num(self, bottle_num): + bottle_num = int(bottle_num) + #发送电解液平台数 + print("启动") + while (self._unilab_rece_electrolyte_bottle_num()) == False: + print("waiting for rece_electrolyte_bottle_num to True") + # self.client.use_node('8520').write(True) + time.sleep(1) + #发送电解液瓶数为2 + self._reg_msg_electrolyte_num(bottle_num) + time.sleep(1) + #完成信号置True + self._unilab_send_electrolyte_bottle_num(True) + time.sleep(1) + #检测到依华已接收 + while (self._unilab_rece_electrolyte_bottle_num()) == True: + print("waiting for rece_electrolyte_bottle_num to False") + time.sleep(1) + #完成信号置False + self._unilab_send_electrolyte_bottle_num(False) + time.sleep(1) + #自动按钮置False + + + # 下发参数 + #def func_pack_send_msg_cmd(self, elec_num: int, elec_use_num: int, elec_vol: float, assembly_type: int, assembly_pressure: int) -> bool: + # """UNILAB写参数""" + # while (self.request_rec_msg_status) == False: + # print("wait for res_msg") + # time.sleep(1) + # self.success = False + # self._unilab_send_msg_electrolyte_num(elec_num) + # time.sleep(1) + # self._unilab_send_msg_electrolyte_use_num(elec_use_num) + # time.sleep(1) + # self._unilab_send_msg_electrolyte_vol(elec_vol) + # time.sleep(1) + # self._unilab_send_msg_assembly_type(assembly_type) + # time.sleep(1) + # self._unilab_send_msg_assembly_pressure(assembly_pressure) + # time.sleep(1) + # self._unilab_send_msg_succ_cmd(True) + # time.sleep(1) + # self._unilab_send_msg_succ_cmd(False) + # #将允许读取标志位置True + # self.allow_data_read = True + # self.success = True + # return self.success + + def func_pack_send_msg_cmd(self, elec_use_num) -> bool: + """UNILAB写参数""" + while (self.request_rec_msg_status) == False: + print("wait for request_rec_msg_status to True") + time.sleep(1) + self.success = False + #self._unilab_send_msg_electrolyte_num(elec_num) + time.sleep(1) + self._unilab_send_msg_electrolyte_use_num(elec_use_num) + time.sleep(1) + self._unilab_send_msg_succ_cmd(True) + time.sleep(1) + while (self.request_rec_msg_status) == True: + print("wait for request_rec_msg_status to False") + time.sleep(1) + self._unilab_send_msg_succ_cmd(False) + #将允许读取标志位置True + self.allow_data_read = True + self.success = True + return self.success + + def func_pack_get_msg_cmd(self, file_path: str="D:\\coin_cell_data") -> bool: + """UNILAB读参数""" + while self.request_send_msg_status == False: + print("waiting for send_read_msg_status to True") + time.sleep(1) + data_open_circuit_voltage = self.data_open_circuit_voltage + data_pole_weight = self.data_pole_weight + data_assembly_time = self.data_assembly_time + data_assembly_pressure = self.data_assembly_pressure + data_electrolyte_volume = self.data_electrolyte_volume + data_coin_num = self.data_coin_num + data_electrolyte_code = self.data_electrolyte_code + data_coin_cell_code = self.data_coin_cell_code + print("data_open_circuit_voltage", data_open_circuit_voltage) + print("data_pole_weight", data_pole_weight) + print("data_assembly_time", data_assembly_time) + print("data_assembly_pressure", data_assembly_pressure) + print("data_electrolyte_volume", data_electrolyte_volume) + print("data_coin_num", data_coin_num) + print("data_electrolyte_code", data_electrolyte_code) + print("data_coin_cell_code", data_coin_cell_code) + #接收完信息后,读取完毕标志位置True + liaopan3 = self.station_resource.get_resource("\u7535\u6c60\u6599\u76d8") + #把物料解绑后放到另一盘上 + battery = ElectrodeSheet(name=f"battery_{self.coin_num_N}", size_x=14, size_y=14, size_z=2) + battery._unilabos_state = { + "electrolyte_name": data_coin_cell_code, + "data_electrolyte_code": data_electrolyte_code, + "open_circuit_voltage": data_open_circuit_voltage, + "assembly_pressure": data_assembly_pressure, + "electrolyte_volume": data_electrolyte_volume + } + liaopan3.children[self.coin_num_N].assign_child_resource(battery, location=None) + #print(jipian2.parent) + ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ + "resources": [self.station_resource] + }) + + + self._unilab_rec_msg_succ_cmd(True) + time.sleep(1) + #等待允许读取标志位置False + while self.request_send_msg_status == True: + print("waiting for send_msg_status to False") + time.sleep(1) + self._unilab_rec_msg_succ_cmd(False) + time.sleep(1) + #将允许读取标志位置True + time_date = datetime.now().strftime("%Y%m%d") + #秒级时间戳用于标记每一行电池数据 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + #生成输出文件的变量 + self.csv_export_file = os.path.join(file_path, f"date_{time_date}.csv") + #将数据存入csv文件 + if not os.path.exists(self.csv_export_file): + #创建一个表头 + with open(self.csv_export_file, 'w', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow([ + 'Time', 'open_circuit_voltage', 'pole_weight', + 'assembly_time', 'assembly_pressure', 'electrolyte_volume', + 'coin_num', 'electrolyte_code', 'coin_cell_code' + ]) + #立刻写入磁盘 + csvfile.flush() + #开始追加电池信息 + with open(self.csv_export_file, 'a', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow([ + timestamp, data_open_circuit_voltage, data_pole_weight, + data_assembly_time, data_assembly_pressure, data_electrolyte_volume, + data_coin_num, data_electrolyte_code, data_coin_cell_code + ]) + #立刻写入磁盘 + csvfile.flush() + self.success = True + return self.success + + + + def func_pack_send_finished_cmd(self) -> bool: + """UNILAB写参数""" + while (self._unilab_rece_finished_cmd()) == False: + print("wait for rece_finished_cmd to True") + time.sleep(1) + self.success = False + self._unilab_send_finished_cmd(True) + time.sleep(1) + while (self._unilab_rece_finished_cmd()) == True: + print("wait for rece_finished_cmd to False") + time.sleep(1) + self._unilab_send_finished_cmd(False) + #将允许读取标志位置True + self.success = True + return self.success + + + + def func_allpack_cmd(self, elec_num, elec_use_num, file_path: str="D:\\coin_cell_data") -> bool: + elec_num, elec_use_num = int(elec_num), int(elec_use_num) + summary_csv_file = os.path.join(file_path, "duandian.csv") + # 如果断点文件存在,先读取之前的进度 + if os.path.exists(summary_csv_file): + read_status_flag = True + with open(summary_csv_file, 'r', newline='', encoding='utf-8') as csvfile: + reader = csv.reader(csvfile) + header = next(reader) # 跳过标题行 + data_row = next(reader) # 读取数据行 + if len(data_row) >= 2: + elec_num_r = int(data_row[0]) + elec_use_num_r = int(data_row[1]) + elec_num_N = int(data_row[2]) + elec_use_num_N = int(data_row[3]) + coin_num_N = int(data_row[4]) + if elec_num_r == elec_num and elec_use_num_r == elec_use_num: + print("断点文件与当前任务匹配,继续") + else: + print("断点文件中elec_num、elec_use_num与当前任务不匹配,请检查任务下发参数或修改断点文件") + return False + print(f"从断点文件读取进度: elec_num_N={elec_num_N}, elec_use_num_N={elec_use_num_N}, coin_num_N={coin_num_N}") + + else: + read_status_flag = False + print("未找到断点文件,从头开始") + elec_num_N = 0 + elec_use_num_N = 0 + coin_num_N = 0 + for i in range(20): + print(f"剩余电解液瓶数: {elec_num}, 已组装电池数: {elec_use_num}") + print(f"剩余电解液瓶数: {type(elec_num)}, 已组装电池数: {type(elec_use_num)}") + print(f"剩余电解液瓶数: {type(int(elec_num))}, 已组装电池数: {type(int(elec_use_num))}") + + #如果是第一次运行,则进行初始化、切换自动、启动, 如果是断点重启则跳过。 + if read_status_flag == False: + pass + #初始化 + #self.func_pack_device_init() + #切换自动 + #self.func_pack_device_auto() + #启动,小车收回 + #self.func_pack_device_start() + #发送电解液瓶数量,启动搬运,多搬运没事 + #self.func_pack_send_bottle_num(elec_num) + last_i = elec_num_N + last_j = elec_use_num_N + for i in range(last_i, elec_num): + print(f"开始第{last_i+i+1}瓶电解液的组装") + #第一个循环从上次断点继续,后续循环从0开始 + j_start = last_j if i == last_i else 0 + self.func_pack_send_msg_cmd(elec_use_num-j_start) + + for j in range(j_start, elec_use_num): + print(f"开始第{last_i+i+1}瓶电解液的第{j+j_start+1}个电池组装") + #读取电池组装数据并存入csv + self.func_pack_get_msg_cmd(file_path) + time.sleep(1) + # TODO:读完再将电池数加一还是进入循环就将电池数加一需要考虑 + + + + # 生成断点文件 + # 生成包含elec_num_N、coin_num_N、timestamp的CSV文件 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + with open(summary_csv_file, 'w', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow(['elec_num','elec_use_num', 'elec_num_N', 'elec_use_num_N', 'coin_num_N', 'timestamp']) + writer.writerow([elec_num, elec_use_num, elec_num_N, elec_use_num_N, coin_num_N, timestamp]) + csvfile.flush() + coin_num_N += 1 + self.coin_num_N = coin_num_N + elec_use_num_N += 1 + elec_num_N += 1 + elec_use_num_N = 0 + + #循环正常结束,则删除断点文件 + os.remove(summary_csv_file) + #全部完成后等待依华发送完成信号 + self.func_pack_send_finished_cmd() + + + def func_pack_device_stop(self) -> bool: + """打包指令:设备停止""" + for i in range(3): + time.sleep(2) + print(f"输出{i}") + #print("_sys_hand_cmd", self._sys_hand_cmd()) + #time.sleep(1) + #print("_sys_hand_status", self._sys_hand_status()) + #time.sleep(1) + #print("_sys_init_cmd", self._sys_init_cmd()) + #time.sleep(1) + #print("_sys_init_status", self._sys_init_status()) + #time.sleep(1) + #print("_sys_auto_status", self._sys_auto_status()) + #time.sleep(1) + #print("data_axis_y_pos", self.data_axis_y_pos) + #time.sleep(1) + #self.success = False + #with open('action_device_stop.json', 'r', encoding='utf-8') as f: + # action_json = json.load(f) + #self.client.execute_procedure_from_json(action_json) + #self.success = True + #return self.success + + def fun_wuliao_test(self) -> bool: + #找到data_init中构建的2个物料盘 + liaopan3 = self.station_resource.get_resource("\u7535\u6c60\u6599\u76d8") + for i in range(16): + battery = ElectrodeSheet(name=f"battery_{i}", size_x=16, size_y=16, size_z=2) + battery._unilabos_state = { + "diameter": 20.0, + "height": 20.0, + "assembly_pressure": i, + "electrolyte_volume": 20.0, + "electrolyte_name": f"DP{i}" + } + liaopan3.children[i].assign_child_resource(battery, location=None) + + ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ + "resources": [self.station_resource] + }) + time.sleep(4) + # 数据读取与输出 + def func_read_data_and_output(self, file_path: str="D:\\coin_cell_data"): + # 检查CSV导出是否正在运行,已运行则跳出,防止同时启动两个while循环 + if self.csv_export_running: + return False, "读取已在运行中" + + #若不存在该目录则创建 + if not os.path.exists(file_path): + os.makedirs(file_path) + print(f"创建目录: {file_path}") + + # 只要允许读取标志位为true,就持续运行该函数,直到触发停止条件 + while self.allow_data_read: + + #函数运行标志位,确保只同时启动一个导出函数 + self.csv_export_running = True + + #等待接收结果标志位置True + while self.request_send_msg_status == False: + print("waiting for send_msg_status to True") + time.sleep(1) + #日期时间戳用于按天存放csv文件 + time_date = datetime.now().strftime("%Y%m%d") + #秒级时间戳用于标记每一行电池数据 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + #生成输出文件的变量 + self.csv_export_file = os.path.join(file_path, f"date_{time_date}.csv") + + #接收信息 + data_open_circuit_voltage = self.data_open_circuit_voltage + data_pole_weight = self.data_pole_weight + data_assembly_time = self.data_assembly_time + data_assembly_pressure = self.data_assembly_pressure + data_electrolyte_volume = self.data_electrolyte_volume + data_coin_num = self.data_coin_num + data_electrolyte_code = self.data_electrolyte_code + data_coin_cell_code = self.data_coin_cell_code + # 电解液瓶位置 + elec_bottle_site = 2 + # 极片夹取位置(应当通过寄存器读光标) + Pos_elec_site = 0 + Al_elec_site = 0 + Gasket_site = 0 + + #接收完信息后,读取完毕标志位置True + self._unilab_rec_msg_succ_cmd()# = True + #等待允许读取标志位置False + while self.request_send_msg_status == True: + print("waiting for send_msg_status to False") + time.sleep(1) + self._unilab_rec_msg_succ_cmd()# = False + + #此处操作物料信息(如果中途报错停止,如何) + #报错怎么办(加个判断标志位,如果发生错误,则根据停止位置扣除物料) + #根据物料光标判断取哪个物料(人工摆盘,电解液瓶,移液枪头都有光标位置,寄存器读即可) + + #物料读取操作写在这里 + #在这里进行物料调取 + #转移物料瓶,elec_bottle_site对应第几瓶电解液(从依华寄存器读取) + # transfer_bottle(deck, elec_bottle_site) + # #找到电解液瓶的对象 + # electrolyte_rack = deck.get_resource("electrolyte_rack") + # pending_positions = electrolyte_rack.get_pending_positions()[elec_bottle_site] + # # TODO: 瓶子取液体操作需要加入 +# +# + # #找到压制工站对应的对象 + # battery_press_slot = deck.get_resource("battery_press_1") + # #创建一个新电池 + # test_battery = Battery( + # name=f"test_battery_{data_coin_num}", + # diameter=20.0, # 与压制槽直径匹配 + # height=3.0, # 电池高度 + # max_volume=100.0, # 100μL容量 + # barcode=data_coin_cell_code, # 电池条码 + # ) + # if battery_press_slot.has_battery(): + # return False, "压制工站已有电池,无法放置新电池" + # #在压制位放置电池 + # battery_press_slot.place_battery(test_battery) + # #从第一个子弹夹中取料 + # clip_magazine_1_hole = self.deck.get_resource("clip_magazine_1").get_item(Pos_elec_site) + # clip_magazine_2_hole = self.deck.get_resource("clip_magazine_2").get_item(Al_elec_site) + # clip_magazine_3_hole = self.deck.get_resource("clip_magazine_3").get_item(Gasket_site) + # + # if clip_magazine_1_hole.get_sheet_count() > 0: # 检查洞位是否有极片 + # electrode_sheet_1 = clip_magazine_1_hole.take_sheet() # 从洞位取出极片 + # test_battery.add_electrode_sheet(electrode_sheet_1) # 添加到电池中 + # print(f"已将极片 {electrode_sheet_1.name} 从子弹夹转移到电池") + # else: + # print("子弹夹洞位0没有极片") +# + # if clip_magazine_2_hole.get_sheet_count() > 0: # 检查洞位是否有极片 + # electrode_sheet_2 = clip_magazine_2_hole.take_sheet() # 从洞位取出极片 + # test_battery.add_electrode_sheet(electrode_sheet_2) # 添加到电池中 + # print(f"已将极片 {electrode_sheet_2.name} 从子弹夹转移到电池") + # else: + # print("子弹夹洞位0没有极片") +# + # if clip_magazine_3_hole.get_sheet_count() > 0: # 检查洞位是否有极片 + # electrode_sheet_3 = clip_magazine_3_hole.take_sheet() # 从洞位取出极片 + # test_battery.add_electrode_sheet(electrode_sheet_3) # 添加到电池中 + # print(f"已将极片 {electrode_sheet_3.name} 从子弹夹转移到电池") + # else: + # print("子弹夹洞位0没有极片") + # + # # TODO:#把电解液从瓶中取到电池夹子中 + # battery_site = deck.get_resource("battery_press_1") + # clip_magazine_battery = deck.get_resource("clip_magazine_battery") + # if battery_site.has_battery(): + # battery = battery_site.take_battery() #从压制槽取出电池 + # clip_magazine_battery.add_battery(battery) #从压制槽取出电池 +# +# +# +# + # # 保存配置到文件 + # self.deck.save("button_battery_station_layout.json", indent=2) + # print("\n台面配置已保存到: button_battery_station_layout.json") + # + # # 保存状态到文件 + # self.deck.save_state_to_file("button_battery_station_state.json", indent=2) + # print("台面状态已保存到: button_battery_station_state.json") + + + + + + + #将数据写入csv中 + #如当前目录下无同名文件则新建一个csv用于存放数据 + if not os.path.exists(self.csv_export_file): + #创建一个表头 + with open(self.csv_export_file, 'w', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow([ + 'Time', 'open_circuit_voltage', 'pole_weight', + 'assembly_time', 'assembly_pressure', 'electrolyte_volume', + 'coin_num', 'electrolyte_code', 'coin_cell_code' + ]) + #立刻写入磁盘 + csvfile.flush() + #开始追加电池信息 + with open(self.csv_export_file, 'a', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow([ + timestamp, data_open_circuit_voltage, data_pole_weight, + data_assembly_time, data_assembly_pressure, data_electrolyte_volume, + data_coin_num, data_electrolyte_code, data_coin_cell_code + ]) + #立刻写入磁盘 + csvfile.flush() + + # 只要不在自动模式运行中,就将允许标志位置False + if self.sys_auto_status == False or self.sys_start_status == False: + self.allow_data_read = False + self.csv_export_running = False + time.sleep(1) + + def func_stop_read_data(self): + """停止CSV导出""" + if not self.csv_export_running: + return False, "read data未在运行" + + self.csv_export_running = False + self.allow_data_read = False + + if self.csv_export_thread and self.csv_export_thread.is_alive(): + self.csv_export_thread.join(timeout=5) + + def func_get_csv_export_status(self): + """获取CSV导出状态""" + return { + 'allow_read': self.allow_data_read, + 'running': self.csv_export_running, + 'thread_alive': self.csv_export_thread.is_alive() if self.csv_export_thread else False + } + + + ''' + # ===================== 物料管理区 ====================== + @property + def data_material_inventory(self) -> int: + """主物料库存 (数量, INT16)""" + inventory, read_err = self.client.use_node('REG_DATA_MATERIAL_INVENTORY').read(1) + return inventory + + @property + def data_tips_inventory(self) -> int: + """移液枪头库存 (数量, INT16)""" + inventory, read_err = self.client.register_node_list(self.nodes).use_node('REG_DATA_TIPS_INVENTORY').read(1) + return inventory + + ''' + + +if __name__ == "__main__": + from pylabrobot.resources import Resource + Coin_Cell = CoinCellAssemblyWorkstation(Resource("1", 1, 1, 1), debug_mode=True) + #Coin_Cell.func_pack_device_init() + #Coin_Cell.func_pack_device_auto() + #Coin_Cell.func_pack_device_start() + #Coin_Cell.func_pack_send_bottle_num(2) + #Coin_Cell.func_pack_send_msg_cmd(2) + #Coin_Cell.func_pack_get_msg_cmd() + #Coin_Cell.func_pack_get_msg_cmd() + #Coin_Cell.func_pack_send_finished_cmd() +# + #Coin_Cell.func_allpack_cmd(3, 2) + #print(Coin_Cell.data_stack_vision_code) + #print("success") + #创建一个物料台面 + + deck = create_a_coin_cell_deck() + #deck = create_a_full_coin_cell_deck() + + + ##在台面上找到料盘和极片 + #liaopan1 = deck.get_resource("liaopan1") + #liaopan2 = deck.get_resource("liaopan2") + #jipian1 = liaopan1.children[1].children[0] +## + #print(jipian1) + ##把物料解绑后放到另一盘上 + #jipian1.parent.unassign_child_resource(jipian1) + #liaopan2.children[1].assign_child_resource(jipian1, location=None) + ##print(jipian2.parent) + + liaopan1 = deck.get_resource("liaopan1") + liaopan2 = deck.get_resource("liaopan2") + for i in range(16): + #找到liaopan1上每一个jipian + jipian_linshi = liaopan1.children[i].children[0] + #把物料解绑后放到另一盘上 + print("极片:", jipian_linshi) + jipian_linshi.parent.unassign_child_resource(jipian_linshi) + liaopan2.children[i].assign_child_resource(jipian_linshi, location=None) + + + from unilabos.resources.graphio import resource_ulab_to_plr, convert_resources_to_type + #with open("./button_battery_station_resources_unilab.json", "r", encoding="utf-8") as f: + # bioyond_resources_unilab = json.load(f) + #print(f"成功读取 JSON 文件,包含 {len(bioyond_resources_unilab)} 个资源") + #ulab_resources = convert_resources_to_type(bioyond_resources_unilab, List[PLRResource]) + #print(f"转换结果类型: {type(ulab_resources)}") + #print(ulab_resources) + + + + from unilabos.resources.graphio import convert_resources_from_type + from unilabos.config.config import BasicConfig + BasicConfig.ak = "beb0c15f-2279-46a1-aba5-00eaf89aef55" + BasicConfig.sk = "15d4f25e-3512-4f9c-9bfb-43ab85e7b561" + from unilabos.app.web.client import http_client + + resources = convert_resources_from_type([deck], [Resource]) + json.dump({"nodes": resources, "links": []}, open("button_battery_station_resources_unilab.json", "w"), indent=2) + + #print(resources) + http_client.remote_addr = "https://uni-lab.test.bohrium.com/api/v1" + + http_client.resource_add(resources) \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_0910.csv b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_0910.csv new file mode 100644 index 0000000..e840715 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_0910.csv @@ -0,0 +1,44 @@ +Name,DataType,InitValue,Comment,Attribute,DeviceType,Address +COIL_SYS_START_CMD,BOOL,,豸,,coil,8010 +COIL_SYS_STOP_CMD,BOOL,,豸ֹͣ,,coil,8020 +COIL_SYS_RESET_CMD,BOOL,,豸λ,,coil,8030 +COIL_SYS_HAND_CMD,BOOL,,豸ֶģʽ,,coil,8040 +COIL_SYS_AUTO_CMD,BOOL,,豸Զģʽ,,coil,8050 +COIL_SYS_INIT_CMD,BOOL,,豸ʼģʽ,,coil,8060 +COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,,UNILAB䷽,,coil,8700 +COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,,UNILABܲ,,coil,8710 +COIL_SYS_START_STATUS,BOOL,,豸,,coil,8210 +COIL_SYS_STOP_STATUS,BOOL,,豸ֹͣ,,coil,8220 +COIL_SYS_RESET_STATUS,BOOL,,豸λ,,coil,8230 +COIL_SYS_HAND_STATUS,BOOL,,豸ֶģʽ,,coil,8240 +COIL_SYS_AUTO_STATUS,BOOL,,豸Զģʽ,,coil,8250 +COIL_SYS_INIT_STATUS,BOOL,,豸ʼ,,coil,8260 +COIL_REQUEST_REC_MSG_STATUS,BOOL,,豸䷽,,coil,8510 +COIL_REQUEST_SEND_MSG_STATUS,BOOL,,豸Ͳ,,coil,8500 +REG_MSG_ELECTROLYTE_USE_NUM,INT16,,ƿҺʹô,,hold_register,11000 +REG_MSG_ELECTROLYTE_NUM,INT16,,Һʹƿ,,hold_register,11002 +REG_MSG_ELECTROLYTE_VOLUME,INT16,,Һȡ,,hold_register,11004 +REG_MSG_ASSEMBLY_TYPE,INT16,,װƬѵʽ,,hold_register,11006 +REG_MSG_ASSEMBLY_PRESSURE,INT16,,װѹ,,hold_register,11008 +REG_DATA_ASSEMBLY_COIN_CELL_NUM,INT16,,ǰװ,,hold_register,10000 +REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,,ǰصѹ,,hold_register,10002 +REG_DATA_AXIS_X_POS,FLOAT32,,ҺXᵱǰλ,,hold_register,10004 +REG_DATA_AXIS_Y_POS,FLOAT32,,ҺZᵱǰλ,,hold_register,10006 +REG_DATA_AXIS_Z_POS,FLOAT32,,ҺYᵱǰλ,,hold_register,10008 +REG_DATA_POLE_WEIGHT,FLOAT32,,ǰƬ,,hold_register,10010 +REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,,ǰŵװʱ,,hold_register,10012 +REG_DATA_ASSEMBLY_PRESSURE,INT16,,ǰװѹ,,hold_register,10014 +REG_DATA_ELECTROLYTE_VOLUME,INT16,,ǰҺע,,hold_register,10016 +REG_DATA_COIN_NUM,INT16,,ǰ,,hold_register,10018 +REG_DATA_ELECTROLYTE_CODE,STRING,,Һάк,,hold_register,10020 +REG_DATA_COIN_CELL_CODE,STRING,,ضάк,,hold_register,10030 +REG_DATA_STACK_VISON_CODE,STRING,,϶ѵͼƬ,,hold_register,12004 +REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,,ѹ,,hold_register,10050 +REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,,ˮ,,hold_register,10052 +REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,,,,hold_register,10054 +UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM,BOOL,,Unilabȷѷ͵Һƿź,,coil,8720 +UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM,BOOL,,UnilabɽܵҺƿ,,coil,8520 +REG_MSG_ELECTROLYTE_NUM_USED,INT16,,Һװƽ,,hold_register,496 +REG_DATA_ELECTROLYTE_USE_NUM,INT16,,ǰװƽ,,hold_register,10000 +UNILAB_SEND_FINISHED_CMD,BOOL,,Unilabյź,,coil,8730 +UNILAB_RECE_FINISHED_CMD,BOOL,,֪unilabź,,coil,8530 diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv new file mode 100644 index 0000000..149a683 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv @@ -0,0 +1,46 @@ +Name,DataType,InitValue,Comment,Attribute,DeviceType,Address, +COIL_SYS_START_CMD,BOOL,,,,coil,8010, +COIL_SYS_STOP_CMD,BOOL,,,,coil,8020, +COIL_SYS_RESET_CMD,BOOL,,,,coil,8030, +COIL_SYS_HAND_CMD,BOOL,,,,coil,8040, +COIL_SYS_AUTO_CMD,BOOL,,,,coil,8050, +COIL_SYS_INIT_CMD,BOOL,,,,coil,8060, +COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,,,,coil,8700, +COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,,,,coil,8710,unilab_rec_msg_succ_cmd +COIL_SYS_START_STATUS,BOOL,,,,coil,8210, +COIL_SYS_STOP_STATUS,BOOL,,,,coil,8220, +COIL_SYS_RESET_STATUS,BOOL,,,,coil,8230, +COIL_SYS_HAND_STATUS,BOOL,,,,coil,8240, +COIL_SYS_AUTO_STATUS,BOOL,,,,coil,8250, +COIL_SYS_INIT_STATUS,BOOL,,,,coil,8260, +COIL_REQUEST_REC_MSG_STATUS,BOOL,,,,coil,8500, +COIL_REQUEST_SEND_MSG_STATUS,BOOL,,,,coil,8510,request_send_msg_status +REG_MSG_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,11000, +REG_MSG_ELECTROLYTE_NUM,INT16,,,,hold_register,11002,unilab_send_msg_electrolyte_num +REG_MSG_ELECTROLYTE_VOLUME,INT16,,,,hold_register,11004,unilab_send_msg_electrolyte_vol +REG_MSG_ASSEMBLY_TYPE,INT16,,,,hold_register,11006,unilab_send_msg_assembly_type +REG_MSG_ASSEMBLY_PRESSURE,INT16,,,,hold_register,11008,unilab_send_msg_assembly_pressure +REG_DATA_ASSEMBLY_COIN_CELL_NUM,INT16,,,,hold_register,10000,data_assembly_coin_cell_num +REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,,,,hold_register,10002,data_open_circuit_voltage +REG_DATA_AXIS_X_POS,FLOAT32,,,,hold_register,10004, +REG_DATA_AXIS_Y_POS,FLOAT32,,,,hold_register,10006, +REG_DATA_AXIS_Z_POS,FLOAT32,,,,hold_register,10008, +REG_DATA_POLE_WEIGHT,FLOAT32,,,,hold_register,10010,data_pole_weight +REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,,,,hold_register,10012,data_assembly_time +REG_DATA_ASSEMBLY_PRESSURE,INT16,,,,hold_register,10014,data_assembly_pressure +REG_DATA_ELECTROLYTE_VOLUME,INT16,,,,hold_register,10016,data_electrolyte_volume +REG_DATA_COIN_NUM,INT16,,,,hold_register,10018,data_coin_num +REG_DATA_ELECTROLYTE_CODE,STRING,,,,hold_register,10020,data_electrolyte_code() +REG_DATA_COIN_CELL_CODE,STRING,,,,hold_register,10030,data_coin_cell_code() +REG_DATA_STACK_VISON_CODE,STRING,,,,hold_register,12004,data_stack_vision_code() +REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,,,,hold_register,10050,data_glove_box_pressure +REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,,,,hold_register,10052,data_glove_box_water_content +REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,,,,hold_register,10054,data_glove_box_o2_content +UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8720, +UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8520, +REG_MSG_ELECTROLYTE_NUM_USED,INT16,,,,hold_register,496, +REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,10000, +UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,8730, +UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,8530, +REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,10018,ASSEMBLY_TYPE7or8 +COIL_ALUMINUM_FOIL,BOOL,,,,coil,8340, diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_old.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_old.py new file mode 100644 index 0000000..ac61948 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_old.py @@ -0,0 +1,1149 @@ +import csv +import json +import os +import threading +import time +from datetime import datetime +from typing import Any, Dict, Optional +from pylabrobot.resources import Resource as PLRResource +from unilabos_msgs.msg import Resource +from unilabos.device_comms.modbus_plc.client import ModbusTcpClient +from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import MaterialHole, MaterialPlate +from unilabos.devices.workstation.workstation_base import WorkstationBase +from unilabos.device_comms.modbus_plc.client import TCPClient, ModbusNode, PLCWorkflow, ModbusWorkflow, WorkflowAction, BaseClient +from unilabos.device_comms.modbus_plc.modbus import DeviceType, Base as ModbusNodeBase, DataType, WorderOrder +from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import * +from unilabos.ros.nodes.base_device_node import ROS2DeviceNode, BaseROS2DeviceNode +from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode + +#构建物料系统 + +class CoinCellAssemblyWorkstation(WorkstationBase): + def __init__( + self, + station_resource: CoincellDeck, + address: str = "192.168.1.20", + port: str = "502", + debug_mode: bool = True, + *args, + **kwargs, + ): + super().__init__( + #桌子 + station_resource=station_resource, + *args, + **kwargs, + ) + self.debug_mode = debug_mode + self.station_resource = station_resource + """ 连接初始化 """ + modbus_client = TCPClient(addr=address, port=port) + print("modbus_client", modbus_client) + if not debug_mode: + modbus_client.client.connect() + count = 100 + while count >0: + count -=1 + if modbus_client.client.is_socket_open(): + break + time.sleep(2) + if not modbus_client.client.is_socket_open(): + raise ValueError('modbus tcp connection failed') + else: + print("测试模式,跳过连接") + + """ 工站的配置 """ + self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_a.csv')) + self.client = modbus_client.register_node_list(self.nodes) + self.success = False + self.allow_data_read = False #允许读取函数运行标志位 + self.csv_export_thread = None + self.csv_export_running = False + self.csv_export_file = None + #创建一个物料台面,包含两个极片板 + #self.deck = create_a_coin_cell_deck() + + #self._ros_node.update_resource(self.deck) + + #ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ + # "resources": [self.deck] + #}) + + + def post_init(self, ros_node: ROS2WorkstationNode): + self._ros_node = ros_node + #self.deck = create_a_coin_cell_deck() + ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ + "resources": [self.station_resource] + }) + + # 批量操作在这里写 + async def change_hole_sheet_to_2(self, hole: MaterialHole): + hole._unilabos_state["max_sheets"] = 2 + return await self._ros_node.update_resource(hole) + + + async def fill_plate(self): + plate_1: MaterialPlate = self.station_resource.children[0].children[0] + #plate_1 + return await self._ros_node.update_resource(plate_1) + + #def run_assembly(self, wf_name: str, resource: PLRResource, params: str = "\{\}"): + # """启动工作流""" + # self.current_workflow_status = WorkflowStatus.RUNNING + # logger.info(f"工作站 {self.device_id} 启动工作流: {wf_name}") +# + # # TODO: 实现工作流逻辑 +# + # anode_sheet = self.deck.get_resource("anode_sheet") + + """ Action逻辑代码 """ + def _sys_start_cmd(self, cmd=None): + """设备启动命令 (可读写)""" + if cmd is not None: # 写入模式 + self.success = False + node = self.client.use_node('COIL_SYS_START_CMD') + ret = node.write(cmd) + print(ret) + self.success = True + return self.success + else: # 读取模式 + cmd_feedback, read_err = self.client.use_node('COIL_SYS_START_CMD').read(1) + return cmd_feedback[0] + + def _sys_stop_cmd(self, cmd=None): + """设备停止命令 (可读写)""" + if cmd is not None: # 写入模式 + self.success = False + node = self.client.use_node('COIL_SYS_STOP_CMD') + node.write(cmd) + self.success = True + return self.success + else: # 读取模式 + cmd_feedback, read_err = self.client.use_node('COIL_SYS_STOP_CMD').read(1) + return cmd_feedback[0] + + def _sys_reset_cmd(self, cmd=None): + """设备复位命令 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_SYS_RESET_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_SYS_RESET_CMD').read(1) + return cmd_feedback[0] + + def _sys_hand_cmd(self, cmd=None): + """手动模式命令 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_SYS_HAND_CMD').write(cmd) + self.success = True + print("步骤0") + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_SYS_HAND_CMD').read(1) + return cmd_feedback[0] + + def _sys_auto_cmd(self, cmd=None): + """自动模式命令 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_SYS_AUTO_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_SYS_AUTO_CMD').read(1) + return cmd_feedback[0] + + def _sys_init_cmd(self, cmd=None): + """初始化命令 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_SYS_INIT_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_SYS_INIT_CMD').read(1) + return cmd_feedback[0] + + def _unilab_send_msg_succ_cmd(self, cmd=None): + """UNILAB发送配方完毕 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_UNILAB_SEND_MSG_SUCC_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_UNILAB_SEND_MSG_SUCC_CMD').read(1) + return cmd_feedback[0] + + def _unilab_rec_msg_succ_cmd(self, cmd=None): + """UNILAB接收测试电池数据完毕 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_UNILAB_REC_MSG_SUCC_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_UNILAB_REC_MSG_SUCC_CMD').read(1) + return cmd_feedback + + + # ====================== 命令类指令(REG_x_) ====================== + def _unilab_send_msg_electrolyte_num(self, num=None): + """UNILAB写电解液使用瓶数(可读写)""" + if num is not None: + self.success = False + ret = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').write(num) + print(ret) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').read(1) + return cmd_feedback[0] + + def _unilab_send_msg_electrolyte_use_num(self, use_num=None): + """UNILAB写单次电解液使用瓶数(可读写)""" + if use_num is not None: + self.success = False + self.client.use_node('REG_MSG_ELECTROLYTE_USE_NUM').write(use_num) + self.success = True + return self.success + else: + return False + + def _unilab_send_msg_assembly_type(self, num=None): + """UNILAB写组装参数""" + if num is not None: + self.success = False + self.client.use_node('REG_MSG_ASSEMBLY_TYPE').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ASSEMBLY_TYPE').read(1) + return cmd_feedback[0] + + def _unilab_send_msg_electrolyte_vol(self, vol=None): + """UNILAB写电解液吸取量参数""" + if vol is not None: + self.success = False + self.client.use_node('REG_MSG_ELECTROLYTE_VOLUME').write(vol, data_type=DataType.FLOAT32, word_order=WorderOrder.LITTLE) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_VOLUME').read(2, word_order=WorderOrder.LITTLE) + return cmd_feedback[0] + + def _unilab_send_msg_assembly_pressure(self, vol=None): + """UNILAB写电池压制力""" + if vol is not None: + self.success = False + self.client.use_node('REG_MSG_ASSEMBLY_PRESSURE').write(vol, data_type=DataType.FLOAT32, word_order=WorderOrder.LITTLE) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ASSEMBLY_PRESSURE').read(2, word_order=WorderOrder.LITTLE) + return cmd_feedback[0] + + # ==================== 0905新增内容(COIL_x_STATUS) ==================== + def _unilab_send_electrolyte_bottle_num(self, num=None): + """UNILAB发送电解液瓶数完毕""" + if num is not None: + self.success = False + self.client.use_node('UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM').read(1) + return cmd_feedback[0] + + def _unilab_rece_electrolyte_bottle_num(self, num=None): + """设备请求接受电解液瓶数""" + if num is not None: + self.success = False + self.client.use_node('UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM').read(1) + return cmd_feedback[0] + + def _reg_msg_electrolyte_num(self, num=None): + """电解液已使用瓶数""" + if num is not None: + self.success = False + self.client.use_node('REG_MSG_ELECTROLYTE_NUM').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').read(1) + return cmd_feedback[0] + + def _reg_data_electrolyte_use_num(self, num=None): + """单瓶电解液完成组装数""" + if num is not None: + self.success = False + self.client.use_node('REG_DATA_ELECTROLYTE_USE_NUM').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_USE_NUM').read(1) + return cmd_feedback[0] + + def _unilab_send_finished_cmd(self, num=None): + """Unilab发送已知一组组装完成信号""" + if num is not None: + self.success = False + self.client.use_node('UNILAB_SEND_FINISHED_CMD').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('UNILAB_SEND_FINISHED_CMD').read(1) + return cmd_feedback[0] + + def _unilab_rece_finished_cmd(self, num=None): + """Unilab接收已知一组组装完成信号""" + if num is not None: + self.success = False + self.client.use_node('UNILAB_RECE_FINISHED_CMD').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('UNILAB_RECE_FINISHED_CMD').read(1) + return cmd_feedback[0] + + + + # ==================== 状态类属性(COIL_x_STATUS) ==================== + def _sys_start_status(self) -> bool: + """设备启动中( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_START_STATUS').read(1) + return status[0] + + def _sys_stop_status(self) -> bool: + """设备停止中( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_STOP_STATUS').read(1) + return status[0] + + def _sys_reset_status(self) -> bool: + """设备复位中( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_RESET_STATUS').read(1) + return status[0] + + def _sys_init_status(self) -> bool: + """设备初始化完成( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_INIT_STATUS').read(1) + return status[0] + + # 查找资源 + def modify_deck_name(self, resource_name: str): + # figure_res = self._ros_node.resource_tracker.figure_resource({"name": resource_name}) + # print(f"!!! figure_res: {type(figure_res)}") + self.station_resource.children[1] + return + + @property + def sys_status(self) -> str: + if self.debug_mode: + return "设备调试模式" + if self._sys_start_status(): + return "设备启动中" + elif self._sys_stop_status(): + return "设备停止中" + elif self._sys_reset_status(): + return "设备复位中" + elif self._sys_init_status(): + return "设备初始化中" + else: + return "未知状态" + + def _sys_hand_status(self) -> bool: + """设备手动模式( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_HAND_STATUS').read(1) + return status[0] + + def _sys_auto_status(self) -> bool: + """设备自动模式( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_AUTO_STATUS').read(1) + return status[0] + + @property + def sys_mode(self) -> str: + if self.debug_mode: + return "设备调试模式" + if self._sys_hand_status(): + return "设备手动模式" + elif self._sys_auto_status(): + return "设备自动模式" + else: + return "未知模式" + + @property + def request_rec_msg_status(self) -> bool: + """设备请求接受配方( BOOL)""" + if self.debug_mode: + return True + status, read_err = self.client.use_node('COIL_REQUEST_REC_MSG_STATUS').read(1) + return status[0] + + @property + def request_send_msg_status(self) -> bool: + """设备请求发送测试数据( BOOL)""" + if self.debug_mode: + return True + status, read_err = self.client.use_node('COIL_REQUEST_SEND_MSG_STATUS').read(1) + return status[0] + + # ======================= 其他属性(特殊功能) ======================== + ''' + @property + def warning_1(self) -> bool: + status, read_err = self.client.use_node('COIL_WARNING_1').read(1) + return status[0] + ''' + # ===================== 生产数据区 ====================== + + @property + def data_assembly_coin_cell_num(self) -> int: + """已完成电池数量 (INT16)""" + if self.debug_mode: + return 0 + num, read_err = self.client.use_node('REG_DATA_ASSEMBLY_COIN_CELL_NUM').read(1) + return num + + @property + def data_assembly_time(self) -> float: + """单颗电池组装时间 (秒, REAL/FLOAT32)""" + if self.debug_mode: + return 0 + time, read_err = self.client.use_node('REG_DATA_ASSEMBLY_PER_TIME').read(2, word_order=WorderOrder.LITTLE) + return time + + @property + def data_open_circuit_voltage(self) -> float: + """开路电压值 (FLOAT32)""" + if self.debug_mode: + return 0 + vol, read_err = self.client.use_node('REG_DATA_OPEN_CIRCUIT_VOLTAGE').read(2, word_order=WorderOrder.LITTLE) + return vol + + @property + def data_axis_x_pos(self) -> float: + """分液X轴当前位置 (FLOAT32)""" + if self.debug_mode: + return 0 + pos, read_err = self.client.use_node('REG_DATA_AXIS_X_POS').read(2, word_order=WorderOrder.LITTLE) + return pos + + @property + def data_axis_y_pos(self) -> float: + """分液Y轴当前位置 (FLOAT32)""" + if self.debug_mode: + return 0 + pos, read_err = self.client.use_node('REG_DATA_AXIS_Y_POS').read(2, word_order=WorderOrder.LITTLE) + return pos + + @property + def data_axis_z_pos(self) -> float: + """分液Z轴当前位置 (FLOAT32)""" + if self.debug_mode: + return 0 + pos, read_err = self.client.use_node('REG_DATA_AXIS_Z_POS').read(2, word_order=WorderOrder.LITTLE) + return pos + + @property + def data_pole_weight(self) -> float: + """当前电池正极片称重数据 (FLOAT32)""" + if self.debug_mode: + return 0 + weight, read_err = self.client.use_node('REG_DATA_POLE_WEIGHT').read(2, word_order=WorderOrder.LITTLE) + return weight + + @property + def data_assembly_pressure(self) -> int: + """当前电池压制力 (INT16)""" + if self.debug_mode: + return 0 + pressure, read_err = self.client.use_node('REG_DATA_ASSEMBLY_PRESSURE').read(1) + return pressure + + @property + def data_electrolyte_volume(self) -> int: + """当前电解液加注量 (INT16)""" + if self.debug_mode: + return 0 + vol, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_VOLUME').read(1) + return vol + + @property + def data_coin_num(self) -> int: + """当前电池数量 (INT16)""" + if self.debug_mode: + return 0 + num, read_err = self.client.use_node('REG_DATA_COIN_NUM').read(1) + return num + + @property + def data_coin_cell_code(self) -> str: + """电池二维码序列号 (STRING)""" + try: + # 尝试不同的字节序读取 + code_little, read_err = self.client.use_node('REG_DATA_COIN_CELL_CODE').read(10, word_order=WorderOrder.LITTLE) + print(code_little) + clean_code = code_little[-8:][::-1] + return clean_code + except Exception as e: + print(f"读取电池二维码失败: {e}") + return "N/A" + + + @property + def data_electrolyte_code(self) -> str: + try: + # 尝试不同的字节序读取 + code_little, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_CODE').read(10, word_order=WorderOrder.LITTLE) + print(code_little) + clean_code = code_little[-8:][::-1] + return clean_code + except Exception as e: + print(f"读取电解液二维码失败: {e}") + return "N/A" + + # ===================== 环境监控区 ====================== + @property + def data_glove_box_pressure(self) -> float: + """手套箱压力 (bar, FLOAT32)""" + if self.debug_mode: + return 0 + status, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_PRESSURE').read(2, word_order=WorderOrder.LITTLE) + return status + + @property + def data_glove_box_o2_content(self) -> float: + """手套箱氧含量 (ppm, FLOAT32)""" + if self.debug_mode: + return 0 + value, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_O2_CONTENT').read(2, word_order=WorderOrder.LITTLE) + return value + + @property + def data_glove_box_water_content(self) -> float: + """手套箱水含量 (ppm, FLOAT32)""" + if self.debug_mode: + return 0 + value, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_WATER_CONTENT').read(2, word_order=WorderOrder.LITTLE) + return value + +# @property +# def data_stack_vision_code(self) -> int: +# """物料堆叠复检图片编码 (INT16)""" +# if self.debug_mode: +# return 0 +# code, read_err = self.client.use_node('REG_DATA_STACK_VISON_CODE').read(1) +# #code, _ = self.client.use_node('REG_DATA_STACK_VISON_CODE').read(1).type +# print(f"读取物料堆叠复检图片编码", {code}, "error", type(code)) +# #print(code.type) +# # print(read_err) +# return int(code) + + def func_pack_device_init(self): + #切换手动模式 + print("切换手动模式") + self._sys_hand_cmd(True) + time.sleep(1) + while (self._sys_hand_status()) == False: + print("waiting for hand_cmd") + time.sleep(1) + #设备初始化 + self._sys_init_cmd(True) + time.sleep(1) + #sys_init_status为bool值,不加括号 + while (self._sys_init_status())== False: + print("waiting for init_cmd") + time.sleep(1) + #手动按钮置回False + self._sys_hand_cmd(False) + time.sleep(1) + while (self._sys_hand_cmd()) == True: + print("waiting for hand_cmd to False") + time.sleep(1) + #初始化命令置回False + self._sys_init_cmd(False) + time.sleep(1) + while (self._sys_init_cmd()) == True: + print("waiting for init_cmd to False") + time.sleep(1) + + def func_pack_device_auto(self): + #切换自动 + print("切换自动模式") + self._sys_auto_cmd(True) + time.sleep(1) + while (self._sys_auto_status()) == False: + print("waiting for auto_status") + time.sleep(1) + #自动按钮置False + self._sys_auto_cmd(False) + time.sleep(1) + while (self._sys_auto_cmd()) == True: + print("waiting for auto_cmd") + time.sleep(1) + + def func_pack_device_start(self): + #切换自动 + print("启动") + self._sys_start_cmd(True) + time.sleep(1) + while (self._sys_start_status()) == False: + print("waiting for start_status") + time.sleep(1) + #自动按钮置False + self._sys_start_cmd(False) + time.sleep(1) + while (self._sys_start_cmd()) == True: + print("waiting for start_cmd") + time.sleep(1) + + def func_pack_send_bottle_num(self, bottle_num: int): + #发送电解液平台数 + print("启动") + while (self._unilab_rece_electrolyte_bottle_num()) == False: + print("waiting for rece_electrolyte_bottle_num to True") + # self.client.use_node('8520').write(True) + time.sleep(1) + #发送电解液瓶数为2 + self._reg_msg_electrolyte_num(bottle_num) + time.sleep(1) + #完成信号置True + self._unilab_send_electrolyte_bottle_num(True) + time.sleep(1) + #检测到依华已接收 + while (self._unilab_rece_electrolyte_bottle_num()) == True: + print("waiting for rece_electrolyte_bottle_num to False") + time.sleep(1) + #完成信号置False + self._unilab_send_electrolyte_bottle_num(False) + time.sleep(1) + #自动按钮置False + + + # 下发参数 + #def func_pack_send_msg_cmd(self, elec_num: int, elec_use_num: int, elec_vol: float, assembly_type: int, assembly_pressure: int) -> bool: + # """UNILAB写参数""" + # while (self.request_rec_msg_status) == False: + # print("wait for res_msg") + # time.sleep(1) + # self.success = False + # self._unilab_send_msg_electrolyte_num(elec_num) + # time.sleep(1) + # self._unilab_send_msg_electrolyte_use_num(elec_use_num) + # time.sleep(1) + # self._unilab_send_msg_electrolyte_vol(elec_vol) + # time.sleep(1) + # self._unilab_send_msg_assembly_type(assembly_type) + # time.sleep(1) + # self._unilab_send_msg_assembly_pressure(assembly_pressure) + # time.sleep(1) + # self._unilab_send_msg_succ_cmd(True) + # time.sleep(1) + # self._unilab_send_msg_succ_cmd(False) + # #将允许读取标志位置True + # self.allow_data_read = True + # self.success = True + # return self.success + + def func_pack_send_msg_cmd(self, elec_use_num) -> bool: + """UNILAB写参数""" + while (self.request_rec_msg_status) == False: + print("wait for request_rec_msg_status to True") + time.sleep(1) + self.success = False + #self._unilab_send_msg_electrolyte_num(elec_num) + time.sleep(1) + self._unilab_send_msg_electrolyte_use_num(elec_use_num) + time.sleep(1) + self._unilab_send_msg_succ_cmd(True) + time.sleep(1) + while (self.request_rec_msg_status) == True: + print("wait for request_rec_msg_status to False") + time.sleep(1) + self._unilab_send_msg_succ_cmd(False) + #将允许读取标志位置True + self.allow_data_read = True + self.success = True + return self.success + + def func_pack_get_msg_cmd(self, file_path: str="D:\\coin_cell_data") -> bool: + """UNILAB读参数""" + while self.request_send_msg_status == False: + print("waiting for send_read_msg_status to True") + time.sleep(1) + data_open_circuit_voltage = self.data_open_circuit_voltage + data_pole_weight = self.data_pole_weight + data_assembly_time = self.data_assembly_time + data_assembly_pressure = self.data_assembly_pressure + data_electrolyte_volume = self.data_electrolyte_volume + data_coin_num = self.data_coin_num + data_electrolyte_code = self.data_electrolyte_code + data_coin_cell_code = self.data_coin_cell_code + print("data_open_circuit_voltage", data_open_circuit_voltage) + print("data_pole_weight", data_pole_weight) + print("data_assembly_time", data_assembly_time) + print("data_assembly_pressure", data_assembly_pressure) + print("data_electrolyte_volume", data_electrolyte_volume) + print("data_coin_num", data_coin_num) + print("data_electrolyte_code", data_electrolyte_code) + print("data_coin_cell_code", data_coin_cell_code) + #接收完信息后,读取完毕标志位置True + self._unilab_rec_msg_succ_cmd(True) + time.sleep(1) + #等待允许读取标志位置False + while self.request_send_msg_status == True: + print("waiting for send_msg_status to False") + time.sleep(1) + self._unilab_rec_msg_succ_cmd(False) + time.sleep(1) + #将允许读取标志位置True + time_date = datetime.now().strftime("%Y%m%d") + #秒级时间戳用于标记每一行电池数据 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + #生成输出文件的变量 + self.csv_export_file = os.path.join(file_path, f"date_{time_date}.csv") + #将数据存入csv文件 + if not os.path.exists(self.csv_export_file): + #创建一个表头 + with open(self.csv_export_file, 'w', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow([ + 'Time', 'open_circuit_voltage', 'pole_weight', + 'assembly_time', 'assembly_pressure', 'electrolyte_volume', + 'coin_num', 'electrolyte_code', 'coin_cell_code' + ]) + #立刻写入磁盘 + csvfile.flush() + #开始追加电池信息 + with open(self.csv_export_file, 'a', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow([ + timestamp, data_open_circuit_voltage, data_pole_weight, + data_assembly_time, data_assembly_pressure, data_electrolyte_volume, + data_coin_num, data_electrolyte_code, data_coin_cell_code + ]) + #立刻写入磁盘 + csvfile.flush() + self.success = True + return self.success + + + + def func_pack_send_finished_cmd(self) -> bool: + """UNILAB写参数""" + while (self._unilab_rece_finished_cmd()) == False: + print("wait for rece_finished_cmd to True") + time.sleep(1) + self.success = False + self._unilab_send_finished_cmd(True) + time.sleep(1) + while (self._unilab_rece_finished_cmd()) == True: + print("wait for rece_finished_cmd to False") + time.sleep(1) + self._unilab_send_finished_cmd(False) + #将允许读取标志位置True + self.success = True + return self.success + + + + def func_allpack_cmd(self, elec_num, elec_use_num, file_path: str="D:\\coin_cell_data") -> bool: + elec_num, elec_use_num = int(elec_num), int(elec_use_num) + summary_csv_file = os.path.join(file_path, "duandian.csv") + # 如果断点文件存在,先读取之前的进度 + if os.path.exists(summary_csv_file): + read_status_flag = True + with open(summary_csv_file, 'r', newline='', encoding='utf-8') as csvfile: + reader = csv.reader(csvfile) + header = next(reader) # 跳过标题行 + data_row = next(reader) # 读取数据行 + if len(data_row) >= 2: + elec_num_r = int(data_row[0]) + elec_use_num_r = int(data_row[1]) + elec_num_N = int(data_row[2]) + elec_use_num_N = int(data_row[3]) + coin_num_N = int(data_row[4]) + if elec_num_r == elec_num and elec_use_num_r == elec_use_num: + print("断点文件与当前任务匹配,继续") + else: + print("断点文件中elec_num、elec_use_num与当前任务不匹配,请检查任务下发参数或修改断点文件") + return False + print(f"从断点文件读取进度: elec_num_N={elec_num_N}, elec_use_num_N={elec_use_num_N}, coin_num_N={coin_num_N}") + + else: + read_status_flag = False + print("未找到断点文件,从头开始") + elec_num_N = 0 + elec_use_num_N = 0 + coin_num_N = 0 + for i in range(20): + print(f"剩余电解液瓶数: {elec_num}, 已组装电池数: {elec_use_num}") + print(f"剩余电解液瓶数: {type(elec_num)}, 已组装电池数: {type(elec_use_num)}") + print(f"剩余电解液瓶数: {type(int(elec_num))}, 已组装电池数: {type(int(elec_use_num))}") + + #如果是第一次运行,则进行初始化、切换自动、启动, 如果是断点重启则跳过。 + if read_status_flag == False: + #初始化 + self.func_pack_device_init() + #切换自动 + self.func_pack_device_auto() + #启动,小车收回 + self.func_pack_device_start() + #发送电解液瓶数量,启动搬运,多搬运没事 + self.func_pack_send_bottle_num(elec_num) + last_i = elec_num_N + last_j = elec_use_num_N + for i in range(last_i, elec_num): + print(f"开始第{last_i+i+1}瓶电解液的组装") + #第一个循环从上次断点继续,后续循环从0开始 + j_start = last_j if i == last_i else 0 + self.func_pack_send_msg_cmd(elec_use_num-j_start) + + for j in range(j_start, elec_use_num): + print(f"开始第{last_i+i+1}瓶电解液的第{j+j_start+1}个电池组装") + #读取电池组装数据并存入csv + self.func_pack_get_msg_cmd(file_path) + time.sleep(1) + # TODO:读完再将电池数加一还是进入循环就将电池数加一需要考虑 + liaopan1 = self.station_resource.get_resource("liaopan1") + liaopan2 = self.station_resource.get_resource("liaopan2") + jipian1 = liaopan1.children[coin_num_N].children[0] + + #print(jipian1) + #把物料解绑后放到另一盘上 + jipian1.parent.unassign_child_resource(jipian1) + liaopan2.children[coin_num_N].assign_child_resource(jipian1, location=None) + #print(jipian2.parent) + ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ + "resources": [self.station_resource] + }) + + # 生成断点文件 + # 生成包含elec_num_N、coin_num_N、timestamp的CSV文件 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + with open(summary_csv_file, 'w', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow(['elec_num','elec_use_num', 'elec_num_N', 'elec_use_num_N', 'coin_num_N', 'timestamp']) + writer.writerow([elec_num, elec_use_num, elec_num_N, elec_use_num_N, coin_num_N, timestamp]) + csvfile.flush() + coin_num_N += 1 + elec_use_num_N += 1 + elec_num_N += 1 + elec_use_num_N = 0 + + #循环正常结束,则删除断点文件 + os.remove(summary_csv_file) + #全部完成后等待依华发送完成信号 + self.func_pack_send_finished_cmd() + + + def func_pack_device_stop(self) -> bool: + """打包指令:设备停止""" + for i in range(3): + time.sleep(2) + print(f"输出{i}") + #print("_sys_hand_cmd", self._sys_hand_cmd()) + #time.sleep(1) + #print("_sys_hand_status", self._sys_hand_status()) + #time.sleep(1) + #print("_sys_init_cmd", self._sys_init_cmd()) + #time.sleep(1) + #print("_sys_init_status", self._sys_init_status()) + #time.sleep(1) + #print("_sys_auto_status", self._sys_auto_status()) + #time.sleep(1) + #print("data_axis_y_pos", self.data_axis_y_pos) + #time.sleep(1) + #self.success = False + #with open('action_device_stop.json', 'r', encoding='utf-8') as f: + # action_json = json.load(f) + #self.client.execute_procedure_from_json(action_json) + #self.success = True + #return self.success + + def fun_wuliao_test(self) -> bool: + #找到data_init中构建的2个物料盘 + liaopan1 = self.station_resource.get_resource("liaopan1") + liaopan2 = self.station_resource.get_resource("liaopan2") + for i in range(16): + #找到liaopan1上每一个jipian + jipian_linshi = liaopan1.children[i].children[0] + #把物料解绑后放到另一盘上 + print("极片:", jipian_linshi) + jipian_linshi.parent.unassign_child_resource(jipian_linshi) + liaopan2.children[i].assign_child_resource(jipian_linshi, location=None) + ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ + "resources": [self.station_resource] + }) + time.sleep(10) + # 数据读取与输出 + def func_read_data_and_output(self, file_path: str="D:\\coin_cell_data"): + # 检查CSV导出是否正在运行,已运行则跳出,防止同时启动两个while循环 + if self.csv_export_running: + return False, "读取已在运行中" + + #若不存在该目录则创建 + if not os.path.exists(file_path): + os.makedirs(file_path) + print(f"创建目录: {file_path}") + + # 只要允许读取标志位为true,就持续运行该函数,直到触发停止条件 + while self.allow_data_read: + + #函数运行标志位,确保只同时启动一个导出函数 + self.csv_export_running = True + + #等待接收结果标志位置True + while self.request_send_msg_status == False: + print("waiting for send_msg_status to True") + time.sleep(1) + #日期时间戳用于按天存放csv文件 + time_date = datetime.now().strftime("%Y%m%d") + #秒级时间戳用于标记每一行电池数据 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + #生成输出文件的变量 + self.csv_export_file = os.path.join(file_path, f"date_{time_date}.csv") + + #接收信息 + data_open_circuit_voltage = self.data_open_circuit_voltage + data_pole_weight = self.data_pole_weight + data_assembly_time = self.data_assembly_time + data_assembly_pressure = self.data_assembly_pressure + data_electrolyte_volume = self.data_electrolyte_volume + data_coin_num = self.data_coin_num + data_electrolyte_code = self.data_electrolyte_code + data_coin_cell_code = self.data_coin_cell_code + # 电解液瓶位置 + elec_bottle_site = 2 + # 极片夹取位置(应当通过寄存器读光标) + Pos_elec_site = 0 + Al_elec_site = 0 + Gasket_site = 0 + + #接收完信息后,读取完毕标志位置True + self._unilab_rec_msg_succ_cmd()# = True + #等待允许读取标志位置False + while self.request_send_msg_status == True: + print("waiting for send_msg_status to False") + time.sleep(1) + self._unilab_rec_msg_succ_cmd()# = False + + #此处操作物料信息(如果中途报错停止,如何) + #报错怎么办(加个判断标志位,如果发生错误,则根据停止位置扣除物料) + #根据物料光标判断取哪个物料(人工摆盘,电解液瓶,移液枪头都有光标位置,寄存器读即可) + + #物料读取操作写在这里 + #在这里进行物料调取 + #转移物料瓶,elec_bottle_site对应第几瓶电解液(从依华寄存器读取) + # transfer_bottle(deck, elec_bottle_site) + # #找到电解液瓶的对象 + # electrolyte_rack = deck.get_resource("electrolyte_rack") + # pending_positions = electrolyte_rack.get_pending_positions()[elec_bottle_site] + # # TODO: 瓶子取液体操作需要加入 +# +# + # #找到压制工站对应的对象 + # battery_press_slot = deck.get_resource("battery_press_1") + # #创建一个新电池 + # test_battery = Battery( + # name=f"test_battery_{data_coin_num}", + # diameter=20.0, # 与压制槽直径匹配 + # height=3.0, # 电池高度 + # max_volume=100.0, # 100μL容量 + # barcode=data_coin_cell_code, # 电池条码 + # ) + # if battery_press_slot.has_battery(): + # return False, "压制工站已有电池,无法放置新电池" + # #在压制位放置电池 + # battery_press_slot.place_battery(test_battery) + # #从第一个子弹夹中取料 + # clip_magazine_1_hole = self.deck.get_resource("clip_magazine_1").get_item(Pos_elec_site) + # clip_magazine_2_hole = self.deck.get_resource("clip_magazine_2").get_item(Al_elec_site) + # clip_magazine_3_hole = self.deck.get_resource("clip_magazine_3").get_item(Gasket_site) + # + # if clip_magazine_1_hole.get_sheet_count() > 0: # 检查洞位是否有极片 + # electrode_sheet_1 = clip_magazine_1_hole.take_sheet() # 从洞位取出极片 + # test_battery.add_electrode_sheet(electrode_sheet_1) # 添加到电池中 + # print(f"已将极片 {electrode_sheet_1.name} 从子弹夹转移到电池") + # else: + # print("子弹夹洞位0没有极片") +# + # if clip_magazine_2_hole.get_sheet_count() > 0: # 检查洞位是否有极片 + # electrode_sheet_2 = clip_magazine_2_hole.take_sheet() # 从洞位取出极片 + # test_battery.add_electrode_sheet(electrode_sheet_2) # 添加到电池中 + # print(f"已将极片 {electrode_sheet_2.name} 从子弹夹转移到电池") + # else: + # print("子弹夹洞位0没有极片") +# + # if clip_magazine_3_hole.get_sheet_count() > 0: # 检查洞位是否有极片 + # electrode_sheet_3 = clip_magazine_3_hole.take_sheet() # 从洞位取出极片 + # test_battery.add_electrode_sheet(electrode_sheet_3) # 添加到电池中 + # print(f"已将极片 {electrode_sheet_3.name} 从子弹夹转移到电池") + # else: + # print("子弹夹洞位0没有极片") + # + # # TODO:#把电解液从瓶中取到电池夹子中 + # battery_site = deck.get_resource("battery_press_1") + # clip_magazine_battery = deck.get_resource("clip_magazine_battery") + # if battery_site.has_battery(): + # battery = battery_site.take_battery() #从压制槽取出电池 + # clip_magazine_battery.add_battery(battery) #从压制槽取出电池 +# +# +# +# + # # 保存配置到文件 + # self.deck.save("button_battery_station_layout.json", indent=2) + # print("\n台面配置已保存到: button_battery_station_layout.json") + # + # # 保存状态到文件 + # self.deck.save_state_to_file("button_battery_station_state.json", indent=2) + # print("台面状态已保存到: button_battery_station_state.json") + + + + + + + #将数据写入csv中 + #如当前目录下无同名文件则新建一个csv用于存放数据 + if not os.path.exists(self.csv_export_file): + #创建一个表头 + with open(self.csv_export_file, 'w', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow([ + 'Time', 'open_circuit_voltage', 'pole_weight', + 'assembly_time', 'assembly_pressure', 'electrolyte_volume', + 'coin_num', 'electrolyte_code', 'coin_cell_code' + ]) + #立刻写入磁盘 + csvfile.flush() + #开始追加电池信息 + with open(self.csv_export_file, 'a', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow([ + timestamp, data_open_circuit_voltage, data_pole_weight, + data_assembly_time, data_assembly_pressure, data_electrolyte_volume, + data_coin_num, data_electrolyte_code, data_coin_cell_code + ]) + #立刻写入磁盘 + csvfile.flush() + + # 只要不在自动模式运行中,就将允许标志位置False + if self.sys_auto_status == False or self.sys_start_status == False: + self.allow_data_read = False + self.csv_export_running = False + time.sleep(1) + + def func_stop_read_data(self): + """停止CSV导出""" + if not self.csv_export_running: + return False, "read data未在运行" + + self.csv_export_running = False + self.allow_data_read = False + + if self.csv_export_thread and self.csv_export_thread.is_alive(): + self.csv_export_thread.join(timeout=5) + + def func_get_csv_export_status(self): + """获取CSV导出状态""" + return { + 'allow_read': self.allow_data_read, + 'running': self.csv_export_running, + 'thread_alive': self.csv_export_thread.is_alive() if self.csv_export_thread else False + } + + + ''' + # ===================== 物料管理区 ====================== + @property + def data_material_inventory(self) -> int: + """主物料库存 (数量, INT16)""" + inventory, read_err = self.client.use_node('REG_DATA_MATERIAL_INVENTORY').read(1) + return inventory + + @property + def data_tips_inventory(self) -> int: + """移液枪头库存 (数量, INT16)""" + inventory, read_err = self.client.register_node_list(self.nodes).use_node('REG_DATA_TIPS_INVENTORY').read(1) + return inventory + + ''' + + +if __name__ == "__main__": + from pylabrobot.resources import Resource + Coin_Cell = CoinCellAssemblyWorkstation(Resource("1", 1, 1, 1), debug_mode=True) + #Coin_Cell.func_pack_device_init() + #Coin_Cell.func_pack_device_auto() + #Coin_Cell.func_pack_device_start() + #Coin_Cell.func_pack_send_bottle_num(2) + #Coin_Cell.func_pack_send_msg_cmd(2) + #Coin_Cell.func_pack_get_msg_cmd() + #Coin_Cell.func_pack_get_msg_cmd() + #Coin_Cell.func_pack_send_finished_cmd() +# + #Coin_Cell.func_allpack_cmd(3, 2) + #print(Coin_Cell.data_stack_vision_code) + #print("success") + #创建一个物料台面 + + #deck = create_a_coin_cell_deck() + deck = create_a_full_coin_cell_deck() + + + ##在台面上找到料盘和极片 + #liaopan1 = deck.get_resource("liaopan1") + #liaopan2 = deck.get_resource("liaopan2") + #jipian1 = liaopan1.children[1].children[0] +## + #print(jipian1) + ##把物料解绑后放到另一盘上 + #jipian1.parent.unassign_child_resource(jipian1) + #liaopan2.children[1].assign_child_resource(jipian1, location=None) + ##print(jipian2.parent) + + liaopan1 = deck.get_resource("liaopan1") + liaopan2 = deck.get_resource("liaopan2") + for i in range(16): + #找到liaopan1上每一个jipian + jipian_linshi = liaopan1.children[i].children[0] + #把物料解绑后放到另一盘上 + print("极片:", jipian_linshi) + jipian_linshi.parent.unassign_child_resource(jipian_linshi) + liaopan2.children[i].assign_child_resource(jipian_linshi, location=None) + + + from unilabos.resources.graphio import resource_ulab_to_plr, convert_resources_to_type + #with open("./button_battery_station_resources_unilab.json", "r", encoding="utf-8") as f: + # bioyond_resources_unilab = json.load(f) + #print(f"成功读取 JSON 文件,包含 {len(bioyond_resources_unilab)} 个资源") + #ulab_resources = convert_resources_to_type(bioyond_resources_unilab, List[PLRResource]) + #print(f"转换结果类型: {type(ulab_resources)}") + #print(ulab_resources) + + + + from unilabos.resources.graphio import convert_resources_from_type + from unilabos.config.config import BasicConfig + BasicConfig.ak = "beb0c15f-2279-46a1-aba5-00eaf89aef55" + BasicConfig.sk = "15d4f25e-3512-4f9c-9bfb-43ab85e7b561" + from unilabos.app.web.client import http_client + + resources = convert_resources_from_type([deck], [Resource]) + json.dump({"nodes": resources, "links": []}, open("button_battery_station_resources_unilab.json", "w"), indent=2) + + #print(resources) + http_client.remote_addr = "https://uni-lab.test.bohrium.com/api/v1" + + http_client.resource_add(resources) \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_system_wuliao.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_system_wuliao.py new file mode 100644 index 0000000..6abdc38 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_system_wuliao.py @@ -0,0 +1,135 @@ +import json +import re +from pymodbus.client import ModbusTcpClient +from unilabos.device_comms.modbus_plc.modbus import WorderOrder, Coil, DiscreteInputs, HoldRegister, InputRegister, DataType +from pymodbus.constants import Endian +import time +import threading +import csv +import os +from datetime import datetime +from typing import Callable +from unilabos.device_comms.modbus_plc.client import TCPClient, ModbusNode, PLCWorkflow, ModbusWorkflow, WorkflowAction, BaseClient +from unilabos.device_comms.modbus_plc.modbus import DeviceType, Base as ModbusNodeBase, DataType, WorderOrder + +class Coin_Cell_Assembly: + + +if __name__ == '__main__': + coin_cell_assmbly = Coin_Cell_Assembly(address="192.168.1.20", port="502") + + #params = { + # "elec_num": 32 + #} + #str_data = json.dumps(params, ensure_ascii=False) + #print('param:', coin_cell_assmbly.func_pack_device_write_batch_elec_param(params)) + #time.sleep(1) + + print(coin_cell_assmbly.func_pack_device_write_per_elec_param( + elec_use_num=4, + elec_num=5, + elec_vol=55, + assembly_type=25, + assembly_pressure=550)) + time.sleep(1) + + +''' + print('start:', coin_cell_assmbly.func_pack_device_start()) + time.sleep(1) + + + + + + + + + print('start:', coin_cell_assmbly.func_pack_device_start()) + time.sleep(1) + + print('stop:', coin_cell_assmbly.func_pack_device_stop()) + time.sleep(1) + + while True: + # cmd coil + print('start cmd:', coin_cell_assmbly.sys_start_cmd(True)) + time.sleep(1) + print('stop cmd:', coin_cell_assmbly.sys_stop_cmd(False)) + time.sleep(1) + print('reset cmd:', coin_cell_assmbly.sys_reset_cmd(True)) + time.sleep(1) + print('hand cmd:', coin_cell_assmbly.sys_hand_cmd(False)) + time.sleep(1) + print('auto cmd:', coin_cell_assmbly.sys_auto_cmd(True)) + time.sleep(1) + print('init cmd:', coin_cell_assmbly.sys_init_cmd(False)) + time.sleep(1) + print('send msg succ cmd:', coin_cell_assmbly.unilab_send_msg_succ_cmd(False)) + time.sleep(1) + print('rec msg succ cmd:', coin_cell_assmbly.unilab_rec_msg_succ_cmd(True)) + time.sleep(1) + + # cmd reg + print('elec use num msg:', coin_cell_assmbly.unilab_send_msg_electrolyte_use_num(8)) + time.sleep(1) + print('elec num msg:', coin_cell_assmbly.unilab_send_msg_electrolyte_num(4)) + time.sleep(1) + print('elec vol msg:', coin_cell_assmbly.unilab_send_msg_electrolyte_vol(3.3)) + time.sleep(1) + print('assembly type msg:', coin_cell_assmbly.unilab_send_msg_assembly_type(1)) + time.sleep(1) + print('assembly pressure msg:', coin_cell_assmbly.unilab_send_msg_assembly_pressure(1)) + time.sleep(1) + + # status coil + print('start status:',coin_cell_assmbly.sys_start_status) + time.sleep(1) + print('stop status:',coin_cell_assmbly.sys_stop_status) + time.sleep(1) + print('reset status:',coin_cell_assmbly.sys_reset_status) + time.sleep(1) + print('hand status:',coin_cell_assmbly.sys_hand_status) + time.sleep(1) + print('auto status:', coin_cell_assmbly.sys_auto_status) + time.sleep(1) + print('init ok:', coin_cell_assmbly.sys_init_status) + time.sleep(1) + print('request rec msg:', coin_cell_assmbly.request_rec_msg_status) + time.sleep(1) + print('request send msg:', coin_cell_assmbly.request_send_msg_status) + time.sleep(1) + + # status reg + print('assembly coin cell num:', coin_cell_assmbly.data_assembly_coin_cell_num) + time.sleep(1) + print('assembly coin assembly per time:', coin_cell_assmbly.data_assembly_time) + time.sleep(1) + print('open circuit vol:', coin_cell_assmbly.data_open_circuit_voltage) + time.sleep(1) + print('axis x pos:', coin_cell_assmbly.data_axis_x_pos) + time.sleep(1) + print('axis y pos:', coin_cell_assmbly.data_axis_y_pos) + time.sleep(1) + print('axis z pos:', coin_cell_assmbly.data_axis_z_pos) + time.sleep(1) + print('pole weight:', coin_cell_assmbly.data_pole_weight) + time.sleep(1) + print('assembly pressure:', coin_cell_assmbly.data_assembly_coin_cell_num) + time.sleep(1) + print('assembly electrolyte vol:', coin_cell_assmbly.data_electrolyte_volume) + time.sleep(1) + print('assembly coin num:', coin_cell_assmbly.data_coin_num) + time.sleep(1) + print('coin cell code:', coin_cell_assmbly.data_coin_cell_code) + time.sleep(1) + print('elec code:', coin_cell_assmbly.data_electrolyte_code) + time.sleep(1) + print('glove box pressure:', coin_cell_assmbly.data_glove_box_pressure) + time.sleep(1) + print('glove box o2:', coin_cell_assmbly.data_glove_box_o2_content) + time.sleep(1) + print('glove box water:', coin_cell_assmbly.data_glove_box_water_content) + time.sleep(1) + +''' \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig.json b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig.json new file mode 100644 index 0000000..31fb003 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig.json @@ -0,0 +1,1925 @@ +{ + "nodes": [ + { + "id": "BatteryStation", + "name": "扣电工作站", + "children": [ + "deck" + ], + "parent": null, + "type": "device", + "class": "bettery_station_registry", + "position": { + "x": 600, + "y": 400, + "z": 0 + }, + "config": { + "debug_mode": false, + "_comment": "protocol_type接外部工站固定写法字段,一般为空,station_resource写法也固定", + "protocol_type": [], + "station_resource": { + "data": { + "_resource_child_name": "deck", + "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" + } + }, + + "address": "192.168.1.20", + "port": 502 + }, + "data": {} + }, + { + "id": "deck", + "name": "deck", + "sample_id": null, + "children": [ + "liaopan1", + "liaopan2" + ], + "parent": null, + "type": "deck", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "Deck", + "size_x": 1200, + "size_y": 800, + "size_z": 900, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "deck", + "barcode": null + }, + "data": {} + }, + { + "id": "liaopan1", + "name": "liaopan1", + "sample_id": null, + "children": [ + "liaopan1_materialhole_0_0", + "liaopan1_materialhole_0_1", + "liaopan1_materialhole_0_2", + "liaopan1_materialhole_0_3", + "liaopan1_materialhole_1_0", + "liaopan1_materialhole_1_1", + "liaopan1_materialhole_1_2", + "liaopan1_materialhole_1_3", + "liaopan1_materialhole_2_0", + "liaopan1_materialhole_2_1", + "liaopan1_materialhole_2_2", + "liaopan1_materialhole_2_3", + "liaopan1_materialhole_3_0", + "liaopan1_materialhole_3_1", + "liaopan1_materialhole_3_2", + "liaopan1_materialhole_3_3" + ], + "parent": "deck", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120.8, + "size_y": 120.5, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan1_materialhole_0_0", + "B1": "liaopan1_materialhole_0_1", + "C1": "liaopan1_materialhole_0_2", + "D1": "liaopan1_materialhole_0_3", + "A2": "liaopan1_materialhole_1_0", + "B2": "liaopan1_materialhole_1_1", + "C2": "liaopan1_materialhole_1_2", + "D2": "liaopan1_materialhole_1_3", + "A3": "liaopan1_materialhole_2_0", + "B3": "liaopan1_materialhole_2_1", + "C3": "liaopan1_materialhole_2_2", + "D3": "liaopan1_materialhole_2_3", + "A4": "liaopan1_materialhole_3_0", + "B4": "liaopan1_materialhole_3_1", + "C4": "liaopan1_materialhole_3_2", + "D4": "liaopan1_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan1_materialhole_0_0", + "name": "liaopan1_materialhole_0_0", + "sample_id": null, + "children": [ + "jipian_0" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_0", + "name": "jipian_0", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_1", + "name": "liaopan1_materialhole_0_1", + "sample_id": null, + "children": [ + "jipian_1" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_1", + "name": "jipian_1", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_2", + "name": "liaopan1_materialhole_0_2", + "sample_id": null, + "children": [], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_3", + "name": "liaopan1_materialhole_0_3", + "sample_id": null, + "children": [ + "jipian_3" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_3", + "name": "jipian_3", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_0", + "name": "liaopan1_materialhole_1_0", + "sample_id": null, + "children": [ + "jipian_4" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_4", + "name": "jipian_4", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_1", + "name": "liaopan1_materialhole_1_1", + "sample_id": null, + "children": [ + "jipian_5" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_5", + "name": "jipian_5", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_2", + "name": "liaopan1_materialhole_1_2", + "sample_id": null, + "children": [ + "jipian_6" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_6", + "name": "jipian_6", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_3", + "name": "liaopan1_materialhole_1_3", + "sample_id": null, + "children": [ + "jipian_7" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_7", + "name": "jipian_7", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_0", + "name": "liaopan1_materialhole_2_0", + "sample_id": null, + "children": [ + "jipian_8" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_8", + "name": "jipian_8", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_1", + "name": "liaopan1_materialhole_2_1", + "sample_id": null, + "children": [ + "jipian_9" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_9", + "name": "jipian_9", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_2", + "name": "liaopan1_materialhole_2_2", + "sample_id": null, + "children": [ + "jipian_10" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_10", + "name": "jipian_10", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_3", + "name": "liaopan1_materialhole_2_3", + "sample_id": null, + "children": [ + "jipian_11" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_11", + "name": "jipian_11", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_0", + "name": "liaopan1_materialhole_3_0", + "sample_id": null, + "children": [ + "jipian_12" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_12", + "name": "jipian_12", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_1", + "name": "liaopan1_materialhole_3_1", + "sample_id": null, + "children": [ + "jipian_13" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_13", + "name": "jipian_13", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_2", + "name": "liaopan1_materialhole_3_2", + "sample_id": null, + "children": [ + "jipian_14" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_14", + "name": "jipian_14", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_3", + "name": "liaopan1_materialhole_3_3", + "sample_id": null, + "children": [ + "jipian_15" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_15", + "name": "jipian_15", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan2", + "name": "liaopan2", + "sample_id": null, + "children": [ + "liaopan2_materialhole_0_0", + "liaopan2_materialhole_0_1", + "liaopan2_materialhole_0_2", + "liaopan2_materialhole_0_3", + "liaopan2_materialhole_1_0", + "liaopan2_materialhole_1_1", + "liaopan2_materialhole_1_2", + "liaopan2_materialhole_1_3", + "liaopan2_materialhole_2_0", + "liaopan2_materialhole_2_1", + "liaopan2_materialhole_2_2", + "liaopan2_materialhole_2_3", + "liaopan2_materialhole_3_0", + "liaopan2_materialhole_3_1", + "liaopan2_materialhole_3_2", + "liaopan2_materialhole_3_3" + ], + "parent": "deck", + "type": "container", + "class": "", + "position": { + "x": 500, + "y": 0, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120.8, + "size_y": 120.5, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan2_materialhole_0_0", + "B1": "liaopan2_materialhole_0_1", + "C1": "liaopan2_materialhole_0_2", + "D1": "liaopan2_materialhole_0_3", + "A2": "liaopan2_materialhole_1_0", + "B2": "liaopan2_materialhole_1_1", + "C2": "liaopan2_materialhole_1_2", + "D2": "liaopan2_materialhole_1_3", + "A3": "liaopan2_materialhole_2_0", + "B3": "liaopan2_materialhole_2_1", + "C3": "liaopan2_materialhole_2_2", + "D3": "liaopan2_materialhole_2_3", + "A4": "liaopan2_materialhole_3_0", + "B4": "liaopan2_materialhole_3_1", + "C4": "liaopan2_materialhole_3_2", + "D4": "liaopan2_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan2_materialhole_0_0", + "name": "liaopan2_materialhole_0_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_1", + "name": "liaopan2_materialhole_0_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_2", + "name": "liaopan2_materialhole_0_2", + "sample_id": null, + "children": [ + "jipian_2" + ], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_2", + "name": "jipian_2", + "sample_id": null, + "children": [], + "parent": "liaopan2_materialhole_0_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_3", + "name": "liaopan2_materialhole_0_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_0", + "name": "liaopan2_materialhole_1_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_1", + "name": "liaopan2_materialhole_1_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_2", + "name": "liaopan2_materialhole_1_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_3", + "name": "liaopan2_materialhole_1_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_0", + "name": "liaopan2_materialhole_2_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_1", + "name": "liaopan2_materialhole_2_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_2", + "name": "liaopan2_materialhole_2_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_3", + "name": "liaopan2_materialhole_2_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_0", + "name": "liaopan2_materialhole_3_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_1", + "name": "liaopan2_materialhole_3_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_2", + "name": "liaopan2_materialhole_3_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_3", + "name": "liaopan2_materialhole_3_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + } + ], + "links": [] +} \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig2.json b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig2.json new file mode 100644 index 0000000..79baec0 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig2.json @@ -0,0 +1,1925 @@ +{ + "nodes": [ + { + "id": "BatteryStation", + "name": "扣电工作站", + "children": [ + "deck" + ], + "parent": null, + "type": "device", + "class": "bettery_station_registry", + "position": { + "x": 600, + "y": 400, + "z": 0 + }, + "config": { + "debug_mode": false, + "_comment": "protocol_type接外部工站固定写法字段,一般为空,station_resource写法也固定", + "protocol_type": [], + "station_resource": { + "data": { + "_resource_child_name": "deck", + "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" + } + }, + + "address": "192.168.1.20", + "port": 502 + }, + "data": {} + }, + { + "id": "deck", + "name": "deck", + "sample_id": null, + "children": [ + "liaopan1", + "liaopan2" + ], + "parent": null, + "type": "deck", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "Deck", + "size_x": 1200, + "size_y": 800, + "size_z": 900, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "deck", + "barcode": null + }, + "data": {} + }, + { + "id": "liaopan1", + "name": "liaopan1", + "sample_id": null, + "children": [ + "liaopan1_materialhole_0_0", + "liaopan1_materialhole_0_1", + "liaopan1_materialhole_0_2", + "liaopan1_materialhole_0_3", + "liaopan1_materialhole_1_0", + "liaopan1_materialhole_1_1", + "liaopan1_materialhole_1_2", + "liaopan1_materialhole_1_3", + "liaopan1_materialhole_2_0", + "liaopan1_materialhole_2_1", + "liaopan1_materialhole_2_2", + "liaopan1_materialhole_2_3", + "liaopan1_materialhole_3_0", + "liaopan1_materialhole_3_1", + "liaopan1_materialhole_3_2", + "liaopan1_materialhole_3_3" + ], + "parent": "deck", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120.8, + "size_y": 120.5, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan1_materialhole_0_0", + "B1": "liaopan1_materialhole_0_1", + "C1": "liaopan1_materialhole_0_2", + "D1": "liaopan1_materialhole_0_3", + "A2": "liaopan1_materialhole_1_0", + "B2": "liaopan1_materialhole_1_1", + "C2": "liaopan1_materialhole_1_2", + "D2": "liaopan1_materialhole_1_3", + "A3": "liaopan1_materialhole_2_0", + "B3": "liaopan1_materialhole_2_1", + "C3": "liaopan1_materialhole_2_2", + "D3": "liaopan1_materialhole_2_3", + "A4": "liaopan1_materialhole_3_0", + "B4": "liaopan1_materialhole_3_1", + "C4": "liaopan1_materialhole_3_2", + "D4": "liaopan1_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan1_materialhole_0_0", + "name": "liaopan1_materialhole_0_0", + "sample_id": null, + "children": [ + "jipian_0" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_0", + "name": "jipian_0", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_1", + "name": "liaopan1_materialhole_0_1", + "sample_id": null, + "children": [ + "jipian_1" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_1", + "name": "jipian_1", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_2", + "name": "liaopan1_materialhole_0_2", + "sample_id": null, + "children": [ + "jipian_2" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_2", + "name": "jipian_2", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_3", + "name": "liaopan1_materialhole_0_3", + "sample_id": null, + "children": [ + "jipian_3" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_3", + "name": "jipian_3", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_0", + "name": "liaopan1_materialhole_1_0", + "sample_id": null, + "children": [ + "jipian_4" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_4", + "name": "jipian_4", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_1", + "name": "liaopan1_materialhole_1_1", + "sample_id": null, + "children": [ + "jipian_5" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_5", + "name": "jipian_5", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_2", + "name": "liaopan1_materialhole_1_2", + "sample_id": null, + "children": [ + "jipian_6" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_6", + "name": "jipian_6", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_3", + "name": "liaopan1_materialhole_1_3", + "sample_id": null, + "children": [ + "jipian_7" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_7", + "name": "jipian_7", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_0", + "name": "liaopan1_materialhole_2_0", + "sample_id": null, + "children": [ + "jipian_8" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_8", + "name": "jipian_8", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_1", + "name": "liaopan1_materialhole_2_1", + "sample_id": null, + "children": [ + "jipian_9" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_9", + "name": "jipian_9", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_2", + "name": "liaopan1_materialhole_2_2", + "sample_id": null, + "children": [ + "jipian_10" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_10", + "name": "jipian_10", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_3", + "name": "liaopan1_materialhole_2_3", + "sample_id": null, + "children": [ + "jipian_11" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_11", + "name": "jipian_11", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_0", + "name": "liaopan1_materialhole_3_0", + "sample_id": null, + "children": [ + "jipian_12" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_12", + "name": "jipian_12", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_1", + "name": "liaopan1_materialhole_3_1", + "sample_id": null, + "children": [ + "jipian_13" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_13", + "name": "jipian_13", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_2", + "name": "liaopan1_materialhole_3_2", + "sample_id": null, + "children": [ + "jipian_14" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_14", + "name": "jipian_14", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_3", + "name": "liaopan1_materialhole_3_3", + "sample_id": null, + "children": [ + "jipian_15" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_15", + "name": "jipian_15", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan2", + "name": "liaopan2", + "sample_id": null, + "children": [ + "liaopan2_materialhole_0_0", + "liaopan2_materialhole_0_1", + "liaopan2_materialhole_0_2", + "liaopan2_materialhole_0_3", + "liaopan2_materialhole_1_0", + "liaopan2_materialhole_1_1", + "liaopan2_materialhole_1_2", + "liaopan2_materialhole_1_3", + "liaopan2_materialhole_2_0", + "liaopan2_materialhole_2_1", + "liaopan2_materialhole_2_2", + "liaopan2_materialhole_2_3", + "liaopan2_materialhole_3_0", + "liaopan2_materialhole_3_1", + "liaopan2_materialhole_3_2", + "liaopan2_materialhole_3_3" + ], + "parent": "deck", + "type": "container", + "class": "", + "position": { + "x": 500, + "y": 0, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120.8, + "size_y": 120.5, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan2_materialhole_0_0", + "B1": "liaopan2_materialhole_0_1", + "C1": "liaopan2_materialhole_0_2", + "D1": "liaopan2_materialhole_0_3", + "A2": "liaopan2_materialhole_1_0", + "B2": "liaopan2_materialhole_1_1", + "C2": "liaopan2_materialhole_1_2", + "D2": "liaopan2_materialhole_1_3", + "A3": "liaopan2_materialhole_2_0", + "B3": "liaopan2_materialhole_2_1", + "C3": "liaopan2_materialhole_2_2", + "D3": "liaopan2_materialhole_2_3", + "A4": "liaopan2_materialhole_3_0", + "B4": "liaopan2_materialhole_3_1", + "C4": "liaopan2_materialhole_3_2", + "D4": "liaopan2_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan2_materialhole_0_0", + "name": "liaopan2_materialhole_0_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_1", + "name": "liaopan2_materialhole_0_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_2", + "name": "liaopan2_materialhole_0_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_3", + "name": "liaopan2_materialhole_0_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_0", + "name": "liaopan2_materialhole_1_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_1", + "name": "liaopan2_materialhole_1_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_2", + "name": "liaopan2_materialhole_1_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_3", + "name": "liaopan2_materialhole_1_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_0", + "name": "liaopan2_materialhole_2_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_1", + "name": "liaopan2_materialhole_2_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_2", + "name": "liaopan2_materialhole_2_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_3", + "name": "liaopan2_materialhole_2_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_0", + "name": "liaopan2_materialhole_3_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_1", + "name": "liaopan2_materialhole_3_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_2", + "name": "liaopan2_materialhole_3_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_3", + "name": "liaopan2_materialhole_3_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + } + ], + "links": [] +} \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3.json b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3.json new file mode 100644 index 0000000..39e72a7 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3.json @@ -0,0 +1,1925 @@ +{ + "nodes": [ + { + "id": "BatteryStation", + "name": "扣电工作站", + "children": [ + "coin_cell_deck" + ], + "parent": null, + "type": "device", + "class": "bettery_station_registry", + "position": { + "x": 600, + "y": 400, + "z": 0 + }, + "config": { + "debug_mode": false, + "_comment": "protocol_type接外部工站固定写法字段,一般为空,station_resource写法也固定", + "protocol_type": [], + "station_resource": { + "data": { + "_resource_child_name": "coin_cell_deck", + "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" + } + }, + + "address": "192.168.1.20", + "port": 502 + }, + "data": {} + }, + { + "id": "coin_cell_deck", + "name": "coin_cell_deck", + "sample_id": null, + "children": [ + "liaopan1", + "liaopan2" + ], + "parent": null, + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "CoincellDeck", + "size_x": 1620.0, + "size_y": 1270.0, + "size_z": 500.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "coin_cell_deck", + "barcode": null + }, + "data": {} + }, + { + "id": "liaopan1", + "name": "liaopan1", + "sample_id": null, + "children": [ + "liaopan1_materialhole_0_0", + "liaopan1_materialhole_0_1", + "liaopan1_materialhole_0_2", + "liaopan1_materialhole_0_3", + "liaopan1_materialhole_1_0", + "liaopan1_materialhole_1_1", + "liaopan1_materialhole_1_2", + "liaopan1_materialhole_1_3", + "liaopan1_materialhole_2_0", + "liaopan1_materialhole_2_1", + "liaopan1_materialhole_2_2", + "liaopan1_materialhole_2_3", + "liaopan1_materialhole_3_0", + "liaopan1_materialhole_3_1", + "liaopan1_materialhole_3_2", + "liaopan1_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120.8, + "size_y": 120.5, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan1_materialhole_0_0", + "B1": "liaopan1_materialhole_0_1", + "C1": "liaopan1_materialhole_0_2", + "D1": "liaopan1_materialhole_0_3", + "A2": "liaopan1_materialhole_1_0", + "B2": "liaopan1_materialhole_1_1", + "C2": "liaopan1_materialhole_1_2", + "D2": "liaopan1_materialhole_1_3", + "A3": "liaopan1_materialhole_2_0", + "B3": "liaopan1_materialhole_2_1", + "C3": "liaopan1_materialhole_2_2", + "D3": "liaopan1_materialhole_2_3", + "A4": "liaopan1_materialhole_3_0", + "B4": "liaopan1_materialhole_3_1", + "C4": "liaopan1_materialhole_3_2", + "D4": "liaopan1_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan1_materialhole_0_0", + "name": "liaopan1_materialhole_0_0", + "sample_id": null, + "children": [ + "jipian_0" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_0", + "name": "jipian_0", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_1", + "name": "liaopan1_materialhole_0_1", + "sample_id": null, + "children": [ + "jipian_1" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_1", + "name": "jipian_1", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_2", + "name": "liaopan1_materialhole_0_2", + "sample_id": null, + "children": [ + "jipian_2" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_2", + "name": "jipian_2", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_3", + "name": "liaopan1_materialhole_0_3", + "sample_id": null, + "children": [ + "jipian_3" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_3", + "name": "jipian_3", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_0", + "name": "liaopan1_materialhole_1_0", + "sample_id": null, + "children": [ + "jipian_4" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_4", + "name": "jipian_4", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_1", + "name": "liaopan1_materialhole_1_1", + "sample_id": null, + "children": [ + "jipian_5" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_5", + "name": "jipian_5", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_2", + "name": "liaopan1_materialhole_1_2", + "sample_id": null, + "children": [ + "jipian_6" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_6", + "name": "jipian_6", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_3", + "name": "liaopan1_materialhole_1_3", + "sample_id": null, + "children": [ + "jipian_7" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_7", + "name": "jipian_7", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_0", + "name": "liaopan1_materialhole_2_0", + "sample_id": null, + "children": [ + "jipian_8" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_8", + "name": "jipian_8", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_1", + "name": "liaopan1_materialhole_2_1", + "sample_id": null, + "children": [ + "jipian_9" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_9", + "name": "jipian_9", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_2", + "name": "liaopan1_materialhole_2_2", + "sample_id": null, + "children": [ + "jipian_10" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_10", + "name": "jipian_10", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_3", + "name": "liaopan1_materialhole_2_3", + "sample_id": null, + "children": [ + "jipian_11" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_11", + "name": "jipian_11", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_0", + "name": "liaopan1_materialhole_3_0", + "sample_id": null, + "children": [ + "jipian_12" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_12", + "name": "jipian_12", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_1", + "name": "liaopan1_materialhole_3_1", + "sample_id": null, + "children": [ + "jipian_13" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_13", + "name": "jipian_13", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_2", + "name": "liaopan1_materialhole_3_2", + "sample_id": null, + "children": [ + "jipian_14" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_14", + "name": "jipian_14", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_3", + "name": "liaopan1_materialhole_3_3", + "sample_id": null, + "children": [ + "jipian_15" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_15", + "name": "jipian_15", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan2", + "name": "liaopan2", + "sample_id": null, + "children": [ + "liaopan2_materialhole_0_0", + "liaopan2_materialhole_0_1", + "liaopan2_materialhole_0_2", + "liaopan2_materialhole_0_3", + "liaopan2_materialhole_1_0", + "liaopan2_materialhole_1_1", + "liaopan2_materialhole_1_2", + "liaopan2_materialhole_1_3", + "liaopan2_materialhole_2_0", + "liaopan2_materialhole_2_1", + "liaopan2_materialhole_2_2", + "liaopan2_materialhole_2_3", + "liaopan2_materialhole_3_0", + "liaopan2_materialhole_3_1", + "liaopan2_materialhole_3_2", + "liaopan2_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 500, + "y": 0, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120.8, + "size_y": 120.5, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan2_materialhole_0_0", + "B1": "liaopan2_materialhole_0_1", + "C1": "liaopan2_materialhole_0_2", + "D1": "liaopan2_materialhole_0_3", + "A2": "liaopan2_materialhole_1_0", + "B2": "liaopan2_materialhole_1_1", + "C2": "liaopan2_materialhole_1_2", + "D2": "liaopan2_materialhole_1_3", + "A3": "liaopan2_materialhole_2_0", + "B3": "liaopan2_materialhole_2_1", + "C3": "liaopan2_materialhole_2_2", + "D3": "liaopan2_materialhole_2_3", + "A4": "liaopan2_materialhole_3_0", + "B4": "liaopan2_materialhole_3_1", + "C4": "liaopan2_materialhole_3_2", + "D4": "liaopan2_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan2_materialhole_0_0", + "name": "liaopan2_materialhole_0_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_1", + "name": "liaopan2_materialhole_0_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_2", + "name": "liaopan2_materialhole_0_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_3", + "name": "liaopan2_materialhole_0_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_0", + "name": "liaopan2_materialhole_1_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_1", + "name": "liaopan2_materialhole_1_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_2", + "name": "liaopan2_materialhole_1_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_3", + "name": "liaopan2_materialhole_1_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_0", + "name": "liaopan2_materialhole_2_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_1", + "name": "liaopan2_materialhole_2_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_2", + "name": "liaopan2_materialhole_2_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_3", + "name": "liaopan2_materialhole_2_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_0", + "name": "liaopan2_materialhole_3_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_1", + "name": "liaopan2_materialhole_3_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_2", + "name": "liaopan2_materialhole_3_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_3", + "name": "liaopan2_materialhole_3_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + } + ], + "links": [] +} \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3a.json b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3a.json new file mode 100644 index 0000000..3a42dda --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3a.json @@ -0,0 +1,1956 @@ +{ + "nodes": [ + { + "id": "BatteryStation", + "name": "扣电工作站", + "children": [ + "coin_cell_deck" + ], + "parent": null, + "type": "device", + "class": "bettery_station_registry", + "position": { + "x": 600, + "y": 400, + "z": 0 + }, + "config": { + "debug_mode": false, + "_comment": "protocol_type接外部工站固定写法字段,一般为空,station_resource写法也固定", + "protocol_type": [], + "station_resource": { + "data": { + "_resource_child_name": "coin_cell_deck", + "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" + } + }, + + "address": "192.168.1.20", + "port": 502 + }, + "data": {} + }, + { + "id": "coin_cell_deck", + "name": "coin_cell_deck", + "sample_id": null, + "children": [ + "liaopan1", + "liaopan2", + "battery1" + ], + "parent": null, + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "CoincellDeck", + "size_x": 1200, + "size_y": 800, + "size_z": 900, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "coin_cell_deck", + "barcode": null + }, + "data": {} + }, + { + "id": "liaopan1", + "name": "liaopan1", + "sample_id": null, + "children": [ + "liaopan1_materialhole_0_0", + "liaopan1_materialhole_0_1", + "liaopan1_materialhole_0_2", + "liaopan1_materialhole_0_3", + "liaopan1_materialhole_1_0", + "liaopan1_materialhole_1_1", + "liaopan1_materialhole_1_2", + "liaopan1_materialhole_1_3", + "liaopan1_materialhole_2_0", + "liaopan1_materialhole_2_1", + "liaopan1_materialhole_2_2", + "liaopan1_materialhole_2_3", + "liaopan1_materialhole_3_0", + "liaopan1_materialhole_3_1", + "liaopan1_materialhole_3_2", + "liaopan1_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120.8, + "size_y": 120.5, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan1_materialhole_0_0", + "B1": "liaopan1_materialhole_0_1", + "C1": "liaopan1_materialhole_0_2", + "D1": "liaopan1_materialhole_0_3", + "A2": "liaopan1_materialhole_1_0", + "B2": "liaopan1_materialhole_1_1", + "C2": "liaopan1_materialhole_1_2", + "D2": "liaopan1_materialhole_1_3", + "A3": "liaopan1_materialhole_2_0", + "B3": "liaopan1_materialhole_2_1", + "C3": "liaopan1_materialhole_2_2", + "D3": "liaopan1_materialhole_2_3", + "A4": "liaopan1_materialhole_3_0", + "B4": "liaopan1_materialhole_3_1", + "C4": "liaopan1_materialhole_3_2", + "D4": "liaopan1_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan1_materialhole_0_0", + "name": "liaopan1_materialhole_0_0", + "sample_id": null, + "children": [ + "jipian_0" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_0", + "name": "jipian_0", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_1", + "name": "liaopan1_materialhole_0_1", + "sample_id": null, + "children": [ + "jipian_1" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_1", + "name": "jipian_1", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_2", + "name": "liaopan1_materialhole_0_2", + "sample_id": null, + "children": [ + "jipian_2" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_2", + "name": "jipian_2", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_3", + "name": "liaopan1_materialhole_0_3", + "sample_id": null, + "children": [ + "jipian_3" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_3", + "name": "jipian_3", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_0", + "name": "liaopan1_materialhole_1_0", + "sample_id": null, + "children": [ + "jipian_4" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_4", + "name": "jipian_4", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_1", + "name": "liaopan1_materialhole_1_1", + "sample_id": null, + "children": [ + "jipian_5" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_5", + "name": "jipian_5", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_2", + "name": "liaopan1_materialhole_1_2", + "sample_id": null, + "children": [ + "jipian_6" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_6", + "name": "jipian_6", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_3", + "name": "liaopan1_materialhole_1_3", + "sample_id": null, + "children": [ + "jipian_7" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_7", + "name": "jipian_7", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_0", + "name": "liaopan1_materialhole_2_0", + "sample_id": null, + "children": [ + "jipian_8" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_8", + "name": "jipian_8", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_1", + "name": "liaopan1_materialhole_2_1", + "sample_id": null, + "children": [ + "jipian_9" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_9", + "name": "jipian_9", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_2", + "name": "liaopan1_materialhole_2_2", + "sample_id": null, + "children": [ + "jipian_10" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_10", + "name": "jipian_10", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_3", + "name": "liaopan1_materialhole_2_3", + "sample_id": null, + "children": [ + "jipian_11" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_11", + "name": "jipian_11", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_0", + "name": "liaopan1_materialhole_3_0", + "sample_id": null, + "children": [ + "jipian_12" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_12", + "name": "jipian_12", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_1", + "name": "liaopan1_materialhole_3_1", + "sample_id": null, + "children": [ + "jipian_13" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_13", + "name": "jipian_13", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_2", + "name": "liaopan1_materialhole_3_2", + "sample_id": null, + "children": [ + "jipian_14" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_14", + "name": "jipian_14", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_3", + "name": "liaopan1_materialhole_3_3", + "sample_id": null, + "children": [ + "jipian_15" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian_15", + "name": "jipian_15", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan2", + "name": "liaopan2", + "sample_id": null, + "children": [ + "liaopan2_materialhole_0_0", + "liaopan2_materialhole_0_1", + "liaopan2_materialhole_0_2", + "liaopan2_materialhole_0_3", + "liaopan2_materialhole_1_0", + "liaopan2_materialhole_1_1", + "liaopan2_materialhole_1_2", + "liaopan2_materialhole_1_3", + "liaopan2_materialhole_2_0", + "liaopan2_materialhole_2_1", + "liaopan2_materialhole_2_2", + "liaopan2_materialhole_2_3", + "liaopan2_materialhole_3_0", + "liaopan2_materialhole_3_1", + "liaopan2_materialhole_3_2", + "liaopan2_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 500, + "y": 0, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120.8, + "size_y": 120.5, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan2_materialhole_0_0", + "B1": "liaopan2_materialhole_0_1", + "C1": "liaopan2_materialhole_0_2", + "D1": "liaopan2_materialhole_0_3", + "A2": "liaopan2_materialhole_1_0", + "B2": "liaopan2_materialhole_1_1", + "C2": "liaopan2_materialhole_1_2", + "D2": "liaopan2_materialhole_1_3", + "A3": "liaopan2_materialhole_2_0", + "B3": "liaopan2_materialhole_2_1", + "C3": "liaopan2_materialhole_2_2", + "D3": "liaopan2_materialhole_2_3", + "A4": "liaopan2_materialhole_3_0", + "B4": "liaopan2_materialhole_3_1", + "C4": "liaopan2_materialhole_3_2", + "D4": "liaopan2_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan2_materialhole_0_0", + "name": "liaopan2_materialhole_0_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_1", + "name": "liaopan2_materialhole_0_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_2", + "name": "liaopan2_materialhole_0_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_3", + "name": "liaopan2_materialhole_0_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_0", + "name": "liaopan2_materialhole_1_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_1", + "name": "liaopan2_materialhole_1_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_2", + "name": "liaopan2_materialhole_1_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_3", + "name": "liaopan2_materialhole_1_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_0", + "name": "liaopan2_materialhole_2_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_1", + "name": "liaopan2_materialhole_2_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_2", + "name": "liaopan2_materialhole_2_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_3", + "name": "liaopan2_materialhole_2_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_0", + "name": "liaopan2_materialhole_3_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_1", + "name": "liaopan2_materialhole_3_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_2", + "name": "liaopan2_materialhole_3_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_3", + "name": "liaopan2_materialhole_3_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "battery1", + "name": "battery1", + "sample_id": null, + "children": [], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 600, + "y": 600, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 30, + "size_y": 30, + "size_z": 30, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "battery", + "model": null, + "barcode": null + }, + "data": {} + } + ], + "links": [] +} \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3b.json b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3b.json new file mode 100644 index 0000000..78a6081 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3b.json @@ -0,0 +1,2589 @@ +{ + "nodes": [ + { + "id": "BatteryStation", + "name": "扣电工作站", + "children": [ + "coin_cell_deck" + ], + "parent": null, + "type": "device", + "class": "bettery_station_registry", + "position": { + "x": 600, + "y": 400, + "z": 0 + }, + "config": { + "debug_mode": false, + "_comment": "protocol_type接外部工站固定写法字段,一般为空,station_resource写法也固定", + "protocol_type": [], + "station_resource": { + "data": { + "_resource_child_name": "coin_cell_deck", + "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" + } + }, + + "address": "192.168.1.20", + "port": 502 + }, + "data": {} + }, + { + "id": "coin_cell_deck", + "name": "coin_cell_deck", + "sample_id": null, + "children": [ + "liaopan1", + "liaopan2", + "\u7535\u6c60\u6599\u76d8" + ], + "parent": null, + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "CoincellDeck", + "size_x": 1200, + "size_y": 800, + "size_z": 900, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "coin_cell_deck", + "barcode": null + }, + "data": {} + }, + { + "id": "liaopan1", + "name": "liaopan1", + "sample_id": null, + "children": [ + "liaopan1_materialhole_0_0", + "liaopan1_materialhole_0_1", + "liaopan1_materialhole_0_2", + "liaopan1_materialhole_0_3", + "liaopan1_materialhole_1_0", + "liaopan1_materialhole_1_1", + "liaopan1_materialhole_1_2", + "liaopan1_materialhole_1_3", + "liaopan1_materialhole_2_0", + "liaopan1_materialhole_2_1", + "liaopan1_materialhole_2_2", + "liaopan1_materialhole_2_3", + "liaopan1_materialhole_3_0", + "liaopan1_materialhole_3_1", + "liaopan1_materialhole_3_2", + "liaopan1_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120.8, + "size_y": 120.5, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan1_materialhole_0_0", + "B1": "liaopan1_materialhole_0_1", + "C1": "liaopan1_materialhole_0_2", + "D1": "liaopan1_materialhole_0_3", + "A2": "liaopan1_materialhole_1_0", + "B2": "liaopan1_materialhole_1_1", + "C2": "liaopan1_materialhole_1_2", + "D2": "liaopan1_materialhole_1_3", + "A3": "liaopan1_materialhole_2_0", + "B3": "liaopan1_materialhole_2_1", + "C3": "liaopan1_materialhole_2_2", + "D3": "liaopan1_materialhole_2_3", + "A4": "liaopan1_materialhole_3_0", + "B4": "liaopan1_materialhole_3_1", + "C4": "liaopan1_materialhole_3_2", + "D4": "liaopan1_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan1_materialhole_0_0", + "name": "liaopan1_materialhole_0_0", + "sample_id": null, + "children": [ + "jipian1_0" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_0", + "name": "jipian1_0", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_1", + "name": "liaopan1_materialhole_0_1", + "sample_id": null, + "children": [ + "jipian1_1" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_1", + "name": "jipian1_1", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_2", + "name": "liaopan1_materialhole_0_2", + "sample_id": null, + "children": [ + "jipian1_2" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_2", + "name": "jipian1_2", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_3", + "name": "liaopan1_materialhole_0_3", + "sample_id": null, + "children": [ + "jipian1_3" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_3", + "name": "jipian1_3", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_0", + "name": "liaopan1_materialhole_1_0", + "sample_id": null, + "children": [ + "jipian1_4" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_4", + "name": "jipian1_4", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_1", + "name": "liaopan1_materialhole_1_1", + "sample_id": null, + "children": [ + "jipian1_5" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_5", + "name": "jipian1_5", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_2", + "name": "liaopan1_materialhole_1_2", + "sample_id": null, + "children": [ + "jipian1_6" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_6", + "name": "jipian1_6", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_3", + "name": "liaopan1_materialhole_1_3", + "sample_id": null, + "children": [ + "jipian1_7" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_7", + "name": "jipian1_7", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_0", + "name": "liaopan1_materialhole_2_0", + "sample_id": null, + "children": [ + "jipian1_8" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_8", + "name": "jipian1_8", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_1", + "name": "liaopan1_materialhole_2_1", + "sample_id": null, + "children": [ + "jipian1_9" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_9", + "name": "jipian1_9", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_2", + "name": "liaopan1_materialhole_2_2", + "sample_id": null, + "children": [ + "jipian1_10" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_10", + "name": "jipian1_10", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_3", + "name": "liaopan1_materialhole_2_3", + "sample_id": null, + "children": [ + "jipian1_11" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_11", + "name": "jipian1_11", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_0", + "name": "liaopan1_materialhole_3_0", + "sample_id": null, + "children": [ + "jipian1_12" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_12", + "name": "jipian1_12", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_1", + "name": "liaopan1_materialhole_3_1", + "sample_id": null, + "children": [ + "jipian1_13" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_13", + "name": "jipian1_13", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_2", + "name": "liaopan1_materialhole_3_2", + "sample_id": null, + "children": [ + "jipian1_14" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_14", + "name": "jipian1_14", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_3", + "name": "liaopan1_materialhole_3_3", + "sample_id": null, + "children": [ + "jipian1_15" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_15", + "name": "jipian1_15", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan2", + "name": "liaopan2", + "sample_id": null, + "children": [ + "liaopan2_materialhole_0_0", + "liaopan2_materialhole_0_1", + "liaopan2_materialhole_0_2", + "liaopan2_materialhole_0_3", + "liaopan2_materialhole_1_0", + "liaopan2_materialhole_1_1", + "liaopan2_materialhole_1_2", + "liaopan2_materialhole_1_3", + "liaopan2_materialhole_2_0", + "liaopan2_materialhole_2_1", + "liaopan2_materialhole_2_2", + "liaopan2_materialhole_2_3", + "liaopan2_materialhole_3_0", + "liaopan2_materialhole_3_1", + "liaopan2_materialhole_3_2", + "liaopan2_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 500, + "y": 0, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120.8, + "size_y": 120.5, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan2_materialhole_0_0", + "B1": "liaopan2_materialhole_0_1", + "C1": "liaopan2_materialhole_0_2", + "D1": "liaopan2_materialhole_0_3", + "A2": "liaopan2_materialhole_1_0", + "B2": "liaopan2_materialhole_1_1", + "C2": "liaopan2_materialhole_1_2", + "D2": "liaopan2_materialhole_1_3", + "A3": "liaopan2_materialhole_2_0", + "B3": "liaopan2_materialhole_2_1", + "C3": "liaopan2_materialhole_2_2", + "D3": "liaopan2_materialhole_2_3", + "A4": "liaopan2_materialhole_3_0", + "B4": "liaopan2_materialhole_3_1", + "C4": "liaopan2_materialhole_3_2", + "D4": "liaopan2_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan2_materialhole_0_0", + "name": "liaopan2_materialhole_0_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_1", + "name": "liaopan2_materialhole_0_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_2", + "name": "liaopan2_materialhole_0_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_3", + "name": "liaopan2_materialhole_0_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_0", + "name": "liaopan2_materialhole_1_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_1", + "name": "liaopan2_materialhole_1_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_2", + "name": "liaopan2_materialhole_1_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_3", + "name": "liaopan2_materialhole_1_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_0", + "name": "liaopan2_materialhole_2_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_1", + "name": "liaopan2_materialhole_2_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_2", + "name": "liaopan2_materialhole_2_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_3", + "name": "liaopan2_materialhole_2_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_0", + "name": "liaopan2_materialhole_3_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_1", + "name": "liaopan2_materialhole_3_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_2", + "name": "liaopan2_materialhole_3_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_3", + "name": "liaopan2_materialhole_3_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8", + "name": "\u7535\u6c60\u6599\u76d8", + "sample_id": null, + "children": [ + "\u7535\u6c60\u6599\u76d8_materialhole_0_0", + "\u7535\u6c60\u6599\u76d8_materialhole_0_1", + "\u7535\u6c60\u6599\u76d8_materialhole_0_2", + "\u7535\u6c60\u6599\u76d8_materialhole_0_3", + "\u7535\u6c60\u6599\u76d8_materialhole_1_0", + "\u7535\u6c60\u6599\u76d8_materialhole_1_1", + "\u7535\u6c60\u6599\u76d8_materialhole_1_2", + "\u7535\u6c60\u6599\u76d8_materialhole_1_3", + "\u7535\u6c60\u6599\u76d8_materialhole_2_0", + "\u7535\u6c60\u6599\u76d8_materialhole_2_1", + "\u7535\u6c60\u6599\u76d8_materialhole_2_2", + "\u7535\u6c60\u6599\u76d8_materialhole_2_3", + "\u7535\u6c60\u6599\u76d8_materialhole_3_0", + "\u7535\u6c60\u6599\u76d8_materialhole_3_1", + "\u7535\u6c60\u6599\u76d8_materialhole_3_2", + "\u7535\u6c60\u6599\u76d8_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 300, + "y": 300, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120.8, + "size_y": 150.5, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", + "B1": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", + "C1": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", + "D1": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", + "A2": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", + "B2": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", + "C2": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", + "D2": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", + "A3": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", + "B3": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", + "C3": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", + "D3": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", + "A4": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", + "B4": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", + "C4": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", + "D4": "\u7535\u6c60\u6599\u76d8_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", + "sample_id": null, + "children": [ + "battery1" + ], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 99.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "battery1", + "name": "battery1", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 16, + "size_y": 16, + "size_z": 2, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20.0, + "height": 20.0, + "assembly_pressure": 20.0, + "electrolyte_volume": 20.0, + "electrolyte_name": "DP001" + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 75.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 51.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 27.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 99.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 75.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 51.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 27.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 99.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 75.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 51.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 27.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 99.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 75.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 51.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_3", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_3", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 27.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + } + ], + "links": [] +} \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json new file mode 100644 index 0000000..630faa5 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json @@ -0,0 +1,691 @@ +{ + "nodes": [ + { + "id": "BatteryStation", + "name": "扣电工作站", + "children": [ + "coin_cell_deck" + ], + "parent": null, + "type": "device", + "class": "bettery_station_registry", + "position": { + "x": 600, + "y": 400, + "z": 0 + }, + "config": { + "debug_mode": false, + "_comment": "protocol_type接外部工站固定写法字段,一般为空,station_resource写法也固定", + "protocol_type": [], + "station_resource": { + "data": { + "_resource_child_name": "coin_cell_deck", + "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" + } + }, + + "address": "192.168.1.20", + "port": 502 + }, + "data": {} + }, + { + "id": "coin_cell_deck", + "name": "coin_cell_deck", + "sample_id": null, + "children": [ + "\u7535\u6c60\u6599\u76d8" + ], + "parent": null, + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "CoincellDeck", + "size_x": 1000, + "size_y": 1000, + "size_z": 900, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "coin_cell_deck", + "barcode": null + }, + "data": {} + }, + { + "id": "\u7535\u6c60\u6599\u76d8", + "name": "\u7535\u6c60\u6599\u76d8", + "sample_id": null, + "children": [ + "\u7535\u6c60\u6599\u76d8_materialhole_0_0", + "\u7535\u6c60\u6599\u76d8_materialhole_0_1", + "\u7535\u6c60\u6599\u76d8_materialhole_0_2", + "\u7535\u6c60\u6599\u76d8_materialhole_0_3", + "\u7535\u6c60\u6599\u76d8_materialhole_1_0", + "\u7535\u6c60\u6599\u76d8_materialhole_1_1", + "\u7535\u6c60\u6599\u76d8_materialhole_1_2", + "\u7535\u6c60\u6599\u76d8_materialhole_1_3", + "\u7535\u6c60\u6599\u76d8_materialhole_2_0", + "\u7535\u6c60\u6599\u76d8_materialhole_2_1", + "\u7535\u6c60\u6599\u76d8_materialhole_2_2", + "\u7535\u6c60\u6599\u76d8_materialhole_2_3", + "\u7535\u6c60\u6599\u76d8_materialhole_3_0", + "\u7535\u6c60\u6599\u76d8_materialhole_3_1", + "\u7535\u6c60\u6599\u76d8_materialhole_3_2", + "\u7535\u6c60\u6599\u76d8_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 100, + "y": 100, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120.8, + "size_y": 160.5, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", + "B1": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", + "C1": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", + "D1": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", + "A2": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", + "B2": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", + "C2": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", + "D2": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", + "A3": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", + "B3": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", + "C3": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", + "D3": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", + "A4": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", + "B4": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", + "C4": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", + "D4": "\u7535\u6c60\u6599\u76d8_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 104.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 80.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 56.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 32.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 104.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 80.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 56.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 32.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 104.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 80.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 56.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 32.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 104.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 80.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 56.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_3", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_3", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 32.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + } + ], + "links": [] +} \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig4.json b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig4.json new file mode 100644 index 0000000..0ba79b7 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig4.json @@ -0,0 +1,14472 @@ +{ + "nodes": [ + { + "id": "BatteryStation", + "name": "扣电工作站", + "children": [ + "coin_cell_deck" + ], + "parent": null, + "type": "device", + "class": "bettery_station_registry", + "position": { + "x": 600, + "y": 400, + "z": 0 + }, + "config": { + "debug_mode": false, + "_comment": "protocol_type接外部工站固定写法字段,一般为空,station_resource写法也固定", + "protocol_type": [], + "station_resource": { + "data": { + "_resource_child_name": "coin_cell_deck", + "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" + } + }, + + "address": "192.168.1.20", + "port": 502 + }, + "data": {} + }, + { + "id": "coin_cell_deck", + "name": "coin_cell_deck", + "sample_id": null, + "children": [ + "zi_dan_jia", + "zi_dan_jia2", + "zi_dan_jia3", + "zi_dan_jia4", + "zi_dan_jia5", + "zi_dan_jia6", + "zi_dan_jia7", + "zi_dan_jia8", + "liaopan1", + "liaopan2", + "liaopan3", + "liaopan4", + "liaopan5", + "liaopan6", + "bottle_rack_3x4", + "bottle_rack_6x2", + "bottle_rack_6x2_2", + "tip_box_64", + "waste_tip_box" + ], + "parent": null, + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "CoincellDeck", + "size_x": 1620.0, + "size_y": 1270.0, + "size_z": 500.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "coin_cell_deck", + "barcode": null + }, + "data": {} + }, + { + "id": "zi_dan_jia", + "name": "zi_dan_jia", + "sample_id": null, + "children": [ + "zi_dan_jia_clipmagazinehole_0_0", + "zi_dan_jia_clipmagazinehole_0_1", + "zi_dan_jia_clipmagazinehole_1_0", + "zi_dan_jia_clipmagazinehole_1_1" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1400, + "y": 50, + "z": 0 + }, + "config": { + "type": "ClipMagazine_four", + "size_x": 80, + "size_y": 80, + "size_z": 10, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_four", + "model": null, + "barcode": null, + "ordering": { + "A1": "zi_dan_jia_clipmagazinehole_0_0", + "B1": "zi_dan_jia_clipmagazinehole_0_1", + "A2": "zi_dan_jia_clipmagazinehole_1_0", + "B2": "zi_dan_jia_clipmagazinehole_1_1" + }, + "hole_diameter": 14.0, + "hole_depth": 10.0, + "max_sheets_per_hole": 100 + }, + "data": {} + }, + { + "id": "zi_dan_jia_clipmagazinehole_0_0", + "name": "zi_dan_jia_clipmagazinehole_0_0", + "sample_id": null, + "children": [ + "zi_dan_jia2_jipian_0" + ], + "parent": "zi_dan_jia", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia2_jipian_0", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia_clipmagazinehole_0_0" + } + ] + } + }, + { + "id": "zi_dan_jia2_jipian_0", + "name": "zi_dan_jia2_jipian_0", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia_clipmagazinehole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia_clipmagazinehole_0_1", + "name": "zi_dan_jia_clipmagazinehole_0_1", + "sample_id": null, + "children": [ + "zi_dan_jia2_jipian_1" + ], + "parent": "zi_dan_jia", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia2_jipian_1", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia_clipmagazinehole_0_1" + } + ] + } + }, + { + "id": "zi_dan_jia2_jipian_1", + "name": "zi_dan_jia2_jipian_1", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia_clipmagazinehole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia_clipmagazinehole_1_0", + "name": "zi_dan_jia_clipmagazinehole_1_0", + "sample_id": null, + "children": [ + "zi_dan_jia2_jipian_2" + ], + "parent": "zi_dan_jia", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia2_jipian_2", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia_clipmagazinehole_1_0" + } + ] + } + }, + { + "id": "zi_dan_jia2_jipian_2", + "name": "zi_dan_jia2_jipian_2", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia_clipmagazinehole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia_clipmagazinehole_1_1", + "name": "zi_dan_jia_clipmagazinehole_1_1", + "sample_id": null, + "children": [ + "zi_dan_jia2_jipian_3" + ], + "parent": "zi_dan_jia", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia2_jipian_3", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia_clipmagazinehole_1_1" + } + ] + } + }, + { + "id": "zi_dan_jia2_jipian_3", + "name": "zi_dan_jia2_jipian_3", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia_clipmagazinehole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia2", + "name": "zi_dan_jia2", + "sample_id": null, + "children": [ + "zi_dan_jia2_clipmagazinehole_0_0", + "zi_dan_jia2_clipmagazinehole_0_1", + "zi_dan_jia2_clipmagazinehole_1_0", + "zi_dan_jia2_clipmagazinehole_1_1" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1600, + "y": 200, + "z": 0 + }, + "config": { + "type": "ClipMagazine_four", + "size_x": 80, + "size_y": 80, + "size_z": 10, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_four", + "model": null, + "barcode": null, + "ordering": { + "A1": "zi_dan_jia2_clipmagazinehole_0_0", + "B1": "zi_dan_jia2_clipmagazinehole_0_1", + "A2": "zi_dan_jia2_clipmagazinehole_1_0", + "B2": "zi_dan_jia2_clipmagazinehole_1_1" + }, + "hole_diameter": 14.0, + "hole_depth": 10.0, + "max_sheets_per_hole": 100 + }, + "data": {} + }, + { + "id": "zi_dan_jia2_clipmagazinehole_0_0", + "name": "zi_dan_jia2_clipmagazinehole_0_0", + "sample_id": null, + "children": [ + "zi_dan_jia_jipian_0" + ], + "parent": "zi_dan_jia2", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia_jipian_0", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia2_clipmagazinehole_0_0" + } + ] + } + }, + { + "id": "zi_dan_jia_jipian_0", + "name": "zi_dan_jia_jipian_0", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia2_clipmagazinehole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia2_clipmagazinehole_0_1", + "name": "zi_dan_jia2_clipmagazinehole_0_1", + "sample_id": null, + "children": [ + "zi_dan_jia_jipian_1" + ], + "parent": "zi_dan_jia2", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia_jipian_1", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia2_clipmagazinehole_0_1" + } + ] + } + }, + { + "id": "zi_dan_jia_jipian_1", + "name": "zi_dan_jia_jipian_1", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia2_clipmagazinehole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia2_clipmagazinehole_1_0", + "name": "zi_dan_jia2_clipmagazinehole_1_0", + "sample_id": null, + "children": [ + "zi_dan_jia_jipian_2" + ], + "parent": "zi_dan_jia2", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia_jipian_2", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia2_clipmagazinehole_1_0" + } + ] + } + }, + { + "id": "zi_dan_jia_jipian_2", + "name": "zi_dan_jia_jipian_2", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia2_clipmagazinehole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia2_clipmagazinehole_1_1", + "name": "zi_dan_jia2_clipmagazinehole_1_1", + "sample_id": null, + "children": [ + "zi_dan_jia_jipian_3" + ], + "parent": "zi_dan_jia2", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia_jipian_3", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia2_clipmagazinehole_1_1" + } + ] + } + }, + { + "id": "zi_dan_jia_jipian_3", + "name": "zi_dan_jia_jipian_3", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia2_clipmagazinehole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia3", + "name": "zi_dan_jia3", + "sample_id": null, + "children": [ + "zi_dan_jia3_clipmagazinehole_0_0", + "zi_dan_jia3_clipmagazinehole_0_1", + "zi_dan_jia3_clipmagazinehole_1_0", + "zi_dan_jia3_clipmagazinehole_1_1", + "zi_dan_jia3_clipmagazinehole_2_0", + "zi_dan_jia3_clipmagazinehole_2_1" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1500, + "y": 200, + "z": 0 + }, + "config": { + "type": "ClipMagazine", + "size_x": 80, + "size_y": 80, + "size_z": 10, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine", + "model": null, + "barcode": null, + "ordering": { + "A1": "zi_dan_jia3_clipmagazinehole_0_0", + "B1": "zi_dan_jia3_clipmagazinehole_0_1", + "A2": "zi_dan_jia3_clipmagazinehole_1_0", + "B2": "zi_dan_jia3_clipmagazinehole_1_1", + "A3": "zi_dan_jia3_clipmagazinehole_2_0", + "B3": "zi_dan_jia3_clipmagazinehole_2_1" + }, + "hole_diameter": 14.0, + "hole_depth": 10.0, + "max_sheets_per_hole": 100 + }, + "data": {} + }, + { + "id": "zi_dan_jia3_clipmagazinehole_0_0", + "name": "zi_dan_jia3_clipmagazinehole_0_0", + "sample_id": null, + "children": [ + "zi_dan_jia3_jipian_0" + ], + "parent": "zi_dan_jia3", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia3_jipian_0", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia3_clipmagazinehole_0_0" + } + ] + } + }, + { + "id": "zi_dan_jia3_jipian_0", + "name": "zi_dan_jia3_jipian_0", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia3_clipmagazinehole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia3_clipmagazinehole_0_1", + "name": "zi_dan_jia3_clipmagazinehole_0_1", + "sample_id": null, + "children": [ + "zi_dan_jia3_jipian_1" + ], + "parent": "zi_dan_jia3", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia3_jipian_1", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia3_clipmagazinehole_0_1" + } + ] + } + }, + { + "id": "zi_dan_jia3_jipian_1", + "name": "zi_dan_jia3_jipian_1", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia3_clipmagazinehole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia3_clipmagazinehole_1_0", + "name": "zi_dan_jia3_clipmagazinehole_1_0", + "sample_id": null, + "children": [ + "zi_dan_jia3_jipian_2" + ], + "parent": "zi_dan_jia3", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia3_jipian_2", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia3_clipmagazinehole_1_0" + } + ] + } + }, + { + "id": "zi_dan_jia3_jipian_2", + "name": "zi_dan_jia3_jipian_2", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia3_clipmagazinehole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia3_clipmagazinehole_1_1", + "name": "zi_dan_jia3_clipmagazinehole_1_1", + "sample_id": null, + "children": [ + "zi_dan_jia3_jipian_3" + ], + "parent": "zi_dan_jia3", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia3_jipian_3", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia3_clipmagazinehole_1_1" + } + ] + } + }, + { + "id": "zi_dan_jia3_jipian_3", + "name": "zi_dan_jia3_jipian_3", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia3_clipmagazinehole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia3_clipmagazinehole_2_0", + "name": "zi_dan_jia3_clipmagazinehole_2_0", + "sample_id": null, + "children": [ + "zi_dan_jia3_jipian_4" + ], + "parent": "zi_dan_jia3", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia3_jipian_4", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia3_clipmagazinehole_2_0" + } + ] + } + }, + { + "id": "zi_dan_jia3_jipian_4", + "name": "zi_dan_jia3_jipian_4", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia3_clipmagazinehole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia3_clipmagazinehole_2_1", + "name": "zi_dan_jia3_clipmagazinehole_2_1", + "sample_id": null, + "children": [ + "zi_dan_jia3_jipian_5" + ], + "parent": "zi_dan_jia3", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia3_jipian_5", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia3_clipmagazinehole_2_1" + } + ] + } + }, + { + "id": "zi_dan_jia3_jipian_5", + "name": "zi_dan_jia3_jipian_5", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia3_clipmagazinehole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia4", + "name": "zi_dan_jia4", + "sample_id": null, + "children": [ + "zi_dan_jia4_clipmagazinehole_0_0", + "zi_dan_jia4_clipmagazinehole_0_1", + "zi_dan_jia4_clipmagazinehole_1_0", + "zi_dan_jia4_clipmagazinehole_1_1", + "zi_dan_jia4_clipmagazinehole_2_0", + "zi_dan_jia4_clipmagazinehole_2_1" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1500, + "y": 300, + "z": 0 + }, + "config": { + "type": "ClipMagazine", + "size_x": 80, + "size_y": 80, + "size_z": 10, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine", + "model": null, + "barcode": null, + "ordering": { + "A1": "zi_dan_jia4_clipmagazinehole_0_0", + "B1": "zi_dan_jia4_clipmagazinehole_0_1", + "A2": "zi_dan_jia4_clipmagazinehole_1_0", + "B2": "zi_dan_jia4_clipmagazinehole_1_1", + "A3": "zi_dan_jia4_clipmagazinehole_2_0", + "B3": "zi_dan_jia4_clipmagazinehole_2_1" + }, + "hole_diameter": 14.0, + "hole_depth": 10.0, + "max_sheets_per_hole": 100 + }, + "data": {} + }, + { + "id": "zi_dan_jia4_clipmagazinehole_0_0", + "name": "zi_dan_jia4_clipmagazinehole_0_0", + "sample_id": null, + "children": [ + "zi_dan_jia4_jipian_0" + ], + "parent": "zi_dan_jia4", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia4_jipian_0", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia4_clipmagazinehole_0_0" + } + ] + } + }, + { + "id": "zi_dan_jia4_jipian_0", + "name": "zi_dan_jia4_jipian_0", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia4_clipmagazinehole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia4_clipmagazinehole_0_1", + "name": "zi_dan_jia4_clipmagazinehole_0_1", + "sample_id": null, + "children": [ + "zi_dan_jia4_jipian_1" + ], + "parent": "zi_dan_jia4", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia4_jipian_1", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia4_clipmagazinehole_0_1" + } + ] + } + }, + { + "id": "zi_dan_jia4_jipian_1", + "name": "zi_dan_jia4_jipian_1", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia4_clipmagazinehole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia4_clipmagazinehole_1_0", + "name": "zi_dan_jia4_clipmagazinehole_1_0", + "sample_id": null, + "children": [ + "zi_dan_jia4_jipian_2" + ], + "parent": "zi_dan_jia4", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia4_jipian_2", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia4_clipmagazinehole_1_0" + } + ] + } + }, + { + "id": "zi_dan_jia4_jipian_2", + "name": "zi_dan_jia4_jipian_2", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia4_clipmagazinehole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia4_clipmagazinehole_1_1", + "name": "zi_dan_jia4_clipmagazinehole_1_1", + "sample_id": null, + "children": [ + "zi_dan_jia4_jipian_3" + ], + "parent": "zi_dan_jia4", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia4_jipian_3", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia4_clipmagazinehole_1_1" + } + ] + } + }, + { + "id": "zi_dan_jia4_jipian_3", + "name": "zi_dan_jia4_jipian_3", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia4_clipmagazinehole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia4_clipmagazinehole_2_0", + "name": "zi_dan_jia4_clipmagazinehole_2_0", + "sample_id": null, + "children": [ + "zi_dan_jia4_jipian_4" + ], + "parent": "zi_dan_jia4", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia4_jipian_4", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia4_clipmagazinehole_2_0" + } + ] + } + }, + { + "id": "zi_dan_jia4_jipian_4", + "name": "zi_dan_jia4_jipian_4", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia4_clipmagazinehole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia4_clipmagazinehole_2_1", + "name": "zi_dan_jia4_clipmagazinehole_2_1", + "sample_id": null, + "children": [ + "zi_dan_jia4_jipian_5" + ], + "parent": "zi_dan_jia4", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia4_jipian_5", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia4_clipmagazinehole_2_1" + } + ] + } + }, + { + "id": "zi_dan_jia4_jipian_5", + "name": "zi_dan_jia4_jipian_5", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia4_clipmagazinehole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia5", + "name": "zi_dan_jia5", + "sample_id": null, + "children": [ + "zi_dan_jia5_clipmagazinehole_0_0", + "zi_dan_jia5_clipmagazinehole_0_1", + "zi_dan_jia5_clipmagazinehole_1_0", + "zi_dan_jia5_clipmagazinehole_1_1", + "zi_dan_jia5_clipmagazinehole_2_0", + "zi_dan_jia5_clipmagazinehole_2_1" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1600, + "y": 300, + "z": 0 + }, + "config": { + "type": "ClipMagazine", + "size_x": 80, + "size_y": 80, + "size_z": 10, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine", + "model": null, + "barcode": null, + "ordering": { + "A1": "zi_dan_jia5_clipmagazinehole_0_0", + "B1": "zi_dan_jia5_clipmagazinehole_0_1", + "A2": "zi_dan_jia5_clipmagazinehole_1_0", + "B2": "zi_dan_jia5_clipmagazinehole_1_1", + "A3": "zi_dan_jia5_clipmagazinehole_2_0", + "B3": "zi_dan_jia5_clipmagazinehole_2_1" + }, + "hole_diameter": 14.0, + "hole_depth": 10.0, + "max_sheets_per_hole": 100 + }, + "data": {} + }, + { + "id": "zi_dan_jia5_clipmagazinehole_0_0", + "name": "zi_dan_jia5_clipmagazinehole_0_0", + "sample_id": null, + "children": [ + "zi_dan_jia5_jipian_0" + ], + "parent": "zi_dan_jia5", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia5_jipian_0", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia5_clipmagazinehole_0_0" + } + ] + } + }, + { + "id": "zi_dan_jia5_jipian_0", + "name": "zi_dan_jia5_jipian_0", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia5_clipmagazinehole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia5_clipmagazinehole_0_1", + "name": "zi_dan_jia5_clipmagazinehole_0_1", + "sample_id": null, + "children": [ + "zi_dan_jia5_jipian_1" + ], + "parent": "zi_dan_jia5", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia5_jipian_1", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia5_clipmagazinehole_0_1" + } + ] + } + }, + { + "id": "zi_dan_jia5_jipian_1", + "name": "zi_dan_jia5_jipian_1", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia5_clipmagazinehole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia5_clipmagazinehole_1_0", + "name": "zi_dan_jia5_clipmagazinehole_1_0", + "sample_id": null, + "children": [ + "zi_dan_jia5_jipian_2" + ], + "parent": "zi_dan_jia5", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia5_jipian_2", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia5_clipmagazinehole_1_0" + } + ] + } + }, + { + "id": "zi_dan_jia5_jipian_2", + "name": "zi_dan_jia5_jipian_2", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia5_clipmagazinehole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia5_clipmagazinehole_1_1", + "name": "zi_dan_jia5_clipmagazinehole_1_1", + "sample_id": null, + "children": [ + "zi_dan_jia5_jipian_3" + ], + "parent": "zi_dan_jia5", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia5_jipian_3", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia5_clipmagazinehole_1_1" + } + ] + } + }, + { + "id": "zi_dan_jia5_jipian_3", + "name": "zi_dan_jia5_jipian_3", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia5_clipmagazinehole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia5_clipmagazinehole_2_0", + "name": "zi_dan_jia5_clipmagazinehole_2_0", + "sample_id": null, + "children": [ + "zi_dan_jia5_jipian_4" + ], + "parent": "zi_dan_jia5", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia5_jipian_4", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia5_clipmagazinehole_2_0" + } + ] + } + }, + { + "id": "zi_dan_jia5_jipian_4", + "name": "zi_dan_jia5_jipian_4", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia5_clipmagazinehole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia5_clipmagazinehole_2_1", + "name": "zi_dan_jia5_clipmagazinehole_2_1", + "sample_id": null, + "children": [ + "zi_dan_jia5_jipian_5" + ], + "parent": "zi_dan_jia5", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia5_jipian_5", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia5_clipmagazinehole_2_1" + } + ] + } + }, + { + "id": "zi_dan_jia5_jipian_5", + "name": "zi_dan_jia5_jipian_5", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia5_clipmagazinehole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia6", + "name": "zi_dan_jia6", + "sample_id": null, + "children": [ + "zi_dan_jia6_clipmagazinehole_0_0", + "zi_dan_jia6_clipmagazinehole_0_1", + "zi_dan_jia6_clipmagazinehole_1_0", + "zi_dan_jia6_clipmagazinehole_1_1", + "zi_dan_jia6_clipmagazinehole_2_0", + "zi_dan_jia6_clipmagazinehole_2_1" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1530, + "y": 500, + "z": 0 + }, + "config": { + "type": "ClipMagazine", + "size_x": 80, + "size_y": 80, + "size_z": 10, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine", + "model": null, + "barcode": null, + "ordering": { + "A1": "zi_dan_jia6_clipmagazinehole_0_0", + "B1": "zi_dan_jia6_clipmagazinehole_0_1", + "A2": "zi_dan_jia6_clipmagazinehole_1_0", + "B2": "zi_dan_jia6_clipmagazinehole_1_1", + "A3": "zi_dan_jia6_clipmagazinehole_2_0", + "B3": "zi_dan_jia6_clipmagazinehole_2_1" + }, + "hole_diameter": 14.0, + "hole_depth": 10.0, + "max_sheets_per_hole": 100 + }, + "data": {} + }, + { + "id": "zi_dan_jia6_clipmagazinehole_0_0", + "name": "zi_dan_jia6_clipmagazinehole_0_0", + "sample_id": null, + "children": [ + "zi_dan_jia6_jipian_0" + ], + "parent": "zi_dan_jia6", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia6_jipian_0", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia6_clipmagazinehole_0_0" + } + ] + } + }, + { + "id": "zi_dan_jia6_jipian_0", + "name": "zi_dan_jia6_jipian_0", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia6_clipmagazinehole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia6_clipmagazinehole_0_1", + "name": "zi_dan_jia6_clipmagazinehole_0_1", + "sample_id": null, + "children": [ + "zi_dan_jia6_jipian_1" + ], + "parent": "zi_dan_jia6", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia6_jipian_1", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia6_clipmagazinehole_0_1" + } + ] + } + }, + { + "id": "zi_dan_jia6_jipian_1", + "name": "zi_dan_jia6_jipian_1", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia6_clipmagazinehole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia6_clipmagazinehole_1_0", + "name": "zi_dan_jia6_clipmagazinehole_1_0", + "sample_id": null, + "children": [ + "zi_dan_jia6_jipian_2" + ], + "parent": "zi_dan_jia6", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia6_jipian_2", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia6_clipmagazinehole_1_0" + } + ] + } + }, + { + "id": "zi_dan_jia6_jipian_2", + "name": "zi_dan_jia6_jipian_2", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia6_clipmagazinehole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia6_clipmagazinehole_1_1", + "name": "zi_dan_jia6_clipmagazinehole_1_1", + "sample_id": null, + "children": [ + "zi_dan_jia6_jipian_3" + ], + "parent": "zi_dan_jia6", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia6_jipian_3", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia6_clipmagazinehole_1_1" + } + ] + } + }, + { + "id": "zi_dan_jia6_jipian_3", + "name": "zi_dan_jia6_jipian_3", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia6_clipmagazinehole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia6_clipmagazinehole_2_0", + "name": "zi_dan_jia6_clipmagazinehole_2_0", + "sample_id": null, + "children": [ + "zi_dan_jia6_jipian_4" + ], + "parent": "zi_dan_jia6", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia6_jipian_4", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia6_clipmagazinehole_2_0" + } + ] + } + }, + { + "id": "zi_dan_jia6_jipian_4", + "name": "zi_dan_jia6_jipian_4", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia6_clipmagazinehole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia6_clipmagazinehole_2_1", + "name": "zi_dan_jia6_clipmagazinehole_2_1", + "sample_id": null, + "children": [ + "zi_dan_jia6_jipian_5" + ], + "parent": "zi_dan_jia6", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia6_jipian_5", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia6_clipmagazinehole_2_1" + } + ] + } + }, + { + "id": "zi_dan_jia6_jipian_5", + "name": "zi_dan_jia6_jipian_5", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia6_clipmagazinehole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia7", + "name": "zi_dan_jia7", + "sample_id": null, + "children": [ + "zi_dan_jia7_clipmagazinehole_0_0", + "zi_dan_jia7_clipmagazinehole_0_1", + "zi_dan_jia7_clipmagazinehole_1_0", + "zi_dan_jia7_clipmagazinehole_1_1", + "zi_dan_jia7_clipmagazinehole_2_0", + "zi_dan_jia7_clipmagazinehole_2_1" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1180, + "y": 400, + "z": 0 + }, + "config": { + "type": "ClipMagazine", + "size_x": 80, + "size_y": 80, + "size_z": 10, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine", + "model": null, + "barcode": null, + "ordering": { + "A1": "zi_dan_jia7_clipmagazinehole_0_0", + "B1": "zi_dan_jia7_clipmagazinehole_0_1", + "A2": "zi_dan_jia7_clipmagazinehole_1_0", + "B2": "zi_dan_jia7_clipmagazinehole_1_1", + "A3": "zi_dan_jia7_clipmagazinehole_2_0", + "B3": "zi_dan_jia7_clipmagazinehole_2_1" + }, + "hole_diameter": 14.0, + "hole_depth": 10.0, + "max_sheets_per_hole": 100 + }, + "data": {} + }, + { + "id": "zi_dan_jia7_clipmagazinehole_0_0", + "name": "zi_dan_jia7_clipmagazinehole_0_0", + "sample_id": null, + "children": [ + "zi_dan_jia7_jipian_0" + ], + "parent": "zi_dan_jia7", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia7_jipian_0", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia7_clipmagazinehole_0_0" + } + ] + } + }, + { + "id": "zi_dan_jia7_jipian_0", + "name": "zi_dan_jia7_jipian_0", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia7_clipmagazinehole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia7_clipmagazinehole_0_1", + "name": "zi_dan_jia7_clipmagazinehole_0_1", + "sample_id": null, + "children": [ + "zi_dan_jia7_jipian_1" + ], + "parent": "zi_dan_jia7", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia7_jipian_1", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia7_clipmagazinehole_0_1" + } + ] + } + }, + { + "id": "zi_dan_jia7_jipian_1", + "name": "zi_dan_jia7_jipian_1", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia7_clipmagazinehole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia7_clipmagazinehole_1_0", + "name": "zi_dan_jia7_clipmagazinehole_1_0", + "sample_id": null, + "children": [ + "zi_dan_jia7_jipian_2" + ], + "parent": "zi_dan_jia7", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia7_jipian_2", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia7_clipmagazinehole_1_0" + } + ] + } + }, + { + "id": "zi_dan_jia7_jipian_2", + "name": "zi_dan_jia7_jipian_2", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia7_clipmagazinehole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia7_clipmagazinehole_1_1", + "name": "zi_dan_jia7_clipmagazinehole_1_1", + "sample_id": null, + "children": [ + "zi_dan_jia7_jipian_3" + ], + "parent": "zi_dan_jia7", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia7_jipian_3", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia7_clipmagazinehole_1_1" + } + ] + } + }, + { + "id": "zi_dan_jia7_jipian_3", + "name": "zi_dan_jia7_jipian_3", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia7_clipmagazinehole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia7_clipmagazinehole_2_0", + "name": "zi_dan_jia7_clipmagazinehole_2_0", + "sample_id": null, + "children": [ + "zi_dan_jia7_jipian_4" + ], + "parent": "zi_dan_jia7", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia7_jipian_4", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia7_clipmagazinehole_2_0" + } + ] + } + }, + { + "id": "zi_dan_jia7_jipian_4", + "name": "zi_dan_jia7_jipian_4", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia7_clipmagazinehole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia7_clipmagazinehole_2_1", + "name": "zi_dan_jia7_clipmagazinehole_2_1", + "sample_id": null, + "children": [ + "zi_dan_jia7_jipian_5" + ], + "parent": "zi_dan_jia7", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia7_jipian_5", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia7_clipmagazinehole_2_1" + } + ] + } + }, + { + "id": "zi_dan_jia7_jipian_5", + "name": "zi_dan_jia7_jipian_5", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia7_clipmagazinehole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia8", + "name": "zi_dan_jia8", + "sample_id": null, + "children": [ + "zi_dan_jia8_clipmagazinehole_0_0", + "zi_dan_jia8_clipmagazinehole_0_1", + "zi_dan_jia8_clipmagazinehole_1_0", + "zi_dan_jia8_clipmagazinehole_1_1", + "zi_dan_jia8_clipmagazinehole_2_0", + "zi_dan_jia8_clipmagazinehole_2_1" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1280, + "y": 400, + "z": 0 + }, + "config": { + "type": "ClipMagazine", + "size_x": 80, + "size_y": 80, + "size_z": 10, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine", + "model": null, + "barcode": null, + "ordering": { + "A1": "zi_dan_jia8_clipmagazinehole_0_0", + "B1": "zi_dan_jia8_clipmagazinehole_0_1", + "A2": "zi_dan_jia8_clipmagazinehole_1_0", + "B2": "zi_dan_jia8_clipmagazinehole_1_1", + "A3": "zi_dan_jia8_clipmagazinehole_2_0", + "B3": "zi_dan_jia8_clipmagazinehole_2_1" + }, + "hole_diameter": 14.0, + "hole_depth": 10.0, + "max_sheets_per_hole": 100 + }, + "data": {} + }, + { + "id": "zi_dan_jia8_clipmagazinehole_0_0", + "name": "zi_dan_jia8_clipmagazinehole_0_0", + "sample_id": null, + "children": [ + "zi_dan_jia8_jipian_0" + ], + "parent": "zi_dan_jia8", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia8_jipian_0", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia8_clipmagazinehole_0_0" + } + ] + } + }, + { + "id": "zi_dan_jia8_jipian_0", + "name": "zi_dan_jia8_jipian_0", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia8_clipmagazinehole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia8_clipmagazinehole_0_1", + "name": "zi_dan_jia8_clipmagazinehole_0_1", + "sample_id": null, + "children": [ + "zi_dan_jia8_jipian_1" + ], + "parent": "zi_dan_jia8", + "type": "container", + "class": "", + "position": { + "x": 15.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia8_jipian_1", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia8_clipmagazinehole_0_1" + } + ] + } + }, + { + "id": "zi_dan_jia8_jipian_1", + "name": "zi_dan_jia8_jipian_1", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia8_clipmagazinehole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia8_clipmagazinehole_1_0", + "name": "zi_dan_jia8_clipmagazinehole_1_0", + "sample_id": null, + "children": [ + "zi_dan_jia8_jipian_2" + ], + "parent": "zi_dan_jia8", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia8_jipian_2", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia8_clipmagazinehole_1_0" + } + ] + } + }, + { + "id": "zi_dan_jia8_jipian_2", + "name": "zi_dan_jia8_jipian_2", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia8_clipmagazinehole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia8_clipmagazinehole_1_1", + "name": "zi_dan_jia8_clipmagazinehole_1_1", + "sample_id": null, + "children": [ + "zi_dan_jia8_jipian_3" + ], + "parent": "zi_dan_jia8", + "type": "container", + "class": "", + "position": { + "x": 40.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia8_jipian_3", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia8_clipmagazinehole_1_1" + } + ] + } + }, + { + "id": "zi_dan_jia8_jipian_3", + "name": "zi_dan_jia8_jipian_3", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia8_clipmagazinehole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia8_clipmagazinehole_2_0", + "name": "zi_dan_jia8_clipmagazinehole_2_0", + "sample_id": null, + "children": [ + "zi_dan_jia8_jipian_4" + ], + "parent": "zi_dan_jia8", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 52.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia8_jipian_4", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia8_clipmagazinehole_2_0" + } + ] + } + }, + { + "id": "zi_dan_jia8_jipian_4", + "name": "zi_dan_jia8_jipian_4", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia8_clipmagazinehole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "zi_dan_jia8_clipmagazinehole_2_1", + "name": "zi_dan_jia8_clipmagazinehole_2_1", + "sample_id": null, + "children": [ + "zi_dan_jia8_jipian_5" + ], + "parent": "zi_dan_jia8", + "type": "container", + "class": "", + "position": { + "x": 65.0, + "y": 27.5, + "z": 10 + }, + "config": { + "type": "ClipMagazineHole", + "size_x": 14.0, + "size_y": 14.0, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "clip_magazine_hole", + "model": null, + "barcode": null, + "max_volume": 1960.0, + "material_z_thickness": null, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "sheet_count": 1, + "sheets": [ + { + "name": "zi_dan_jia8_jipian_5", + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "location": null, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null, + "children": [], + "parent_name": "zi_dan_jia8_clipmagazinehole_2_1" + } + ] + } + }, + { + "id": "zi_dan_jia8_jipian_5", + "name": "zi_dan_jia8_jipian_5", + "sample_id": null, + "children": [], + "parent": "zi_dan_jia8_clipmagazinehole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1", + "name": "liaopan1", + "sample_id": null, + "children": [ + "liaopan1_materialhole_0_0", + "liaopan1_materialhole_0_1", + "liaopan1_materialhole_0_2", + "liaopan1_materialhole_0_3", + "liaopan1_materialhole_1_0", + "liaopan1_materialhole_1_1", + "liaopan1_materialhole_1_2", + "liaopan1_materialhole_1_3", + "liaopan1_materialhole_2_0", + "liaopan1_materialhole_2_1", + "liaopan1_materialhole_2_2", + "liaopan1_materialhole_2_3", + "liaopan1_materialhole_3_0", + "liaopan1_materialhole_3_1", + "liaopan1_materialhole_3_2", + "liaopan1_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1010, + "y": 50, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120, + "size_y": 100, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan1_materialhole_0_0", + "B1": "liaopan1_materialhole_0_1", + "C1": "liaopan1_materialhole_0_2", + "D1": "liaopan1_materialhole_0_3", + "A2": "liaopan1_materialhole_1_0", + "B2": "liaopan1_materialhole_1_1", + "C2": "liaopan1_materialhole_1_2", + "D2": "liaopan1_materialhole_1_3", + "A3": "liaopan1_materialhole_2_0", + "B3": "liaopan1_materialhole_2_1", + "C3": "liaopan1_materialhole_2_2", + "D3": "liaopan1_materialhole_2_3", + "A4": "liaopan1_materialhole_3_0", + "B4": "liaopan1_materialhole_3_1", + "C4": "liaopan1_materialhole_3_2", + "D4": "liaopan1_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan1_materialhole_0_0", + "name": "liaopan1_materialhole_0_0", + "sample_id": null, + "children": [ + "liaopan1_jipian_0" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_0", + "name": "liaopan1_jipian_0", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_1", + "name": "liaopan1_materialhole_0_1", + "sample_id": null, + "children": [ + "liaopan1_jipian_1" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_1", + "name": "liaopan1_jipian_1", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_2", + "name": "liaopan1_materialhole_0_2", + "sample_id": null, + "children": [ + "liaopan1_jipian_2" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_2", + "name": "liaopan1_jipian_2", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_3", + "name": "liaopan1_materialhole_0_3", + "sample_id": null, + "children": [ + "liaopan1_jipian_3" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_3", + "name": "liaopan1_jipian_3", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_0", + "name": "liaopan1_materialhole_1_0", + "sample_id": null, + "children": [ + "liaopan1_jipian_4" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_4", + "name": "liaopan1_jipian_4", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_1", + "name": "liaopan1_materialhole_1_1", + "sample_id": null, + "children": [ + "liaopan1_jipian_5" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_5", + "name": "liaopan1_jipian_5", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_2", + "name": "liaopan1_materialhole_1_2", + "sample_id": null, + "children": [ + "liaopan1_jipian_6" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_6", + "name": "liaopan1_jipian_6", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_3", + "name": "liaopan1_materialhole_1_3", + "sample_id": null, + "children": [ + "liaopan1_jipian_7" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_7", + "name": "liaopan1_jipian_7", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_0", + "name": "liaopan1_materialhole_2_0", + "sample_id": null, + "children": [ + "liaopan1_jipian_8" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_8", + "name": "liaopan1_jipian_8", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_1", + "name": "liaopan1_materialhole_2_1", + "sample_id": null, + "children": [ + "liaopan1_jipian_9" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_9", + "name": "liaopan1_jipian_9", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_2", + "name": "liaopan1_materialhole_2_2", + "sample_id": null, + "children": [ + "liaopan1_jipian_10" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_10", + "name": "liaopan1_jipian_10", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_3", + "name": "liaopan1_materialhole_2_3", + "sample_id": null, + "children": [ + "liaopan1_jipian_11" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_11", + "name": "liaopan1_jipian_11", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_0", + "name": "liaopan1_materialhole_3_0", + "sample_id": null, + "children": [ + "liaopan1_jipian_12" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_12", + "name": "liaopan1_jipian_12", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_1", + "name": "liaopan1_materialhole_3_1", + "sample_id": null, + "children": [ + "liaopan1_jipian_13" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_13", + "name": "liaopan1_jipian_13", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_2", + "name": "liaopan1_materialhole_3_2", + "sample_id": null, + "children": [ + "liaopan1_jipian_14" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_14", + "name": "liaopan1_jipian_14", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_3", + "name": "liaopan1_materialhole_3_3", + "sample_id": null, + "children": [ + "liaopan1_jipian_15" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan1_jipian_15", + "name": "liaopan1_jipian_15", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan2", + "name": "liaopan2", + "sample_id": null, + "children": [ + "liaopan2_materialhole_0_0", + "liaopan2_materialhole_0_1", + "liaopan2_materialhole_0_2", + "liaopan2_materialhole_0_3", + "liaopan2_materialhole_1_0", + "liaopan2_materialhole_1_1", + "liaopan2_materialhole_1_2", + "liaopan2_materialhole_1_3", + "liaopan2_materialhole_2_0", + "liaopan2_materialhole_2_1", + "liaopan2_materialhole_2_2", + "liaopan2_materialhole_2_3", + "liaopan2_materialhole_3_0", + "liaopan2_materialhole_3_1", + "liaopan2_materialhole_3_2", + "liaopan2_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1130, + "y": 50, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120, + "size_y": 100, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan2_materialhole_0_0", + "B1": "liaopan2_materialhole_0_1", + "C1": "liaopan2_materialhole_0_2", + "D1": "liaopan2_materialhole_0_3", + "A2": "liaopan2_materialhole_1_0", + "B2": "liaopan2_materialhole_1_1", + "C2": "liaopan2_materialhole_1_2", + "D2": "liaopan2_materialhole_1_3", + "A3": "liaopan2_materialhole_2_0", + "B3": "liaopan2_materialhole_2_1", + "C3": "liaopan2_materialhole_2_2", + "D3": "liaopan2_materialhole_2_3", + "A4": "liaopan2_materialhole_3_0", + "B4": "liaopan2_materialhole_3_1", + "C4": "liaopan2_materialhole_3_2", + "D4": "liaopan2_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan2_materialhole_0_0", + "name": "liaopan2_materialhole_0_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_1", + "name": "liaopan2_materialhole_0_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_2", + "name": "liaopan2_materialhole_0_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_3", + "name": "liaopan2_materialhole_0_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_0", + "name": "liaopan2_materialhole_1_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_1", + "name": "liaopan2_materialhole_1_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_2", + "name": "liaopan2_materialhole_1_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_3", + "name": "liaopan2_materialhole_1_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_0", + "name": "liaopan2_materialhole_2_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_1", + "name": "liaopan2_materialhole_2_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_2", + "name": "liaopan2_materialhole_2_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_3", + "name": "liaopan2_materialhole_2_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_0", + "name": "liaopan2_materialhole_3_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_1", + "name": "liaopan2_materialhole_3_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_2", + "name": "liaopan2_materialhole_3_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_3", + "name": "liaopan2_materialhole_3_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3", + "name": "liaopan3", + "sample_id": null, + "children": [ + "liaopan3_materialhole_0_0", + "liaopan3_materialhole_0_1", + "liaopan3_materialhole_0_2", + "liaopan3_materialhole_0_3", + "liaopan3_materialhole_1_0", + "liaopan3_materialhole_1_1", + "liaopan3_materialhole_1_2", + "liaopan3_materialhole_1_3", + "liaopan3_materialhole_2_0", + "liaopan3_materialhole_2_1", + "liaopan3_materialhole_2_2", + "liaopan3_materialhole_2_3", + "liaopan3_materialhole_3_0", + "liaopan3_materialhole_3_1", + "liaopan3_materialhole_3_2", + "liaopan3_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1250, + "y": 50, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120, + "size_y": 100, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan3_materialhole_0_0", + "B1": "liaopan3_materialhole_0_1", + "C1": "liaopan3_materialhole_0_2", + "D1": "liaopan3_materialhole_0_3", + "A2": "liaopan3_materialhole_1_0", + "B2": "liaopan3_materialhole_1_1", + "C2": "liaopan3_materialhole_1_2", + "D2": "liaopan3_materialhole_1_3", + "A3": "liaopan3_materialhole_2_0", + "B3": "liaopan3_materialhole_2_1", + "C3": "liaopan3_materialhole_2_2", + "D3": "liaopan3_materialhole_2_3", + "A4": "liaopan3_materialhole_3_0", + "B4": "liaopan3_materialhole_3_1", + "C4": "liaopan3_materialhole_3_2", + "D4": "liaopan3_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan3_materialhole_0_0", + "name": "liaopan3_materialhole_0_0", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_0_1", + "name": "liaopan3_materialhole_0_1", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_0_2", + "name": "liaopan3_materialhole_0_2", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_0_3", + "name": "liaopan3_materialhole_0_3", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_1_0", + "name": "liaopan3_materialhole_1_0", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_1_1", + "name": "liaopan3_materialhole_1_1", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_1_2", + "name": "liaopan3_materialhole_1_2", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_1_3", + "name": "liaopan3_materialhole_1_3", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_2_0", + "name": "liaopan3_materialhole_2_0", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_2_1", + "name": "liaopan3_materialhole_2_1", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_2_2", + "name": "liaopan3_materialhole_2_2", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_2_3", + "name": "liaopan3_materialhole_2_3", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_3_0", + "name": "liaopan3_materialhole_3_0", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_3_1", + "name": "liaopan3_materialhole_3_1", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_3_2", + "name": "liaopan3_materialhole_3_2", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan3_materialhole_3_3", + "name": "liaopan3_materialhole_3_3", + "sample_id": null, + "children": [], + "parent": "liaopan3", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4", + "name": "liaopan4", + "sample_id": null, + "children": [ + "liaopan4_materialhole_0_0", + "liaopan4_materialhole_0_1", + "liaopan4_materialhole_0_2", + "liaopan4_materialhole_0_3", + "liaopan4_materialhole_1_0", + "liaopan4_materialhole_1_1", + "liaopan4_materialhole_1_2", + "liaopan4_materialhole_1_3", + "liaopan4_materialhole_2_0", + "liaopan4_materialhole_2_1", + "liaopan4_materialhole_2_2", + "liaopan4_materialhole_2_3", + "liaopan4_materialhole_3_0", + "liaopan4_materialhole_3_1", + "liaopan4_materialhole_3_2", + "liaopan4_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1010, + "y": 150, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120, + "size_y": 100, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan4_materialhole_0_0", + "B1": "liaopan4_materialhole_0_1", + "C1": "liaopan4_materialhole_0_2", + "D1": "liaopan4_materialhole_0_3", + "A2": "liaopan4_materialhole_1_0", + "B2": "liaopan4_materialhole_1_1", + "C2": "liaopan4_materialhole_1_2", + "D2": "liaopan4_materialhole_1_3", + "A3": "liaopan4_materialhole_2_0", + "B3": "liaopan4_materialhole_2_1", + "C3": "liaopan4_materialhole_2_2", + "D3": "liaopan4_materialhole_2_3", + "A4": "liaopan4_materialhole_3_0", + "B4": "liaopan4_materialhole_3_1", + "C4": "liaopan4_materialhole_3_2", + "D4": "liaopan4_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan4_materialhole_0_0", + "name": "liaopan4_materialhole_0_0", + "sample_id": null, + "children": [ + "liaopan4_jipian_0" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_0", + "name": "liaopan4_jipian_0", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_0_1", + "name": "liaopan4_materialhole_0_1", + "sample_id": null, + "children": [ + "liaopan4_jipian_1" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_1", + "name": "liaopan4_jipian_1", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_0_2", + "name": "liaopan4_materialhole_0_2", + "sample_id": null, + "children": [ + "liaopan4_jipian_2" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_2", + "name": "liaopan4_jipian_2", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_0_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_0_3", + "name": "liaopan4_materialhole_0_3", + "sample_id": null, + "children": [ + "liaopan4_jipian_3" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_3", + "name": "liaopan4_jipian_3", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_0_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_1_0", + "name": "liaopan4_materialhole_1_0", + "sample_id": null, + "children": [ + "liaopan4_jipian_4" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_4", + "name": "liaopan4_jipian_4", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_1_1", + "name": "liaopan4_materialhole_1_1", + "sample_id": null, + "children": [ + "liaopan4_jipian_5" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_5", + "name": "liaopan4_jipian_5", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_1_2", + "name": "liaopan4_materialhole_1_2", + "sample_id": null, + "children": [ + "liaopan4_jipian_6" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_6", + "name": "liaopan4_jipian_6", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_1_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_1_3", + "name": "liaopan4_materialhole_1_3", + "sample_id": null, + "children": [ + "liaopan4_jipian_7" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_7", + "name": "liaopan4_jipian_7", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_1_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_2_0", + "name": "liaopan4_materialhole_2_0", + "sample_id": null, + "children": [ + "liaopan4_jipian_8" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_8", + "name": "liaopan4_jipian_8", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_2_1", + "name": "liaopan4_materialhole_2_1", + "sample_id": null, + "children": [ + "liaopan4_jipian_9" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_9", + "name": "liaopan4_jipian_9", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_2_2", + "name": "liaopan4_materialhole_2_2", + "sample_id": null, + "children": [ + "liaopan4_jipian_10" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_10", + "name": "liaopan4_jipian_10", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_2_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_2_3", + "name": "liaopan4_materialhole_2_3", + "sample_id": null, + "children": [ + "liaopan4_jipian_11" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_11", + "name": "liaopan4_jipian_11", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_2_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_3_0", + "name": "liaopan4_materialhole_3_0", + "sample_id": null, + "children": [ + "liaopan4_jipian_12" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_12", + "name": "liaopan4_jipian_12", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_3_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_3_1", + "name": "liaopan4_materialhole_3_1", + "sample_id": null, + "children": [ + "liaopan4_jipian_13" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_13", + "name": "liaopan4_jipian_13", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_3_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_3_2", + "name": "liaopan4_materialhole_3_2", + "sample_id": null, + "children": [ + "liaopan4_jipian_14" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_14", + "name": "liaopan4_jipian_14", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_3_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan4_materialhole_3_3", + "name": "liaopan4_materialhole_3_3", + "sample_id": null, + "children": [ + "liaopan4_jipian_15" + ], + "parent": "liaopan4", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan4_jipian_15", + "name": "liaopan4_jipian_15", + "sample_id": null, + "children": [], + "parent": "liaopan4_materialhole_3_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan5", + "name": "liaopan5", + "sample_id": null, + "children": [ + "liaopan5_materialhole_0_0", + "liaopan5_materialhole_0_1", + "liaopan5_materialhole_0_2", + "liaopan5_materialhole_0_3", + "liaopan5_materialhole_1_0", + "liaopan5_materialhole_1_1", + "liaopan5_materialhole_1_2", + "liaopan5_materialhole_1_3", + "liaopan5_materialhole_2_0", + "liaopan5_materialhole_2_1", + "liaopan5_materialhole_2_2", + "liaopan5_materialhole_2_3", + "liaopan5_materialhole_3_0", + "liaopan5_materialhole_3_1", + "liaopan5_materialhole_3_2", + "liaopan5_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1130, + "y": 150, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120, + "size_y": 100, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan5_materialhole_0_0", + "B1": "liaopan5_materialhole_0_1", + "C1": "liaopan5_materialhole_0_2", + "D1": "liaopan5_materialhole_0_3", + "A2": "liaopan5_materialhole_1_0", + "B2": "liaopan5_materialhole_1_1", + "C2": "liaopan5_materialhole_1_2", + "D2": "liaopan5_materialhole_1_3", + "A3": "liaopan5_materialhole_2_0", + "B3": "liaopan5_materialhole_2_1", + "C3": "liaopan5_materialhole_2_2", + "D3": "liaopan5_materialhole_2_3", + "A4": "liaopan5_materialhole_3_0", + "B4": "liaopan5_materialhole_3_1", + "C4": "liaopan5_materialhole_3_2", + "D4": "liaopan5_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan5_materialhole_0_0", + "name": "liaopan5_materialhole_0_0", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_0_1", + "name": "liaopan5_materialhole_0_1", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_0_2", + "name": "liaopan5_materialhole_0_2", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_0_3", + "name": "liaopan5_materialhole_0_3", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_1_0", + "name": "liaopan5_materialhole_1_0", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_1_1", + "name": "liaopan5_materialhole_1_1", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_1_2", + "name": "liaopan5_materialhole_1_2", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_1_3", + "name": "liaopan5_materialhole_1_3", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_2_0", + "name": "liaopan5_materialhole_2_0", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_2_1", + "name": "liaopan5_materialhole_2_1", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_2_2", + "name": "liaopan5_materialhole_2_2", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_2_3", + "name": "liaopan5_materialhole_2_3", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_3_0", + "name": "liaopan5_materialhole_3_0", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_3_1", + "name": "liaopan5_materialhole_3_1", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_3_2", + "name": "liaopan5_materialhole_3_2", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan5_materialhole_3_3", + "name": "liaopan5_materialhole_3_3", + "sample_id": null, + "children": [], + "parent": "liaopan5", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6", + "name": "liaopan6", + "sample_id": null, + "children": [ + "liaopan6_materialhole_0_0", + "liaopan6_materialhole_0_1", + "liaopan6_materialhole_0_2", + "liaopan6_materialhole_0_3", + "liaopan6_materialhole_1_0", + "liaopan6_materialhole_1_1", + "liaopan6_materialhole_1_2", + "liaopan6_materialhole_1_3", + "liaopan6_materialhole_2_0", + "liaopan6_materialhole_2_1", + "liaopan6_materialhole_2_2", + "liaopan6_materialhole_2_3", + "liaopan6_materialhole_3_0", + "liaopan6_materialhole_3_1", + "liaopan6_materialhole_3_2", + "liaopan6_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 1250, + "y": 150, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120, + "size_y": 100, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan6_materialhole_0_0", + "B1": "liaopan6_materialhole_0_1", + "C1": "liaopan6_materialhole_0_2", + "D1": "liaopan6_materialhole_0_3", + "A2": "liaopan6_materialhole_1_0", + "B2": "liaopan6_materialhole_1_1", + "C2": "liaopan6_materialhole_1_2", + "D2": "liaopan6_materialhole_1_3", + "A3": "liaopan6_materialhole_2_0", + "B3": "liaopan6_materialhole_2_1", + "C3": "liaopan6_materialhole_2_2", + "D3": "liaopan6_materialhole_2_3", + "A4": "liaopan6_materialhole_3_0", + "B4": "liaopan6_materialhole_3_1", + "C4": "liaopan6_materialhole_3_2", + "D4": "liaopan6_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan6_materialhole_0_0", + "name": "liaopan6_materialhole_0_0", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_0_1", + "name": "liaopan6_materialhole_0_1", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_0_2", + "name": "liaopan6_materialhole_0_2", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_0_3", + "name": "liaopan6_materialhole_0_3", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 12.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_1_0", + "name": "liaopan6_materialhole_1_0", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_1_1", + "name": "liaopan6_materialhole_1_1", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_1_2", + "name": "liaopan6_materialhole_1_2", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_1_3", + "name": "liaopan6_materialhole_1_3", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 36.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_2_0", + "name": "liaopan6_materialhole_2_0", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_2_1", + "name": "liaopan6_materialhole_2_1", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_2_2", + "name": "liaopan6_materialhole_2_2", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_2_3", + "name": "liaopan6_materialhole_2_3", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 60.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_3_0", + "name": "liaopan6_materialhole_3_0", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 74.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_3_1", + "name": "liaopan6_materialhole_3_1", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 50.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_3_2", + "name": "liaopan6_materialhole_3_2", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 26.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan6_materialhole_3_3", + "name": "liaopan6_materialhole_3_3", + "sample_id": null, + "children": [], + "parent": "liaopan6", + "type": "container", + "class": "", + "position": { + "x": 84.0, + "y": 2.0, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "bottle_rack_3x4", + "name": "bottle_rack_3x4", + "sample_id": null, + "children": [ + "sheet_3x4_0", + "sheet_3x4_1", + "sheet_3x4_2", + "sheet_3x4_3", + "sheet_3x4_4", + "sheet_3x4_5", + "sheet_3x4_6", + "sheet_3x4_7", + "sheet_3x4_8", + "sheet_3x4_9", + "sheet_3x4_10", + "sheet_3x4_11" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 100, + "y": 200, + "z": 0 + }, + "config": { + "type": "BottleRack", + "size_x": 210.0, + "size_y": 140.0, + "size_z": 100.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "bottle_rack", + "model": null, + "barcode": null, + "num_items_x": 3, + "num_items_y": 4, + "position_spacing": 35.0, + "orientation": "vertical", + "padding_x": 20.0, + "padding_y": 20.0 + }, + "data": { + "bottle_diameter": 30.0, + "bottle_height": 100.0, + "position_spacing": 35.0, + "name_to_index": {} + } + }, + { + "id": "sheet_3x4_0", + "name": "sheet_3x4_0", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 20.0, + "y": 20.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_1", + "name": "sheet_3x4_1", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 20.0, + "y": 55.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_2", + "name": "sheet_3x4_2", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 20.0, + "y": 90.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_3", + "name": "sheet_3x4_3", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 55.0, + "y": 20.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_4", + "name": "sheet_3x4_4", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 55.0, + "y": 55.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_5", + "name": "sheet_3x4_5", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 55.0, + "y": 90.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_6", + "name": "sheet_3x4_6", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 90.0, + "y": 20.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_7", + "name": "sheet_3x4_7", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 90.0, + "y": 55.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_8", + "name": "sheet_3x4_8", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 90.0, + "y": 90.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_9", + "name": "sheet_3x4_9", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 125.0, + "y": 20.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_10", + "name": "sheet_3x4_10", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 125.0, + "y": 55.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_3x4_11", + "name": "sheet_3x4_11", + "sample_id": null, + "children": [], + "parent": "bottle_rack_3x4", + "type": "container", + "class": "", + "position": { + "x": 125.0, + "y": 90.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "bottle_rack_6x2", + "name": "bottle_rack_6x2", + "sample_id": null, + "children": [ + "sheet_6x2_0", + "sheet_6x2_1", + "sheet_6x2_2", + "sheet_6x2_3", + "sheet_6x2_4", + "sheet_6x2_5", + "sheet_6x2_6", + "sheet_6x2_7", + "sheet_6x2_8", + "sheet_6x2_9", + "sheet_6x2_10", + "sheet_6x2_11" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 300, + "y": 300, + "z": 0 + }, + "config": { + "type": "BottleRack", + "size_x": 120.0, + "size_y": 250.0, + "size_z": 100.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "bottle_rack", + "model": null, + "barcode": null, + "num_items_x": 6, + "num_items_y": 2, + "position_spacing": 35.0, + "orientation": "vertical", + "padding_x": 20.0, + "padding_y": 20.0 + }, + "data": { + "bottle_diameter": 30.0, + "bottle_height": 100.0, + "position_spacing": 35.0, + "name_to_index": {} + } + }, + { + "id": "sheet_6x2_0", + "name": "sheet_6x2_0", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 20.0, + "y": 20.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_1", + "name": "sheet_6x2_1", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 20.0, + "y": 55.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_2", + "name": "sheet_6x2_2", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 20.0, + "y": 90.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_3", + "name": "sheet_6x2_3", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 20.0, + "y": 125.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_4", + "name": "sheet_6x2_4", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 20.0, + "y": 160.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_5", + "name": "sheet_6x2_5", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 20.0, + "y": 195.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_6", + "name": "sheet_6x2_6", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 55.0, + "y": 20.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_7", + "name": "sheet_6x2_7", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 55.0, + "y": 55.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_8", + "name": "sheet_6x2_8", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 55.0, + "y": 90.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_9", + "name": "sheet_6x2_9", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 55.0, + "y": 125.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_10", + "name": "sheet_6x2_10", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 55.0, + "y": 160.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "sheet_6x2_11", + "name": "sheet_6x2_11", + "sample_id": null, + "children": [], + "parent": "bottle_rack_6x2", + "type": "container", + "class": "", + "position": { + "x": 55.0, + "y": 195.0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "bottle_rack_6x2_2", + "name": "bottle_rack_6x2_2", + "sample_id": null, + "children": [], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 430, + "y": 300, + "z": 0 + }, + "config": { + "type": "BottleRack", + "size_x": 120.0, + "size_y": 250.0, + "size_z": 100.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "bottle_rack", + "model": null, + "barcode": null, + "num_items_x": 6, + "num_items_y": 2, + "position_spacing": 35.0, + "orientation": "vertical", + "padding_x": 20.0, + "padding_y": 20.0 + }, + "data": { + "bottle_diameter": 30.0, + "bottle_height": 100.0, + "position_spacing": 35.0, + "name_to_index": {} + } + }, + { + "id": "tip_box_64", + "name": "tip_box_64", + "sample_id": null, + "children": [ + "tip_box_64_tipspot_0_0", + "tip_box_64_tipspot_0_1", + "tip_box_64_tipspot_0_2", + "tip_box_64_tipspot_0_3", + "tip_box_64_tipspot_0_4", + "tip_box_64_tipspot_0_5", + "tip_box_64_tipspot_0_6", + "tip_box_64_tipspot_0_7", + "tip_box_64_tipspot_1_0", + "tip_box_64_tipspot_1_1", + "tip_box_64_tipspot_1_2", + "tip_box_64_tipspot_1_3", + "tip_box_64_tipspot_1_4", + "tip_box_64_tipspot_1_5", + "tip_box_64_tipspot_1_6", + "tip_box_64_tipspot_1_7", + "tip_box_64_tipspot_2_0", + "tip_box_64_tipspot_2_1", + "tip_box_64_tipspot_2_2", + "tip_box_64_tipspot_2_3", + "tip_box_64_tipspot_2_4", + "tip_box_64_tipspot_2_5", + "tip_box_64_tipspot_2_6", + "tip_box_64_tipspot_2_7", + "tip_box_64_tipspot_3_0", + "tip_box_64_tipspot_3_1", + "tip_box_64_tipspot_3_2", + "tip_box_64_tipspot_3_3", + "tip_box_64_tipspot_3_4", + "tip_box_64_tipspot_3_5", + "tip_box_64_tipspot_3_6", + "tip_box_64_tipspot_3_7", + "tip_box_64_tipspot_4_0", + "tip_box_64_tipspot_4_1", + "tip_box_64_tipspot_4_2", + "tip_box_64_tipspot_4_3", + "tip_box_64_tipspot_4_4", + "tip_box_64_tipspot_4_5", + "tip_box_64_tipspot_4_6", + "tip_box_64_tipspot_4_7", + "tip_box_64_tipspot_5_0", + "tip_box_64_tipspot_5_1", + "tip_box_64_tipspot_5_2", + "tip_box_64_tipspot_5_3", + "tip_box_64_tipspot_5_4", + "tip_box_64_tipspot_5_5", + "tip_box_64_tipspot_5_6", + "tip_box_64_tipspot_5_7", + "tip_box_64_tipspot_6_0", + "tip_box_64_tipspot_6_1", + "tip_box_64_tipspot_6_2", + "tip_box_64_tipspot_6_3", + "tip_box_64_tipspot_6_4", + "tip_box_64_tipspot_6_5", + "tip_box_64_tipspot_6_6", + "tip_box_64_tipspot_6_7", + "tip_box_64_tipspot_7_0", + "tip_box_64_tipspot_7_1", + "tip_box_64_tipspot_7_2", + "tip_box_64_tipspot_7_3", + "tip_box_64_tipspot_7_4", + "tip_box_64_tipspot_7_5", + "tip_box_64_tipspot_7_6", + "tip_box_64_tipspot_7_7" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 300, + "y": 100, + "z": 0 + }, + "config": { + "type": "TipBox64", + "size_x": 127.8, + "size_y": 85.5, + "size_z": 60.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_box_64", + "model": null, + "barcode": null, + "ordering": { + "A1": "tip_box_64_tipspot_0_0", + "B1": "tip_box_64_tipspot_0_1", + "C1": "tip_box_64_tipspot_0_2", + "D1": "tip_box_64_tipspot_0_3", + "E1": "tip_box_64_tipspot_0_4", + "F1": "tip_box_64_tipspot_0_5", + "G1": "tip_box_64_tipspot_0_6", + "H1": "tip_box_64_tipspot_0_7", + "A2": "tip_box_64_tipspot_1_0", + "B2": "tip_box_64_tipspot_1_1", + "C2": "tip_box_64_tipspot_1_2", + "D2": "tip_box_64_tipspot_1_3", + "E2": "tip_box_64_tipspot_1_4", + "F2": "tip_box_64_tipspot_1_5", + "G2": "tip_box_64_tipspot_1_6", + "H2": "tip_box_64_tipspot_1_7", + "A3": "tip_box_64_tipspot_2_0", + "B3": "tip_box_64_tipspot_2_1", + "C3": "tip_box_64_tipspot_2_2", + "D3": "tip_box_64_tipspot_2_3", + "E3": "tip_box_64_tipspot_2_4", + "F3": "tip_box_64_tipspot_2_5", + "G3": "tip_box_64_tipspot_2_6", + "H3": "tip_box_64_tipspot_2_7", + "A4": "tip_box_64_tipspot_3_0", + "B4": "tip_box_64_tipspot_3_1", + "C4": "tip_box_64_tipspot_3_2", + "D4": "tip_box_64_tipspot_3_3", + "E4": "tip_box_64_tipspot_3_4", + "F4": "tip_box_64_tipspot_3_5", + "G4": "tip_box_64_tipspot_3_6", + "H4": "tip_box_64_tipspot_3_7", + "A5": "tip_box_64_tipspot_4_0", + "B5": "tip_box_64_tipspot_4_1", + "C5": "tip_box_64_tipspot_4_2", + "D5": "tip_box_64_tipspot_4_3", + "E5": "tip_box_64_tipspot_4_4", + "F5": "tip_box_64_tipspot_4_5", + "G5": "tip_box_64_tipspot_4_6", + "H5": "tip_box_64_tipspot_4_7", + "A6": "tip_box_64_tipspot_5_0", + "B6": "tip_box_64_tipspot_5_1", + "C6": "tip_box_64_tipspot_5_2", + "D6": "tip_box_64_tipspot_5_3", + "E6": "tip_box_64_tipspot_5_4", + "F6": "tip_box_64_tipspot_5_5", + "G6": "tip_box_64_tipspot_5_6", + "H6": "tip_box_64_tipspot_5_7", + "A7": "tip_box_64_tipspot_6_0", + "B7": "tip_box_64_tipspot_6_1", + "C7": "tip_box_64_tipspot_6_2", + "D7": "tip_box_64_tipspot_6_3", + "E7": "tip_box_64_tipspot_6_4", + "F7": "tip_box_64_tipspot_6_5", + "G7": "tip_box_64_tipspot_6_6", + "H7": "tip_box_64_tipspot_6_7", + "A8": "tip_box_64_tipspot_7_0", + "B8": "tip_box_64_tipspot_7_1", + "C8": "tip_box_64_tipspot_7_2", + "D8": "tip_box_64_tipspot_7_3", + "E8": "tip_box_64_tipspot_7_4", + "F8": "tip_box_64_tipspot_7_5", + "G8": "tip_box_64_tipspot_7_6", + "H8": "tip_box_64_tipspot_7_7" + }, + "num_items_x": 8, + "num_items_y": 8, + "dx": 8.0, + "dy": 8.0, + "item_dx": 9.0, + "item_dy": 9.0 + }, + "data": {} + }, + { + "id": "tip_box_64_tipspot_0_0", + "name": "tip_box_64_tipspot_0_0", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 8.0, + "y": 71.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_0_1", + "name": "tip_box_64_tipspot_0_1", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 8.0, + "y": 62.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_0_2", + "name": "tip_box_64_tipspot_0_2", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 8.0, + "y": 53.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_0_3", + "name": "tip_box_64_tipspot_0_3", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 8.0, + "y": 44.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_0_4", + "name": "tip_box_64_tipspot_0_4", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 8.0, + "y": 35.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_0_5", + "name": "tip_box_64_tipspot_0_5", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 8.0, + "y": 26.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_0_6", + "name": "tip_box_64_tipspot_0_6", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 8.0, + "y": 17.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_0_7", + "name": "tip_box_64_tipspot_0_7", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 8.0, + "y": 8.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_1_0", + "name": "tip_box_64_tipspot_1_0", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 17.0, + "y": 71.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_1_1", + "name": "tip_box_64_tipspot_1_1", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 17.0, + "y": 62.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_1_2", + "name": "tip_box_64_tipspot_1_2", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 17.0, + "y": 53.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_1_3", + "name": "tip_box_64_tipspot_1_3", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 17.0, + "y": 44.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_1_4", + "name": "tip_box_64_tipspot_1_4", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 17.0, + "y": 35.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_1_5", + "name": "tip_box_64_tipspot_1_5", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 17.0, + "y": 26.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_1_6", + "name": "tip_box_64_tipspot_1_6", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 17.0, + "y": 17.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_1_7", + "name": "tip_box_64_tipspot_1_7", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 17.0, + "y": 8.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_2_0", + "name": "tip_box_64_tipspot_2_0", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 26.0, + "y": 71.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_2_1", + "name": "tip_box_64_tipspot_2_1", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 26.0, + "y": 62.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_2_2", + "name": "tip_box_64_tipspot_2_2", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 26.0, + "y": 53.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_2_3", + "name": "tip_box_64_tipspot_2_3", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 26.0, + "y": 44.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_2_4", + "name": "tip_box_64_tipspot_2_4", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 26.0, + "y": 35.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_2_5", + "name": "tip_box_64_tipspot_2_5", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 26.0, + "y": 26.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_2_6", + "name": "tip_box_64_tipspot_2_6", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 26.0, + "y": 17.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_2_7", + "name": "tip_box_64_tipspot_2_7", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 26.0, + "y": 8.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_3_0", + "name": "tip_box_64_tipspot_3_0", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 35.0, + "y": 71.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_3_1", + "name": "tip_box_64_tipspot_3_1", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 35.0, + "y": 62.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_3_2", + "name": "tip_box_64_tipspot_3_2", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 35.0, + "y": 53.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_3_3", + "name": "tip_box_64_tipspot_3_3", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 35.0, + "y": 44.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_3_4", + "name": "tip_box_64_tipspot_3_4", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 35.0, + "y": 35.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_3_5", + "name": "tip_box_64_tipspot_3_5", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 35.0, + "y": 26.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_3_6", + "name": "tip_box_64_tipspot_3_6", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 35.0, + "y": 17.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_3_7", + "name": "tip_box_64_tipspot_3_7", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 35.0, + "y": 8.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_4_0", + "name": "tip_box_64_tipspot_4_0", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 44.0, + "y": 71.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_4_1", + "name": "tip_box_64_tipspot_4_1", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 44.0, + "y": 62.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_4_2", + "name": "tip_box_64_tipspot_4_2", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 44.0, + "y": 53.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_4_3", + "name": "tip_box_64_tipspot_4_3", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 44.0, + "y": 44.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_4_4", + "name": "tip_box_64_tipspot_4_4", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 44.0, + "y": 35.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_4_5", + "name": "tip_box_64_tipspot_4_5", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 44.0, + "y": 26.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_4_6", + "name": "tip_box_64_tipspot_4_6", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 44.0, + "y": 17.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_4_7", + "name": "tip_box_64_tipspot_4_7", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 44.0, + "y": 8.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_5_0", + "name": "tip_box_64_tipspot_5_0", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 53.0, + "y": 71.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_5_1", + "name": "tip_box_64_tipspot_5_1", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 53.0, + "y": 62.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_5_2", + "name": "tip_box_64_tipspot_5_2", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 53.0, + "y": 53.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_5_3", + "name": "tip_box_64_tipspot_5_3", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 53.0, + "y": 44.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_5_4", + "name": "tip_box_64_tipspot_5_4", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 53.0, + "y": 35.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_5_5", + "name": "tip_box_64_tipspot_5_5", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 53.0, + "y": 26.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_5_6", + "name": "tip_box_64_tipspot_5_6", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 53.0, + "y": 17.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_5_7", + "name": "tip_box_64_tipspot_5_7", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 53.0, + "y": 8.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_6_0", + "name": "tip_box_64_tipspot_6_0", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 62.0, + "y": 71.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_6_1", + "name": "tip_box_64_tipspot_6_1", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 62.0, + "y": 62.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_6_2", + "name": "tip_box_64_tipspot_6_2", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 62.0, + "y": 53.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_6_3", + "name": "tip_box_64_tipspot_6_3", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 62.0, + "y": 44.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_6_4", + "name": "tip_box_64_tipspot_6_4", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 62.0, + "y": 35.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_6_5", + "name": "tip_box_64_tipspot_6_5", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 62.0, + "y": 26.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_6_6", + "name": "tip_box_64_tipspot_6_6", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 62.0, + "y": 17.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_6_7", + "name": "tip_box_64_tipspot_6_7", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 62.0, + "y": 8.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_7_0", + "name": "tip_box_64_tipspot_7_0", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 71.0, + "y": 71.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_7_1", + "name": "tip_box_64_tipspot_7_1", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 71.0, + "y": 62.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_7_2", + "name": "tip_box_64_tipspot_7_2", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 71.0, + "y": 53.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_7_3", + "name": "tip_box_64_tipspot_7_3", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 71.0, + "y": 44.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_7_4", + "name": "tip_box_64_tipspot_7_4", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 71.0, + "y": 35.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_7_5", + "name": "tip_box_64_tipspot_7_5", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 71.0, + "y": 26.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_7_6", + "name": "tip_box_64_tipspot_7_6", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 71.0, + "y": 17.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "tip_box_64_tipspot_7_7", + "name": "tip_box_64_tipspot_7_7", + "sample_id": null, + "children": [], + "parent": "tip_box_64", + "type": "container", + "class": "", + "position": { + "x": 71.0, + "y": 8.0, + "z": 0.0 + }, + "config": { + "type": "TipSpot", + "size_x": 10, + "size_y": 10, + "size_z": 0.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "tip_spot", + "model": null, + "barcode": null, + "prototype_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + }, + "data": { + "tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + }, + "tip_state": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + }, + "pending_tip": { + "type": "Tip", + "total_tip_length": 20.0, + "has_filter": false, + "maximal_volume": 1000, + "fitting_depth": 8.0 + } + } + }, + { + "id": "waste_tip_box", + "name": "waste_tip_box", + "sample_id": null, + "children": [], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 300, + "y": 200, + "z": 0 + }, + "config": { + "type": "WasteTipBox", + "size_x": 127.8, + "size_y": 85.5, + "size_z": 60.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "waste_tip_box", + "model": null, + "barcode": null, + "max_volume": "Infinity", + "material_z_thickness": 0, + "compute_volume_from_height": null, + "compute_height_from_volume": null + }, + "data": { + "liquids": [], + "pending_liquids": [], + "liquid_history": [] + } + } + ], + "links": [] +} \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/test.ipynb b/unilabos/devices/workstation/coin_cell_assembly/test.ipynb new file mode 100644 index 0000000..76c07d7 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/test.ipynb @@ -0,0 +1,750 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 22, + "id": "80bc9500", + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import annotations\n", + "\n", + "from collections import OrderedDict\n", + "from typing import Any, Dict, List, Optional, TypedDict, Union, cast\n", + "\n", + "from pylabrobot.resources.coordinate import Coordinate\n", + "from pylabrobot.resources.container import Container\n", + "from pylabrobot.resources.deck import Deck\n", + "from pylabrobot.resources.itemized_resource import ItemizedResource\n", + "from pylabrobot.resources.resource import Resource\n", + "from pylabrobot.resources.resource_stack import ResourceStack\n", + "from pylabrobot.resources.tip_rack import TipRack, TipSpot\n", + "from pylabrobot.resources.trash import Trash\n", + "from pylabrobot.resources.utils import create_ordered_items_2d" + ] + }, + { + "cell_type": "markdown", + "id": "498a9159", + "metadata": {}, + "source": [ + "物料类型构建" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "f4a27241", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "class ElectrodeSheetState(TypedDict):\n", + " diameter: float # 直径 (mm)\n", + " thickness: float # 厚度 (mm)\n", + " mass: float # 质量 (g)\n", + " material_type: str # 材料类型(正极、负极、隔膜、弹片、垫片、铝箔等)\n", + " info: Optional[str] # 附加信息\n", + "\n", + "class ElectrodeSheet(Resource):\n", + " \"\"\"极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料\"\"\"\n", + "\n", + " def __init__(\n", + " self,\n", + " name: str = \"极片\",\n", + " size_x=10,\n", + " size_y=10,\n", + " size_z=10,\n", + " category: str = \"electrode_sheet\",\n", + " model: Optional[str] = None,\n", + " ):\n", + " \"\"\"初始化极片\n", + "\n", + " Args:\n", + " name: 极片名称\n", + " category: 类别\n", + " model: 型号\n", + " \"\"\"\n", + " super().__init__(\n", + " name=name,\n", + " size_x=size_x,\n", + " size_y=size_y,\n", + " size_z=size_z,\n", + " category=category,\n", + " model=model,\n", + " )\n", + " self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState(\n", + " diameter=14,\n", + " thickness=0.1,\n", + " mass=0.5,\n", + " material_type=\"copper\",\n", + " info=None\n", + " )\n", + "\n", + " # TODO: 这个还要不要?给self._unilabos_state赋值的?\n", + " def load_state(self, state: Dict[str, Any]) -> None:\n", + " \"\"\"格式不变\"\"\"\n", + " super().load_state(state)\n", + " self._unilabos_state = state\n", + " #序列化\n", + " def serialize_state(self) -> Dict[str, Dict[str, Any]]:\n", + " \"\"\"格式不变\"\"\"\n", + " data = super().serialize_state()\n", + " data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)\n", + " return data\n" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "830f052e", + "metadata": {}, + "outputs": [], + "source": [ + "# TODO: 这个应该只能放一个极片\n", + "class MaterialHoleState(TypedDict):\n", + " diameter: int\n", + " depth: int\n", + " max_sheets: int\n", + " info: Optional[str] # 附加信息\n", + "\n", + "class MaterialHole(Resource):\n", + " \"\"\"料板洞位类\"\"\"\n", + " children: List[ElectrodeSheet] = []\n", + "\n", + " def __init__(\n", + " self,\n", + " name: str,\n", + " size_x: float,\n", + " size_y: float,\n", + " size_z: float,\n", + " category: str = \"material_hole\",\n", + " **kwargs\n", + " ):\n", + " super().__init__(\n", + " name=name,\n", + " size_x=size_x,\n", + " size_y=size_y,\n", + " size_z=size_z,\n", + " category=category,\n", + " )\n", + " self._unilabos_state: MaterialHoleState = MaterialHoleState(\n", + " diameter=20,\n", + " depth=10,\n", + " max_sheets=1,\n", + " info=None\n", + " )\n", + "\n", + " def get_all_sheet_info(self):\n", + " info_list = []\n", + " for sheet in self.children:\n", + " info_list.append(sheet._unilabos_state[\"info\"])\n", + " return info_list\n", + " \n", + " #这个函数函数好像没用,一般不会集中赋值质量\n", + " def set_all_sheet_mass(self):\n", + " for sheet in self.children:\n", + " sheet._unilabos_state[\"mass\"] = 0.5 # 示例:设置质量为0.5g\n", + "\n", + " def load_state(self, state: Dict[str, Any]) -> None:\n", + " \"\"\"格式不变\"\"\"\n", + " super().load_state(state)\n", + " self._unilabos_state = state\n", + "\n", + " def serialize_state(self) -> Dict[str, Dict[str, Any]]:\n", + " \"\"\"格式不变\"\"\"\n", + " data = super().serialize_state()\n", + " data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)\n", + " return data\n", + " #移动极片前先取出对象\n", + " def get_sheet_with_name(self, name: str) -> Optional[ElectrodeSheet]:\n", + " for sheet in self.children:\n", + " if sheet.name == name:\n", + " return sheet\n", + " return None\n", + "\n", + " def has_electrode_sheet(self) -> bool:\n", + " \"\"\"检查洞位是否有极片\"\"\"\n", + " return len(self.children) > 0\n", + "\n", + " def assign_child_resource(\n", + " self,\n", + " resource: ElectrodeSheet,\n", + " location: Optional[Coordinate],\n", + " reassign: bool = True,\n", + " ):\n", + " \"\"\"放置极片\"\"\"\n", + " # TODO: 这里要改,diameter找不到,加入._unilabos_state后应该没问题\n", + " if resource._unilabos_state[\"diameter\"] > self._unilabos_state[\"diameter\"]:\n", + " raise ValueError(f\"极片直径 {resource._unilabos_state['diameter']} 超过洞位直径 {self._unilabos_state['diameter']}\")\n", + " if len(self.children) >= self._unilabos_state[\"max_sheets\"]:\n", + " raise ValueError(f\"洞位已满,无法放置更多极片\")\n", + " super().assign_child_resource(resource, location, reassign)\n", + "\n", + " # 根据children的编号取物料对象。\n", + " def get_electrode_sheet_info(self, index: int) -> ElectrodeSheet:\n", + " return self.children[index]\n", + "\n", + "\n", + "#料板\n", + "class MaterialPlateState(TypedDict):\n", + " hole_spacing_x: float\n", + " hole_spacing_y: float\n", + " hole_diameter: float\n", + " info: Optional[str] # 附加信息\n", + "\n", + "class MaterialPlate(ItemizedResource[MaterialHole]):\n", + " \"\"\"料板类 - 4x4个洞位,每个洞位放1个极片\"\"\"\n", + " \n", + " children: List[MaterialHole]\n", + "\n", + " def __init__(\n", + " self,\n", + " name: str,\n", + " size_x: float,\n", + " size_y: float,\n", + " size_z: float,\n", + " ordered_items: Optional[Dict[str, MaterialHole]] = None,\n", + " ordering: Optional[OrderedDict[str, str]] = None,\n", + " category: str = \"material_plate\",\n", + " model: Optional[str] = None,\n", + " fill: bool = False\n", + " ):\n", + " \"\"\"初始化料板\n", + "\n", + " Args:\n", + " name: 料板名称\n", + " size_x: 长度 (mm)\n", + " size_y: 宽度 (mm)\n", + " size_z: 高度 (mm)\n", + " hole_diameter: 洞直径 (mm)\n", + " hole_depth: 洞深度 (mm)\n", + " hole_spacing_x: X方向洞位间距 (mm)\n", + " hole_spacing_y: Y方向洞位间距 (mm)\n", + " number: 编号\n", + " category: 类别\n", + " model: 型号\n", + " \"\"\"\n", + " self._unilabos_state: MaterialPlateState = MaterialPlateState(\n", + " hole_spacing_x=24.0,\n", + " hole_spacing_y=24.0,\n", + " hole_diameter=20.0,\n", + " info=\"\",\n", + " )\n", + " # 创建4x4的洞位\n", + " # TODO: 这里要改,对应不同形状\n", + " holes = create_ordered_items_2d(\n", + " klass=MaterialHole,\n", + " num_items_x=4,\n", + " num_items_y=4,\n", + " dx=(size_x - 4 * self._unilabos_state[\"hole_spacing_x\"]) / 2, # 居中\n", + " dy=(size_y - 4 * self._unilabos_state[\"hole_spacing_y\"]) / 2, # 居中\n", + " dz=size_z,\n", + " item_dx=self._unilabos_state[\"hole_spacing_x\"],\n", + " item_dy=self._unilabos_state[\"hole_spacing_y\"],\n", + " size_x = 16,\n", + " size_y = 16,\n", + " size_z = 16,\n", + " )\n", + " if fill:\n", + " super().__init__(\n", + " name=name,\n", + " size_x=size_x,\n", + " size_y=size_y,\n", + " size_z=size_z,\n", + " ordered_items=holes,\n", + " category=category,\n", + " model=model,\n", + " )\n", + " else:\n", + " super().__init__(\n", + " name=name,\n", + " size_x=size_x,\n", + " size_y=size_y,\n", + " size_z=size_z,\n", + " ordered_items=ordered_items,\n", + " ordering=ordering,\n", + " category=category,\n", + " model=model,\n", + " )\n", + "\n", + " def update_locations(self):\n", + " # TODO:调多次相加\n", + " holes = create_ordered_items_2d(\n", + " klass=MaterialHole,\n", + " num_items_x=4,\n", + " num_items_y=4,\n", + " dx=(self._size_x - 3 * self._unilabos_state[\"hole_spacing_x\"]) / 2, # 居中\n", + " dy=(self._size_y - 3 * self._unilabos_state[\"hole_spacing_y\"]) / 2, # 居中\n", + " dz=self._size_z,\n", + " item_dx=self._unilabos_state[\"hole_spacing_x\"],\n", + " item_dy=self._unilabos_state[\"hole_spacing_y\"],\n", + " size_x = 1,\n", + " size_y = 1,\n", + " size_z = 1,\n", + " )\n", + " for item, original_item in zip(holes.items(), self.children):\n", + " original_item.location = item[1].location" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "8318ccca", + "metadata": {}, + "outputs": [], + "source": [ + "class CoincellDeck(Deck):\n", + " \"\"\"纽扣电池组装工作站台面类\"\"\"\n", + "\n", + " def __init__(\n", + " self,\n", + " name: str = \"coin_cell_deck\",\n", + " size_x: float = 1620.0, # 3.66m\n", + " size_y: float = 1270.0, # 1.23m\n", + " size_z: float = 500.0,\n", + " origin: Coordinate = Coordinate(0, 0, 0),\n", + " category: str = \"coin_cell_deck\",\n", + " ):\n", + " \"\"\"初始化纽扣电池组装工作站台面\n", + "\n", + " Args:\n", + " name: 台面名称\n", + " size_x: 长度 (mm) - 3.66m\n", + " size_y: 宽度 (mm) - 1.23m\n", + " size_z: 高度 (mm)\n", + " origin: 原点坐标\n", + " category: 类别\n", + " \"\"\"\n", + " super().__init__(\n", + " name=name,\n", + " size_x=size_x,\n", + " size_y=size_y,\n", + " size_z=size_z,\n", + " origin=origin,\n", + " category=category,\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "c73bae21", + "metadata": {}, + "outputs": [], + "source": [ + "import json" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "3369a1dd", + "metadata": {}, + "outputs": [], + "source": [ + "def upload_resources_to_unilab(wuliao: List[Resource]):\n", + " from unilabos.resources.graphio import convert_resources_from_type\n", + " from unilabos.config.config import BasicConfig \n", + " BasicConfig.ak = \"beb0c15f-2279-46a1-aba5-00eaf89aef55\"\n", + " BasicConfig.sk = \"15d4f25e-3512-4f9c-9bfb-43ab85e7b561\"\n", + " from unilabos.app.web.client import http_client\n", + " resources = convert_resources_from_type(wuliao, [Resource])\n", + " json.dump({\"nodes\": resources, \"links\": []}, open(\"button_battery_station_resources_unilab.json\", \"w\"), indent=2)\n", + " \n", + " #print(resources)\n", + " http_client.remote_addr = \"https://uni-lab.test.bohrium.com/api/v1\"\n", + " \n", + " http_client.resource_add(resources)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "1543ddab", + "metadata": {}, + "outputs": [], + "source": [ + "liaopan1 = MaterialPlate(name=\"liaopan1\", size_x=120.8, size_y=120.5, size_z=10.0, fill=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "b732754a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MaterialPlate(name=liaopan1, size_x=120.8, size_y=120.5, size_z=10.0, location=None)\n" + ] + } + ], + "source": [ + "print(liaopan1)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "7e6e7252", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[MaterialHole(name=liaopan1_materialhole_0_0, location=Coordinate(012.400, 084.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_0_1, location=Coordinate(012.400, 060.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_0_2, location=Coordinate(012.400, 036.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_0_3, location=Coordinate(012.400, 012.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_1_0, location=Coordinate(036.400, 084.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_1_1, location=Coordinate(036.400, 060.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_1_2, location=Coordinate(036.400, 036.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_1_3, location=Coordinate(036.400, 012.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_2_0, location=Coordinate(060.400, 084.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_2_1, location=Coordinate(060.400, 060.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_2_2, location=Coordinate(060.400, 036.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_2_3, location=Coordinate(060.400, 012.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_3_0, location=Coordinate(084.400, 084.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_3_1, location=Coordinate(084.400, 060.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_3_2, location=Coordinate(084.400, 036.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_3_3, location=Coordinate(084.400, 012.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole)]\n" + ] + } + ], + "source": [ + "print(liaopan1.children)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "836ff68d", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[37m25-09-22 [15:15:08,950]\u001b[0m \u001b[1;36m[DEBUG]\u001b[0m \u001b[37mStarting new HTTPS connection (1): uni-lab.test.bohrium.com:443\u001b[37m [_new_conn:1049] [urllib3.connectionpool.connectionpool]\u001b[0m\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CoincellDeck(name=coin_cell_deck, location=Coordinate(000.000, 000.000, 000.000), size_x=1620.0, size_y=1270.0, size_z=500.0, category=coin_cell_deck)\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_plate\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_plate\n", + "转换pylabrobot的时候,出现未知类型 coin_cell_deck\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[37m25-09-22 [15:15:09,218]\u001b[0m \u001b[1;36m[DEBUG]\u001b[0m \u001b[37mhttps://uni-lab.test.bohrium.com:443 \"POST /api/v1/lab/material HTTP/1.1\" 200 10\u001b[37m [_make_request:544] [urllib3.connectionpool.connectionpool]\u001b[0m\n" + ] + } + ], + "source": [ + "deck = CoincellDeck()\n", + "#创建一个4*4的物料板\n", + "liaopan1 = MaterialPlate(name=\"liaopan1\", size_x=120.8, size_y=120.5, size_z=10.0, fill=True)\n", + "#把物料板放到桌子上\n", + "deck.assign_child_resource(liaopan1, Coordinate(x=0, y=0, z=0))\n", + "#创建一个极片\n", + "for i in range(16):\n", + " jipian = ElectrodeSheet(name=f\"jipian_{i}\", size_x= 12, size_y=12, size_z=0.1)\n", + " liaopan1.children[i].assign_child_resource(jipian, location=None)\n", + "#创建一个4*4的物料板\n", + "liaopan2 = MaterialPlate(name=\"liaopan2\", size_x=120.8, size_y=120.5, size_z=10.0, fill=True)\n", + "#把物料板放到桌子上\n", + "deck.assign_child_resource(liaopan2, Coordinate(x=500, y=0, z=0))\n", + "#liaopan.children[3].assign_child_resource(jipian, location=None)\n", + "print(deck)\n", + "\n", + "upload_resources_to_unilab([deck])" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "00aab9cf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MaterialPlate(name=liaopan1, size_x=120.8, size_y=120.5, size_z=10.0, location=Coordinate(000.000, 000.000, 000.000))\n" + ] + } + ], + "source": [ + "liaopan1 = deck.get_resource(\"liaopan1\")\n", + "print(liaopan1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7409969c", + "metadata": {}, + "outputs": [], + "source": [ + "liaopan1 = deck.get_resource(\"liaopan1\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "096dde04", + "metadata": {}, + "outputs": [], + "source": [ + "print()" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "5528df96", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[37m25-09-22 [15:17:44,322]\u001b[0m \u001b[1;36m[DEBUG]\u001b[0m \u001b[37mStarting new HTTPS connection (1): uni-lab.test.bohrium.com:443\u001b[37m [_new_conn:1049] [urllib3.connectionpool.connectionpool]\u001b[0m\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ElectrodeSheet(name=jipian_1, location=None, size_x=12, size_y=12, size_z=0.1, category=electrode_sheet)\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_plate\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_plate\n", + "转换pylabrobot的时候,出现未知类型 coin_cell_deck\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[37m25-09-22 [15:17:44,599]\u001b[0m \u001b[1;36m[DEBUG]\u001b[0m \u001b[37mhttps://uni-lab.test.bohrium.com:443 \"POST /api/v1/lab/material HTTP/1.1\" 200 10\u001b[37m [_make_request:544] [urllib3.connectionpool.connectionpool]\u001b[0m\n" + ] + } + ], + "source": [ + "#在台面上找到料盘和极片\n", + "liaopan1 = deck.get_resource(\"liaopan1\")\n", + "liaopan2 = deck.get_resource(\"liaopan2\")\n", + "jipian1 = liaopan1.children[1].children[0]\n", + "#\n", + "print(jipian1)\n", + "#把物料解绑后放到另一盘上\n", + "jipian1.parent.unassign_child_resource(jipian1)\n", + "liaopan2.children[1].assign_child_resource(jipian1, location=None)\n", + "#print(jipian2.parent)\n", + "upload_resources_to_unilab([deck])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43736700", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[37m25-09-22 [14:31:50,027]\u001b[0m \u001b[1;36m[DEBUG]\u001b[0m \u001b[37mStarting new HTTPS connection (1): uni-lab.test.bohrium.com:443\u001b[37m [_new_conn:1049] [urllib3.connectionpool.connectionpool]\u001b[0m\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_plate\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_hole\n", + "转换pylabrobot的时候,出现未知类型 material_plate\n", + "转换pylabrobot的时候,出现未知类型 coin_cell_deck\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[37m25-09-22 [14:31:50,358]\u001b[0m \u001b[1;36m[DEBUG]\u001b[0m \u001b[37mhttps://uni-lab.test.bohrium.com:443 \"POST /api/v1/lab/material HTTP/1.1\" 200 10\u001b[37m [_make_request:544] [urllib3.connectionpool.connectionpool]\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "unilab", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/unilabos/devices/workstation/coin_cell_assembly/work_station.yaml b/unilabos/devices/workstation/coin_cell_assembly/work_station.yaml new file mode 100644 index 0000000..29e8437 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/work_station.yaml @@ -0,0 +1,6674 @@ +bettery_station_registry: + category: + - work_station + class: + action_value_mappings: + auto-change_hole_sheet_to_2: + feedback: {} + goal: {} + goal_default: + hole: null + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + hole: + type: object + required: + - hole + type: object + result: {} + required: + - goal + title: change_hole_sheet_to_2参数 + type: object + type: UniLabJsonCommandAsync + auto-fill_plate: + feedback: {} + goal: {} + goal_default: {} + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: fill_plate参数 + type: object + type: UniLabJsonCommandAsync + auto-fun_wuliao_test: + feedback: {} + goal: {} + goal_default: {} + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: fun_wuliao_test参数 + type: object + type: UniLabJsonCommand + auto-func_allpack_cmd: + feedback: {} + goal: {} + goal_default: + elec_num: null + elec_use_num: null + file_path: D:\coin_cell_data + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + elec_num: + type: string + elec_use_num: + type: string + file_path: + default: D:\coin_cell_data + type: string + required: + - elec_num + - elec_use_num + type: object + result: {} + required: + - goal + title: func_allpack_cmd参数 + type: object + type: UniLabJsonCommand + auto-func_get_csv_export_status: + feedback: {} + goal: {} + goal_default: {} + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_get_csv_export_status参数 + type: object + type: UniLabJsonCommand + auto-func_pack_device_auto: + feedback: {} + goal: {} + goal_default: {} + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_pack_device_auto参数 + type: object + type: UniLabJsonCommand + auto-func_pack_device_init: + feedback: {} + goal: {} + goal_default: {} + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_pack_device_init参数 + type: object + type: UniLabJsonCommand + auto-func_pack_device_start: + feedback: {} + goal: {} + goal_default: {} + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_pack_device_start参数 + type: object + type: UniLabJsonCommand + auto-func_pack_device_stop: + feedback: {} + goal: {} + goal_default: {} + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_pack_device_stop参数 + type: object + type: UniLabJsonCommand + auto-func_pack_get_msg_cmd: + feedback: {} + goal: {} + goal_default: + file_path: D:\coin_cell_data + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + file_path: + default: D:\coin_cell_data + type: string + required: [] + type: object + result: {} + required: + - goal + title: func_pack_get_msg_cmd参数 + type: object + type: UniLabJsonCommand + auto-func_pack_send_bottle_num: + feedback: {} + goal: {} + goal_default: + bottle_num: null + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + bottle_num: + type: integer + required: + - bottle_num + type: object + result: {} + required: + - goal + title: func_pack_send_bottle_num参数 + type: object + type: UniLabJsonCommand + auto-func_pack_send_finished_cmd: + feedback: {} + goal: {} + goal_default: {} + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_pack_send_finished_cmd参数 + type: object + type: UniLabJsonCommand + auto-func_pack_send_msg_cmd: + feedback: {} + goal: {} + goal_default: + elec_use_num: null + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + elec_use_num: + type: string + required: + - elec_use_num + type: object + result: {} + required: + - goal + title: func_pack_send_msg_cmd参数 + type: object + type: UniLabJsonCommand + auto-func_read_data_and_output: + feedback: {} + goal: {} + goal_default: + file_path: D:\coin_cell_data + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + file_path: + default: D:\coin_cell_data + type: string + required: [] + type: object + result: {} + required: + - goal + title: func_read_data_and_output参数 + type: object + type: UniLabJsonCommand + auto-func_stop_read_data: + feedback: {} + goal: {} + goal_default: {} + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_stop_read_data参数 + type: object + type: UniLabJsonCommand + auto-modify_deck_name: + feedback: {} + goal: {} + goal_default: + resource_name: null + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + resource_name: + type: string + required: + - resource_name + type: object + result: {} + required: + - goal + title: modify_deck_name参数 + type: object + type: UniLabJsonCommand + auto-post_init: + feedback: {} + goal: {} + goal_default: + ros_node: null + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + ros_node: + type: object + required: + - ros_node + type: object + result: {} + required: + - goal + title: post_init参数 + type: object + type: UniLabJsonCommand + module: unilabos.devices.workstation.coin_cell_assembly.coin_cell_assembly:CoinCellAssemblyWorkstation + status_types: + data_assembly_coin_cell_num: int + data_assembly_pressure: int + data_assembly_time: float + data_axis_x_pos: float + data_axis_y_pos: float + data_axis_z_pos: float + data_coin_cell_code: str + data_coin_num: int + data_electrolyte_code: str + data_electrolyte_volume: int + data_glove_box_o2_content: float + data_glove_box_pressure: float + data_glove_box_water_content: float + data_open_circuit_voltage: float + data_pole_weight: float + request_rec_msg_status: bool + request_send_msg_status: bool + sys_mode: str + sys_status: str + type: python + config_info: [] + description: '' + handles: [] + icon: '' + init_param_schema: + config: + properties: + address: + default: 192.168.1.20 + type: string + debug_mode: + default: true + type: boolean + port: + default: '502' + type: string + station_resource: + type: object + required: + - station_resource + type: object + data: + properties: + data_assembly_coin_cell_num: + type: integer + data_assembly_pressure: + type: integer + data_assembly_time: + type: number + data_axis_x_pos: + type: number + data_axis_y_pos: + type: number + data_axis_z_pos: + type: number + data_coin_cell_code: + type: string + data_coin_num: + type: integer + data_electrolyte_code: + type: string + data_electrolyte_volume: + type: integer + data_glove_box_o2_content: + type: number + data_glove_box_pressure: + type: number + data_glove_box_water_content: + type: number + data_open_circuit_voltage: + type: number + data_pole_weight: + type: number + request_rec_msg_status: + type: boolean + request_send_msg_status: + type: boolean + sys_mode: + type: string + sys_status: + type: string + required: + - sys_status + - sys_mode + - request_rec_msg_status + - request_send_msg_status + - data_assembly_coin_cell_num + - data_assembly_time + - data_open_circuit_voltage + - data_axis_x_pos + - data_axis_y_pos + - data_axis_z_pos + - data_pole_weight + - data_assembly_pressure + - data_electrolyte_volume + - data_coin_num + - data_coin_cell_code + - data_electrolyte_code + - data_glove_box_pressure + - data_glove_box_o2_content + - data_glove_box_water_content + type: object + version: 1.0.0 +workstation: + category: + - work_station + class: + action_value_mappings: + AGVTransferProtocol: + feedback: {} + goal: + from_repo: from_repo + from_repo_position: from_repo_position + to_repo: to_repo + to_repo_position: to_repo_position + goal_default: + from_repo: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + from_repo_position: '' + to_repo: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + to_repo_position: '' + handles: {} + result: {} + schema: + description: '' + properties: + feedback: + properties: + status: + type: string + required: + - status + title: AGVTransfer_Feedback + type: object + goal: + properties: + from_repo: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: from_repo + type: object + from_repo_position: + type: string + to_repo: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: to_repo + type: object + to_repo_position: + type: string + required: + - from_repo + - from_repo_position + - to_repo + - to_repo_position + title: AGVTransfer_Goal + type: object + result: + properties: + return_info: + type: string + success: + type: boolean + required: + - return_info + - success + title: AGVTransfer_Result + type: object + required: + - goal + title: AGVTransfer + type: object + type: AGVTransfer + AddProtocol: + feedback: {} + goal: + amount: amount + equiv: equiv + event: event + mass: mass + mol: mol + purpose: purpose + rate_spec: rate_spec + ratio: ratio + reagent: reagent + stir: stir + stir_speed: stir_speed + time: time + vessel: vessel + viscous: viscous + volume: volume + goal_default: + amount: '' + equiv: '' + event: '' + mass: '' + mol: '' + purpose: '' + rate_spec: '' + ratio: '' + reagent: '' + stir: false + stir_speed: 0.0 + time: '' + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + viscous: false + volume: '' + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: Vessel + label: Vessel + - data_key: reagent + data_source: handle + data_type: resource + handler_key: reagent + label: Reagent + output: + - data_key: vessel + data_source: executor + data_type: resource + handler_key: VesselOut + label: Vessel + placeholder_keys: + vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + current_status: + type: string + progress: + type: number + required: + - progress + - current_status + title: Add_Feedback + type: object + goal: + properties: + amount: + type: string + equiv: + type: string + event: + type: string + mass: + type: string + mol: + type: string + purpose: + type: string + rate_spec: + type: string + ratio: + type: string + reagent: + type: string + stir: + type: boolean + stir_speed: + type: number + time: + type: string + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + viscous: + type: boolean + volume: + type: string + required: + - vessel + - reagent + - volume + - mass + - amount + - time + - stir + - stir_speed + - viscous + - purpose + - event + - mol + - rate_spec + - equiv + - ratio + title: Add_Goal + type: object + result: + properties: + message: + type: string + return_info: + type: string + success: + type: boolean + required: + - success + - message + - return_info + title: Add_Result + type: object + required: + - goal + title: Add + type: object + type: Add + AdjustPHProtocol: + feedback: {} + goal: + ph_value: ph_value + reagent: reagent + settling_time: settling_time + stir: stir + stir_speed: stir_speed + stir_time: stir_time + vessel: vessel + volume: volume + goal_default: + ph_value: 0.0 + reagent: '' + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: Vessel + label: Vessel + - data_key: reagent + data_source: handle + data_type: resource + handler_key: reagent + label: Reagent + output: + - data_key: vessel + data_source: executor + data_type: resource + handler_key: VesselOut + label: Vessel + placeholder_keys: + vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + progress: + type: number + status: + type: string + required: + - status + - progress + title: AdjustPH_Feedback + type: object + goal: + properties: + ph_value: + type: number + reagent: + type: string + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + required: + - vessel + - ph_value + - reagent + title: AdjustPH_Goal + type: object + result: + properties: + message: + type: string + return_info: + type: string + success: + type: boolean + required: + - success + - message + - return_info + title: AdjustPH_Result + type: object + required: + - goal + title: AdjustPH + type: object + type: AdjustPH + CentrifugeProtocol: + feedback: {} + goal: + speed: speed + temp: temp + time: time + vessel: vessel + goal_default: + speed: 0.0 + temp: 0.0 + time: 0.0 + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: Vessel + label: Vessel + output: + - data_key: vessel + data_source: executor + data_type: resource + handler_key: VesselOut + label: Vessel + placeholder_keys: + vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + current_speed: + type: number + current_status: + type: string + current_temp: + type: number + progress: + type: number + required: + - progress + - current_speed + - current_temp + - current_status + title: Centrifuge_Feedback + type: object + goal: + properties: + speed: + type: number + temp: + type: number + time: + type: number + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + required: + - vessel + - speed + - time + - temp + title: Centrifuge_Goal + type: object + result: + properties: + message: + type: string + return_info: + type: string + success: + type: boolean + required: + - success + - message + - return_info + title: Centrifuge_Result + type: object + required: + - goal + title: Centrifuge + type: object + type: Centrifuge + CleanProtocol: + feedback: {} + goal: + repeats: repeats + solvent: solvent + temp: temp + vessel: vessel + volume: volume + goal_default: + repeats: 0 + solvent: '' + temp: 0.0 + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + volume: 0.0 + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: Vessel + label: Vessel + - data_key: solvent + data_source: handle + data_type: resource + handler_key: solvent + label: Solvent + output: + - data_key: vessel + data_source: executor + data_type: resource + handler_key: VesselOut + label: Vessel + placeholder_keys: + vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + current_device: + type: string + status: + type: string + time_remaining: + properties: + nanosec: + maximum: 4294967295 + minimum: 0 + type: integer + sec: + maximum: 2147483647 + minimum: -2147483648 + type: integer + required: + - sec + - nanosec + title: time_remaining + type: object + time_spent: + properties: + nanosec: + maximum: 4294967295 + minimum: 0 + type: integer + sec: + maximum: 2147483647 + minimum: -2147483648 + type: integer + required: + - sec + - nanosec + title: time_spent + type: object + required: + - status + - current_device + - time_spent + - time_remaining + title: Clean_Feedback + type: object + goal: + properties: + repeats: + maximum: 2147483647 + minimum: -2147483648 + type: integer + solvent: + type: string + temp: + type: number + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + volume: + type: number + required: + - vessel + - solvent + - volume + - temp + - repeats + title: Clean_Goal + type: object + result: + properties: + return_info: + type: string + success: + type: boolean + required: + - return_info + - success + title: Clean_Result + type: object + required: + - goal + title: Clean + type: object + type: Clean + CleanVesselProtocol: + feedback: {} + goal: + repeats: repeats + solvent: solvent + temp: temp + vessel: vessel + volume: volume + goal_default: + repeats: 0 + solvent: '' + temp: 0.0 + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + volume: 0.0 + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: Vessel + label: Vessel + - data_key: solvent + data_source: handle + data_type: resource + handler_key: solvent + label: Solvent + output: + - data_key: vessel + data_source: executor + data_type: resource + handler_key: VesselOut + label: Vessel + placeholder_keys: + vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + progress: + type: number + status: + type: string + required: + - status + - progress + title: CleanVessel_Feedback + type: object + goal: + properties: + repeats: + maximum: 2147483647 + minimum: -2147483648 + type: integer + solvent: + type: string + temp: + type: number + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + volume: + type: number + required: + - vessel + - solvent + - volume + - temp + - repeats + title: CleanVessel_Goal + type: object + result: + properties: + message: + type: string + return_info: + type: string + success: + type: boolean + required: + - success + - message + - return_info + title: CleanVessel_Result + type: object + required: + - goal + title: CleanVessel + type: object + type: CleanVessel + DissolveProtocol: + feedback: {} + goal: + amount: amount + event: event + mass: mass + mol: mol + reagent: reagent + solvent: solvent + stir_speed: stir_speed + temp: temp + time: time + vessel: vessel + volume: volume + goal_default: + amount: '' + event: '' + mass: '' + mol: '' + reagent: '' + solvent: '' + stir_speed: 0.0 + temp: '' + time: '' + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + volume: '' + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: Vessel + label: Vessel + - data_key: solvent + data_source: handle + data_type: resource + handler_key: solvent + label: Solvent + - data_key: reagent + data_source: handle + data_type: resource + handler_key: reagent + label: Reagent + output: + - data_key: vessel + data_source: executor + data_type: resource + handler_key: VesselOut + label: Vessel + placeholder_keys: + vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + progress: + type: number + status: + type: string + required: + - status + - progress + title: Dissolve_Feedback + type: object + goal: + properties: + amount: + type: string + event: + type: string + mass: + type: string + mol: + type: string + reagent: + type: string + solvent: + type: string + stir_speed: + type: number + temp: + type: string + time: + type: string + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + volume: + type: string + required: + - vessel + - solvent + - volume + - amount + - temp + - time + - stir_speed + - mass + - mol + - reagent + - event + title: Dissolve_Goal + type: object + result: + properties: + message: + type: string + return_info: + type: string + success: + type: boolean + required: + - success + - message + - return_info + title: Dissolve_Result + type: object + required: + - goal + title: Dissolve + type: object + type: Dissolve + DryProtocol: + feedback: {} + goal: + compound: compound + vessel: vessel + goal_default: + compound: '' + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: Vessel + label: Vessel + output: + - data_key: vessel + data_source: executor + data_type: resource + handler_key: VesselOut + label: Vessel + placeholder_keys: + vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + progress: + type: number + status: + type: string + required: + - status + - progress + title: Dry_Feedback + type: object + goal: + properties: + compound: + type: string + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + required: + - compound + - vessel + title: Dry_Goal + type: object + result: + properties: + message: + type: string + return_info: + type: string + success: + type: boolean + required: + - success + - message + - return_info + title: Dry_Result + type: object + required: + - goal + title: Dry + type: object + type: Dry + EvacuateAndRefillProtocol: + feedback: {} + goal: + gas: gas + vessel: vessel + goal_default: + gas: '' + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: Vessel + label: Vessel + output: + - data_key: vessel + data_source: executor + data_type: resource + handler_key: VesselOut + label: Vessel + placeholder_keys: + vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + current_device: + type: string + status: + type: string + time_remaining: + properties: + nanosec: + maximum: 4294967295 + minimum: 0 + type: integer + sec: + maximum: 2147483647 + minimum: -2147483648 + type: integer + required: + - sec + - nanosec + title: time_remaining + type: object + time_spent: + properties: + nanosec: + maximum: 4294967295 + minimum: 0 + type: integer + sec: + maximum: 2147483647 + minimum: -2147483648 + type: integer + required: + - sec + - nanosec + title: time_spent + type: object + required: + - status + - current_device + - time_spent + - time_remaining + title: EvacuateAndRefill_Feedback + type: object + goal: + properties: + gas: + type: string + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + required: + - vessel + - gas + title: EvacuateAndRefill_Goal + type: object + result: + properties: + return_info: + type: string + success: + type: boolean + required: + - return_info + - success + title: EvacuateAndRefill_Result + type: object + required: + - goal + title: EvacuateAndRefill + type: object + type: EvacuateAndRefill + EvaporateProtocol: + feedback: {} + goal: + pressure: pressure + solvent: solvent + stir_speed: stir_speed + temp: temp + time: time + vessel: vessel + goal_default: + pressure: 0.0 + solvent: '' + stir_speed: 0.0 + temp: 0.0 + time: '' + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: Vessel + label: Evaporation Vessel + - data_key: solvent + data_source: handle + data_type: resource + handler_key: solvent + label: Eluting Solvent + output: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: VesselOut + label: Evaporation Vessel + placeholder_keys: + vessel: unilabos_nodes + result: {} + schema: + description: '' + properties: + feedback: + properties: + current_device: + type: string + status: + type: string + time_remaining: + properties: + nanosec: + maximum: 4294967295 + minimum: 0 + type: integer + sec: + maximum: 2147483647 + minimum: -2147483648 + type: integer + required: + - sec + - nanosec + title: time_remaining + type: object + time_spent: + properties: + nanosec: + maximum: 4294967295 + minimum: 0 + type: integer + sec: + maximum: 2147483647 + minimum: -2147483648 + type: integer + required: + - sec + - nanosec + title: time_spent + type: object + required: + - status + - current_device + - time_spent + - time_remaining + title: Evaporate_Feedback + type: object + goal: + properties: + pressure: + type: number + solvent: + type: string + stir_speed: + type: number + temp: + type: number + time: + type: string + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + required: + - vessel + - pressure + - temp + - time + - stir_speed + - solvent + title: Evaporate_Goal + type: object + result: + properties: + return_info: + type: string + success: + type: boolean + required: + - return_info + - success + title: Evaporate_Result + type: object + required: + - goal + title: Evaporate + type: object + type: Evaporate + FilterProtocol: + feedback: {} + goal: + continue_heatchill: continue_heatchill + filtrate_vessel: filtrate_vessel + stir: stir + stir_speed: stir_speed + temp: temp + vessel: vessel + volume: volume + goal_default: + continue_heatchill: false + filtrate_vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + stir: false + stir_speed: 0.0 + temp: 0.0 + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + volume: 0.0 + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: Vessel + label: Vessel + - data_key: filtrate_vessel + data_source: handle + data_type: resource + handler_key: FiltrateVessel + label: Filtrate Vessel + output: + - data_key: vessel + data_source: executor + data_type: resource + handler_key: VesselOut + label: Vessel + - data_key: filtrate_vessel + data_source: executor + data_type: resource + handler_key: FiltrateOut + label: Filtrate Vessel + placeholder_keys: + filtrate_vessel: unilabos_resources + vessel: unilabos_nodes + result: {} + schema: + description: '' + properties: + feedback: + properties: + current_status: + type: string + current_temp: + type: number + filtered_volume: + type: number + progress: + type: number + required: + - progress + - current_temp + - filtered_volume + - current_status + title: Filter_Feedback + type: object + goal: + properties: + continue_heatchill: + type: boolean + filtrate_vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: filtrate_vessel + type: object + stir: + type: boolean + stir_speed: + type: number + temp: + type: number + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + volume: + type: number + required: + - vessel + - filtrate_vessel + - stir + - stir_speed + - temp + - continue_heatchill + - volume + title: Filter_Goal + type: object + result: + properties: + message: + type: string + return_info: + type: string + success: + type: boolean + required: + - success + - message + - return_info + title: Filter_Result + type: object + required: + - goal + title: Filter + type: object + type: Filter + FilterThroughProtocol: + feedback: {} + goal: + eluting_repeats: eluting_repeats + eluting_solvent: eluting_solvent + eluting_volume: eluting_volume + filter_through: filter_through + from_vessel: from_vessel + residence_time: residence_time + to_vessel: to_vessel + goal_default: + eluting_repeats: 0 + eluting_solvent: '' + eluting_volume: 0.0 + filter_through: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + from_vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + residence_time: 0.0 + to_vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: FromVessel + label: From Vessel + - data_key: vessel + data_source: executor + data_type: resource + handler_key: ToVessel + label: To Vessel + - data_key: solvent + data_source: handle + data_type: resource + handler_key: solvent + label: Eluting Solvent + output: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: FromVesselOut + label: From Vessel + - data_key: vessel + data_source: executor + data_type: resource + handler_key: ToVesselOut + label: To Vessel + placeholder_keys: + from_vessel: unilabos_resources + to_vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + progress: + type: number + status: + type: string + required: + - status + - progress + title: FilterThrough_Feedback + type: object + goal: + properties: + eluting_repeats: + maximum: 2147483647 + minimum: -2147483648 + type: integer + eluting_solvent: + type: string + eluting_volume: + type: number + filter_through: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: filter_through + type: object + from_vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: from_vessel + type: object + residence_time: + type: number + to_vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: to_vessel + type: object + required: + - from_vessel + - to_vessel + - filter_through + - eluting_solvent + - eluting_volume + - eluting_repeats + - residence_time + title: FilterThrough_Goal + type: object + result: + properties: + message: + type: string + return_info: + type: string + success: + type: boolean + required: + - success + - message + - return_info + title: FilterThrough_Result + type: object + required: + - goal + title: FilterThrough + type: object + type: FilterThrough + HeatChillProtocol: + feedback: {} + goal: + pressure: pressure + purpose: purpose + reflux_solvent: reflux_solvent + stir: stir + stir_speed: stir_speed + temp: temp + temp_spec: temp_spec + time: time + time_spec: time_spec + vessel: vessel + goal_default: + pressure: '' + purpose: '' + reflux_solvent: '' + stir: false + stir_speed: 0.0 + temp: 0.0 + temp_spec: '' + time: '' + time_spec: '' + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: Vessel + label: Vessel + output: + - data_key: vessel + data_source: executor + data_type: resource + handler_key: VesselOut + label: Vessel + placeholder_keys: + vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + status: + type: string + required: + - status + title: HeatChill_Feedback + type: object + goal: + properties: + pressure: + type: string + purpose: + type: string + reflux_solvent: + type: string + stir: + type: boolean + stir_speed: + type: number + temp: + type: number + temp_spec: + type: string + time: + type: string + time_spec: + type: string + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + required: + - vessel + - temp + - time + - temp_spec + - time_spec + - pressure + - reflux_solvent + - stir + - stir_speed + - purpose + title: HeatChill_Goal + type: object + result: + properties: + message: + type: string + return_info: + type: string + success: + type: boolean + required: + - success + - message + - return_info + title: HeatChill_Result + type: object + required: + - goal + title: HeatChill + type: object + type: HeatChill + HeatChillStartProtocol: + feedback: {} + goal: + purpose: purpose + temp: temp + vessel: vessel + goal_default: + purpose: '' + temp: 0.0 + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: Vessel + label: Vessel + output: + - data_key: vessel + data_source: executor + data_type: resource + handler_key: VesselOut + label: Vessel + placeholder_keys: + vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + status: + type: string + required: + - status + title: HeatChillStart_Feedback + type: object + goal: + properties: + purpose: + type: string + temp: + type: number + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + required: + - vessel + - temp + - purpose + title: HeatChillStart_Goal + type: object + result: + properties: + return_info: + type: string + success: + type: boolean + required: + - return_info + - success + title: HeatChillStart_Result + type: object + required: + - goal + title: HeatChillStart + type: object + type: HeatChillStart + HeatChillStopProtocol: + feedback: {} + goal: + vessel: vessel + goal_default: + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: Vessel + label: Vessel + output: + - data_key: vessel + data_source: executor + data_type: resource + handler_key: VesselOut + label: Vessel + placeholder_keys: + vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + status: + type: string + required: + - status + title: HeatChillStop_Feedback + type: object + goal: + properties: + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + required: + - vessel + title: HeatChillStop_Goal + type: object + result: + properties: + return_info: + type: string + success: + type: boolean + required: + - return_info + - success + title: HeatChillStop_Result + type: object + required: + - goal + title: HeatChillStop + type: object + type: HeatChillStop + HydrogenateProtocol: + feedback: {} + goal: + temp: temp + time: time + vessel: vessel + goal_default: + temp: '' + time: '' + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: Vessel + label: Vessel + output: + - data_key: vessel + data_source: executor + data_type: resource + handler_key: VesselOut + label: Vessel + placeholder_keys: + vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + progress: + type: number + status: + type: string + required: + - status + - progress + title: Hydrogenate_Feedback + type: object + goal: + properties: + temp: + type: string + time: + type: string + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + required: + - temp + - time + - vessel + title: Hydrogenate_Goal + type: object + result: + properties: + message: + type: string + return_info: + type: string + success: + type: boolean + required: + - success + - message + - return_info + title: Hydrogenate_Result + type: object + required: + - goal + title: Hydrogenate + type: object + type: Hydrogenate + PumpTransferProtocol: + feedback: {} + goal: + amount: amount + event: event + flowrate: flowrate + from_vessel: from_vessel + rate_spec: rate_spec + rinsing_repeats: rinsing_repeats + rinsing_solvent: rinsing_solvent + rinsing_volume: rinsing_volume + solid: solid + through: through + time: time + to_vessel: to_vessel + transfer_flowrate: transfer_flowrate + viscous: viscous + volume: volume + goal_default: + amount: '' + event: '' + flowrate: 0.0 + from_vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + rate_spec: '' + rinsing_repeats: 0 + rinsing_solvent: '' + rinsing_volume: 0.0 + solid: false + through: '' + time: 0.0 + to_vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + transfer_flowrate: 0.0 + viscous: false + volume: 0.0 + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: FromVessel + label: From Vessel + - data_key: vessel + data_source: executor + data_type: resource + handler_key: ToVessel + label: To Vessel + - data_key: solvent + data_source: handle + data_type: resource + handler_key: solvent + label: Rinsing Solvent + output: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: FromVesselOut + label: From Vessel + - data_key: vessel + data_source: executor + data_type: resource + handler_key: ToVesselOut + label: To Vessel + placeholder_keys: + from_vessel: unilabos_nodes + to_vessel: unilabos_nodes + result: {} + schema: + description: '' + properties: + feedback: + properties: + current_device: + type: string + status: + type: string + time_remaining: + properties: + nanosec: + maximum: 4294967295 + minimum: 0 + type: integer + sec: + maximum: 2147483647 + minimum: -2147483648 + type: integer + required: + - sec + - nanosec + title: time_remaining + type: object + time_spent: + properties: + nanosec: + maximum: 4294967295 + minimum: 0 + type: integer + sec: + maximum: 2147483647 + minimum: -2147483648 + type: integer + required: + - sec + - nanosec + title: time_spent + type: object + required: + - status + - current_device + - time_spent + - time_remaining + title: PumpTransfer_Feedback + type: object + goal: + properties: + amount: + type: string + event: + type: string + flowrate: + type: number + from_vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: from_vessel + type: object + rate_spec: + type: string + rinsing_repeats: + maximum: 2147483647 + minimum: -2147483648 + type: integer + rinsing_solvent: + type: string + rinsing_volume: + type: number + solid: + type: boolean + through: + type: string + time: + type: number + to_vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: to_vessel + type: object + transfer_flowrate: + type: number + viscous: + type: boolean + volume: + type: number + required: + - from_vessel + - to_vessel + - volume + - amount + - time + - viscous + - rinsing_solvent + - rinsing_volume + - rinsing_repeats + - solid + - flowrate + - transfer_flowrate + - rate_spec + - event + - through + title: PumpTransfer_Goal + type: object + result: + properties: + return_info: + type: string + success: + type: boolean + required: + - return_info + - success + title: PumpTransfer_Result + type: object + required: + - goal + title: PumpTransfer + type: object + type: PumpTransfer + RecrystallizeProtocol: + feedback: {} + goal: + ratio: ratio + solvent1: solvent1 + solvent2: solvent2 + vessel: vessel + volume: volume + goal_default: + ratio: '' + solvent1: '' + solvent2: '' + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + volume: '' + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: Vessel + label: Vessel + - data_key: solvent1 + data_source: handle + data_type: resource + handler_key: solvent1 + label: Solvent 1 + - data_key: solvent2 + data_source: handle + data_type: resource + handler_key: solvent2 + label: Solvent 2 + output: + - data_key: vessel + data_source: executor + data_type: resource + handler_key: VesselOut + label: Vessel + placeholder_keys: + vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + progress: + type: number + status: + type: string + required: + - status + - progress + title: Recrystallize_Feedback + type: object + goal: + properties: + ratio: + type: string + solvent1: + type: string + solvent2: + type: string + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + volume: + type: string + required: + - ratio + - solvent1 + - solvent2 + - vessel + - volume + title: Recrystallize_Goal + type: object + result: + properties: + message: + type: string + return_info: + type: string + success: + type: boolean + required: + - success + - message + - return_info + title: Recrystallize_Result + type: object + required: + - goal + title: Recrystallize + type: object + type: Recrystallize + ResetHandlingProtocol: + feedback: {} + goal: + solvent: solvent + goal_default: + solvent: '' + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + handles: + input: + - data_key: solvent + data_source: handle + data_type: resource + handler_key: solvent + label: Solvent + output: [] + result: {} + schema: + description: '' + properties: + feedback: + properties: + progress: + type: number + status: + type: string + required: + - status + - progress + title: ResetHandling_Feedback + type: object + goal: + properties: + solvent: + type: string + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + required: + - solvent + - vessel + title: ResetHandling_Goal + type: object + result: + properties: + message: + type: string + return_info: + type: string + success: + type: boolean + required: + - success + - message + - return_info + title: ResetHandling_Result + type: object + required: + - goal + title: ResetHandling + type: object + type: ResetHandling + RunColumnProtocol: + feedback: {} + goal: + column: column + from_vessel: from_vessel + to_vessel: to_vessel + goal_default: + column: '' + from_vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + pct1: '' + pct2: '' + ratio: '' + rf: '' + solvent1: '' + solvent2: '' + to_vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: FromVessel + label: From Vessel + - data_key: vessel + data_source: executor + data_type: resource + handler_key: ToVessel + label: To Vessel + output: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: FromVesselOut + label: From Vessel + - data_key: vessel + data_source: executor + data_type: resource + handler_key: ToVesselOut + label: To Vessel + placeholder_keys: + column: unilabos_devices + from_vessel: unilabos_resources + to_vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + progress: + type: number + status: + type: string + required: + - status + - progress + title: RunColumn_Feedback + type: object + goal: + properties: + column: + type: string + from_vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: from_vessel + type: object + pct1: + type: string + pct2: + type: string + ratio: + type: string + rf: + type: string + solvent1: + type: string + solvent2: + type: string + to_vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: to_vessel + type: object + required: + - from_vessel + - to_vessel + - column + - rf + - pct1 + - pct2 + - solvent1 + - solvent2 + - ratio + title: RunColumn_Goal + type: object + result: + properties: + message: + type: string + return_info: + type: string + success: + type: boolean + required: + - success + - message + - return_info + title: RunColumn_Result + type: object + required: + - goal + title: RunColumn + type: object + type: RunColumn + SeparateProtocol: + feedback: {} + goal: + from_vessel: from_vessel + product_phase: product_phase + purpose: purpose + repeats: repeats + separation_vessel: separation_vessel + settling_time: settling_time + solvent: solvent + solvent_volume: solvent_volume + stir_speed: stir_speed + stir_time: stir_time + through: through + to_vessel: to_vessel + waste_phase_to_vessel: waste_phase_to_vessel + goal_default: + from_vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + product_phase: '' + product_vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + purpose: '' + repeats: 0 + separation_vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + settling_time: 0.0 + solvent: '' + solvent_volume: '' + stir_speed: 0.0 + stir_time: 0.0 + through: '' + to_vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + volume: '' + waste_phase_to_vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + waste_vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: FromVessel + label: From Vessel + - data_key: vessel + data_source: executor + data_type: resource + handler_key: ToVessel + label: To Vessel + - data_key: solvent + data_source: handle + data_type: resource + handler_key: solvent + label: Solvent + output: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: FromVesselOut + label: From Vessel + - data_key: vessel + data_source: executor + data_type: resource + handler_key: ToVesselOut + label: To Vessel + placeholder_keys: + from_vessel: unilabos_resources + to_vessel: unilabos_resources + waste_phase_to_vessel: unilabos_resources + waste_vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + progress: + type: number + status: + type: string + required: + - status + - progress + title: Separate_Feedback + type: object + goal: + properties: + from_vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: from_vessel + type: object + product_phase: + type: string + product_vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: product_vessel + type: object + purpose: + type: string + repeats: + maximum: 2147483647 + minimum: -2147483648 + type: integer + separation_vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: separation_vessel + type: object + settling_time: + type: number + solvent: + type: string + solvent_volume: + type: string + stir_speed: + type: number + stir_time: + type: number + through: + type: string + to_vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: to_vessel + type: object + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + volume: + type: string + waste_phase_to_vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: waste_phase_to_vessel + type: object + waste_vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: waste_vessel + type: object + required: + - vessel + - purpose + - product_phase + - from_vessel + - separation_vessel + - to_vessel + - waste_phase_to_vessel + - product_vessel + - waste_vessel + - solvent + - solvent_volume + - volume + - through + - repeats + - stir_time + - stir_speed + - settling_time + title: Separate_Goal + type: object + result: + properties: + message: + type: string + return_info: + type: string + success: + type: boolean + required: + - success + - message + - return_info + title: Separate_Result + type: object + required: + - goal + title: Separate + type: object + type: Separate + StartStirProtocol: + feedback: {} + goal: + purpose: purpose + stir_speed: stir_speed + vessel: vessel + goal_default: + purpose: '' + stir_speed: 0.0 + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: Vessel + label: Vessel + output: + - data_key: vessel + data_source: executor + data_type: resource + handler_key: VesselOut + label: Vessel + placeholder_keys: + vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + current_speed: + type: number + current_status: + type: string + progress: + type: number + required: + - progress + - current_speed + - current_status + title: StartStir_Feedback + type: object + goal: + properties: + purpose: + type: string + stir_speed: + type: number + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + required: + - vessel + - stir_speed + - purpose + title: StartStir_Goal + type: object + result: + properties: + message: + type: string + return_info: + type: string + success: + type: boolean + required: + - success + - message + - return_info + title: StartStir_Result + type: object + required: + - goal + title: StartStir + type: object + type: StartStir + StirProtocol: + feedback: {} + goal: + event: event + settling_time: settling_time + stir_speed: stir_speed + stir_time: stir_time + time: time + time_spec: time_spec + vessel: vessel + goal_default: + event: '' + settling_time: '' + stir_speed: 0.0 + stir_time: 0.0 + time: '' + time_spec: '' + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: Vessel + label: Vessel + output: + - data_key: vessel + data_source: executor + data_type: resource + handler_key: VesselOut + label: Vessel + placeholder_keys: + vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + status: + type: string + required: + - status + title: Stir_Feedback + type: object + goal: + properties: + event: + type: string + settling_time: + type: string + stir_speed: + type: number + stir_time: + type: number + time: + type: string + time_spec: + type: string + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + required: + - vessel + - time + - event + - time_spec + - stir_time + - stir_speed + - settling_time + title: Stir_Goal + type: object + result: + properties: + message: + type: string + return_info: + type: string + success: + type: boolean + required: + - success + - message + - return_info + title: Stir_Result + type: object + required: + - goal + title: Stir + type: object + type: Stir + StopStirProtocol: + feedback: {} + goal: + vessel: vessel + goal_default: + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: Vessel + label: Vessel + output: + - data_key: vessel + data_source: executor + data_type: resource + handler_key: VesselOut + label: Vessel + placeholder_keys: + vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + current_status: + type: string + progress: + type: number + required: + - progress + - current_status + title: StopStir_Feedback + type: object + goal: + properties: + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + required: + - vessel + title: StopStir_Goal + type: object + result: + properties: + message: + type: string + return_info: + type: string + success: + type: boolean + required: + - success + - message + - return_info + title: StopStir_Result + type: object + required: + - goal + title: StopStir + type: object + type: StopStir + TransferProtocol: + feedback: {} + goal: + amount: amount + from_vessel: from_vessel + rinsing_repeats: rinsing_repeats + rinsing_solvent: rinsing_solvent + rinsing_volume: rinsing_volume + solid: solid + time: time + to_vessel: to_vessel + viscous: viscous + volume: volume + goal_default: + amount: '' + from_vessel: '' + rinsing_repeats: 0 + rinsing_solvent: '' + rinsing_volume: 0.0 + solid: false + time: 0.0 + to_vessel: '' + viscous: false + volume: 0.0 + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: FromVessel + label: From Vessel + - data_key: vessel + data_source: executor + data_type: resource + handler_key: ToVessel + label: To Vessel + - data_key: solvent + data_source: handle + data_type: resource + handler_key: solvent + label: Rinsing Solvent + output: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: FromVesselOut + label: From Vessel + - data_key: vessel + data_source: executor + data_type: resource + handler_key: ToVesselOut + label: To Vessel + placeholder_keys: + from_vessel: unilabos_nodes + to_vessel: unilabos_nodes + result: {} + schema: + description: '' + properties: + feedback: + properties: + current_status: + type: string + progress: + type: number + transferred_volume: + type: number + required: + - progress + - transferred_volume + - current_status + title: Transfer_Feedback + type: object + goal: + properties: + amount: + type: string + from_vessel: + type: string + rinsing_repeats: + maximum: 2147483647 + minimum: -2147483648 + type: integer + rinsing_solvent: + type: string + rinsing_volume: + type: number + solid: + type: boolean + time: + type: number + to_vessel: + type: string + viscous: + type: boolean + volume: + type: number + required: + - from_vessel + - to_vessel + - volume + - amount + - time + - viscous + - rinsing_solvent + - rinsing_volume + - rinsing_repeats + - solid + title: Transfer_Goal + type: object + result: + properties: + message: + type: string + return_info: + type: string + success: + type: boolean + required: + - success + - message + - return_info + title: Transfer_Result + type: object + required: + - goal + title: Transfer + type: object + type: Transfer + WashSolidProtocol: + feedback: {} + goal: + filtrate_vessel: filtrate_vessel + repeats: repeats + solvent: solvent + stir: stir + stir_speed: stir_speed + temp: temp + time: time + vessel: vessel + volume: volume + goal_default: + event: '' + filtrate_vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + mass: '' + repeats: 0 + repeats_spec: '' + solvent: '' + stir: false + stir_speed: 0.0 + temp: 0.0 + time: '' + vessel: + category: '' + children: [] + config: '' + data: '' + id: '' + name: '' + parent: '' + pose: + orientation: + w: 1.0 + x: 0.0 + y: 0.0 + z: 0.0 + position: + x: 0.0 + y: 0.0 + z: 0.0 + sample_id: '' + type: '' + volume: '' + volume_spec: '' + handles: + input: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: Vessel + label: Vessel + - data_key: solvent + data_source: handle + data_type: resource + handler_key: solvent + label: Solvent + - data_key: filtrate_vessel + data_source: handle + data_type: resource + handler_key: filtrate_vessel + label: Filtrate Vessel + output: + - data_key: vessel + data_source: handle + data_type: resource + handler_key: VesselOut + label: Vessel Out + - data_key: filtrate_vessel + data_source: executor + data_type: resource + handler_key: filtrate_vessel_out + label: Filtrate Vessel + placeholder_keys: + filtrate_vessel: unilabos_resources + vessel: unilabos_resources + result: {} + schema: + description: '' + properties: + feedback: + properties: + progress: + type: number + status: + type: string + required: + - status + - progress + title: WashSolid_Feedback + type: object + goal: + properties: + event: + type: string + filtrate_vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: filtrate_vessel + type: object + mass: + type: string + repeats: + maximum: 2147483647 + minimum: -2147483648 + type: integer + repeats_spec: + type: string + solvent: + type: string + stir: + type: boolean + stir_speed: + type: number + temp: + type: number + time: + type: string + vessel: + properties: + category: + type: string + children: + items: + type: string + type: array + config: + type: string + data: + type: string + id: + type: string + name: + type: string + parent: + type: string + pose: + properties: + orientation: + properties: + w: + type: number + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + - w + title: orientation + type: object + position: + properties: + x: + type: number + y: + type: number + z: + type: number + required: + - x + - y + - z + title: position + type: object + required: + - position + - orientation + title: pose + type: object + sample_id: + type: string + type: + type: string + required: + - id + - name + - sample_id + - children + - parent + - type + - category + - pose + - config + - data + title: vessel + type: object + volume: + type: string + volume_spec: + type: string + required: + - vessel + - solvent + - volume + - filtrate_vessel + - temp + - stir + - stir_speed + - time + - repeats + - volume_spec + - repeats_spec + - mass + - event + title: WashSolid_Goal + type: object + result: + properties: + message: + type: string + return_info: + type: string + success: + type: boolean + required: + - success + - message + - return_info + title: WashSolid_Result + type: object + required: + - goal + title: WashSolid + type: object + type: WashSolid + auto-create_ros_action_server: + feedback: {} + goal: {} + goal_default: + action_name: null + action_value_mapping: null + handles: {} + result: {} + schema: + description: create_ros_action_server的参数schema + properties: + feedback: {} + goal: + properties: + action_name: + type: string + action_value_mapping: + type: string + required: + - action_name + - action_value_mapping + type: object + result: {} + required: + - goal + title: create_ros_action_server参数 + type: object + type: UniLabJsonCommand + auto-execute_single_action: + feedback: {} + goal: {} + goal_default: + action_kwargs: null + action_name: null + device_id: null + handles: {} + result: {} + schema: + description: execute_single_action的参数schema + properties: + feedback: {} + goal: + properties: + action_kwargs: + type: string + action_name: + type: string + device_id: + type: string + required: + - device_id + - action_name + - action_kwargs + type: object + result: {} + required: + - goal + title: execute_single_action参数 + type: object + type: UniLabJsonCommandAsync + auto-initialize_device: + feedback: {} + goal: {} + goal_default: + device_config: null + device_id: null + handles: {} + result: {} + schema: + description: initialize_device的参数schema + properties: + feedback: {} + goal: + properties: + device_config: + type: string + device_id: + type: string + required: + - device_id + - device_config + type: object + result: {} + required: + - goal + title: initialize_device参数 + type: object + type: UniLabJsonCommand + module: unilabos.ros.nodes.presets.workstation:ROS2WorkstationNode + status_types: {} + type: ros2 + config_info: [] + description: Workstation + handles: [] + icon: '' + init_param_schema: + config: + properties: + action_value_mappings: + type: object + children: + type: object + device_id: + type: string + driver_instance: + type: string + hardware_interface: + type: object + print_publish: + default: true + type: string + protocol_type: + items: + type: string + type: array + resource_tracker: + type: string + status_types: + type: object + required: + - protocol_type + - children + - driver_instance + - device_id + - status_types + - action_value_mappings + - hardware_interface + type: object + data: + properties: {} + required: [] + type: object + version: 1.0.0 +workstation.example: + category: + - work_station + class: + action_value_mappings: {} + module: unilabos.devices.workstation.workstation_base:WorkstationExample + status_types: {} + type: python + config_info: [] + description: '' + handles: [] + icon: '' + init_param_schema: + config: + properties: + station_resource: + type: object + required: + - station_resource + type: object + data: + properties: {} + required: [] + type: object + version: 1.0.0 From 190b2d251837fdef5a26b17cae865edee8fb7469 Mon Sep 17 00:00:00 2001 From: calvincao Date: Mon, 27 Oct 2025 11:43:03 +0800 Subject: [PATCH 021/104] =?UTF-8?q?=E6=B8=85=E7=90=86=E6=89=A3=E7=94=B5?= =?UTF-8?q?=E4=B8=8D=E5=BF=85=E8=A6=81=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coin_cell_assembly/__init__.py | 0 .../button_battery_station.py | 1006 -- .../button_battery_station_big.py | 1412 -- ..._battery_station_resources_unilab_big.json | 1895 -- .../coin_cell_assembly/cellconfig.py | 114 - .../coin_cell_assembly/celljson.json | 1332 -- .../coin_cell_assembly/coin_cell_assembly.py | 1163 -- .../coin_cell_assembly_0910.csv | 44 - .../coin_cell_assembly_a.csv | 46 - .../coin_cell_assembly_old.py | 1149 -- .../coin_cell_assembly_system_wuliao.py | 135 - .../coin_cell_assembly/new_cellconfig.json | 1925 -- .../coin_cell_assembly/new_cellconfig2.json | 1925 -- .../coin_cell_assembly/new_cellconfig3.json | 1925 -- .../coin_cell_assembly/new_cellconfig3a.json | 1956 --- .../coin_cell_assembly/new_cellconfig3b.json | 2589 --- .../coin_cell_assembly/new_cellconfig3c.json | 691 - .../coin_cell_assembly/new_cellconfig4.json | 14472 ---------------- .../workstation/coin_cell_assembly/test.ipynb | 750 - .../coin_cell_assembly/work_station.yaml | 6674 ------- 20 files changed, 41203 deletions(-) delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/__init__.py delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/button_battery_station_big.py delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/button_battery_station_resources_unilab_big.json delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/cellconfig.py delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/celljson.json delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_0910.csv delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_old.py delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_system_wuliao.py delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/new_cellconfig.json delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/new_cellconfig2.json delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3.json delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3a.json delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3b.json delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/new_cellconfig4.json delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/test.ipynb delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/work_station.yaml diff --git a/unilabos/devices/workstation/coin_cell_assembly/__init__.py b/unilabos/devices/workstation/coin_cell_assembly/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py b/unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py deleted file mode 100644 index eae09b8..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py +++ /dev/null @@ -1,1006 +0,0 @@ -""" -纽扣电池组装工作站物料类定义 -Button Battery Assembly Station Resource Classes -""" - -from __future__ import annotations - -from collections import OrderedDict -from typing import Any, Dict, List, Optional, TypedDict, Union, cast - -from pylabrobot.resources.coordinate import Coordinate -from pylabrobot.resources.container import Container -from pylabrobot.resources.deck import Deck -from pylabrobot.resources.itemized_resource import ItemizedResource -from pylabrobot.resources.resource import Resource -from pylabrobot.resources.resource_stack import ResourceStack -from pylabrobot.resources.tip_rack import TipRack, TipSpot -from pylabrobot.resources.trash import Trash -from pylabrobot.resources.utils import create_ordered_items_2d - - -class ElectrodeSheetState(TypedDict): - diameter: float # 直径 (mm) - thickness: float # 厚度 (mm) - mass: float # 质量 (g) - material_type: str # 材料类型(正极、负极、隔膜、弹片、垫片、铝箔等) - height: float - electrolyte_name: str - data_electrolyte_code: str - open_circuit_voltage: float - assembly_pressure: float - electrolyte_volume: float - - info: Optional[str] # 附加信息 - -class ElectrodeSheet(Resource): - """极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料""" - - def __init__( - self, - name: str = "极片", - size_x=10, - size_y=10, - size_z=10, - category: str = "electrode_sheet", - model: Optional[str] = None, - ): - """初始化极片 - - Args: - name: 极片名称 - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - category=category, - model=model, - ) - self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState( - diameter=14, - thickness=0.1, - mass=0.5, - material_type="copper", - info=None - ) - - # TODO: 这个还要不要?给self._unilabos_state赋值的? - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - #序列化 - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - -# TODO: 这个应该只能放一个极片 -class MaterialHoleState(TypedDict): - diameter: int - depth: int - max_sheets: int - info: Optional[str] # 附加信息 - -class MaterialHole(Resource): - """料板洞位类""" - children: List[ElectrodeSheet] = [] - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - category: str = "material_hole", - **kwargs - ): - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - category=category, - ) - self._unilabos_state: MaterialHoleState = MaterialHoleState( - diameter=20, - depth=10, - max_sheets=1, - info=None - ) - - def get_all_sheet_info(self): - info_list = [] - for sheet in self.children: - info_list.append(sheet._unilabos_state["info"]) - return info_list - - #这个函数函数好像没用,一般不会集中赋值质量 - def set_all_sheet_mass(self): - for sheet in self.children: - sheet._unilabos_state["mass"] = 0.5 # 示例:设置质量为0.5g - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - #移动极片前先取出对象 - def get_sheet_with_name(self, name: str) -> Optional[ElectrodeSheet]: - for sheet in self.children: - if sheet.name == name: - return sheet - return None - - def has_electrode_sheet(self) -> bool: - """检查洞位是否有极片""" - return len(self.children) > 0 - - def assign_child_resource( - self, - resource: ElectrodeSheet, - location: Optional[Coordinate], - reassign: bool = True, - ): - """放置极片""" - # TODO: 这里要改,diameter找不到,加入._unilabos_state后应该没问题 - #if resource._unilabos_state["diameter"] > self._unilabos_state["diameter"]: - # raise ValueError(f"极片直径 {resource._unilabos_state['diameter']} 超过洞位直径 {self._unilabos_state['diameter']}") - #if len(self.children) >= self._unilabos_state["max_sheets"]: - # raise ValueError(f"洞位已满,无法放置更多极片") - super().assign_child_resource(resource, location, reassign) - - # 根据children的编号取物料对象。 - def get_electrode_sheet_info(self, index: int) -> ElectrodeSheet: - return self.children[index] - - - -class MaterialPlateState(TypedDict): - hole_spacing_x: float - hole_spacing_y: float - hole_diameter: float - info: Optional[str] # 附加信息 - -class MaterialPlate(ItemizedResource[MaterialHole]): - """料板类 - 4x4个洞位,每个洞位放1个极片""" - - children: List[MaterialHole] - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - ordered_items: Optional[Dict[str, MaterialHole]] = None, - ordering: Optional[OrderedDict[str, str]] = None, - category: str = "material_plate", - model: Optional[str] = None, - fill: bool = False - ): - """初始化料板 - - Args: - name: 料板名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - hole_diameter: 洞直径 (mm) - hole_depth: 洞深度 (mm) - hole_spacing_x: X方向洞位间距 (mm) - hole_spacing_y: Y方向洞位间距 (mm) - number: 编号 - category: 类别 - model: 型号 - """ - self._unilabos_state: MaterialPlateState = MaterialPlateState( - hole_spacing_x=24.0, - hole_spacing_y=24.0, - hole_diameter=20.0, - info="", - ) - # 创建4x4的洞位 - # TODO: 这里要改,对应不同形状 - holes = create_ordered_items_2d( - klass=MaterialHole, - num_items_x=4, - num_items_y=4, - dx=(size_x - 4 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中 - dy=(size_y - 4 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中 - dz=size_z, - item_dx=self._unilabos_state["hole_spacing_x"], - item_dy=self._unilabos_state["hole_spacing_y"], - size_x = 16, - size_y = 16, - size_z = 16, - ) - if fill: - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=holes, - category=category, - model=model, - ) - else: - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=ordered_items, - ordering=ordering, - category=category, - model=model, - ) - - def update_locations(self): - # TODO:调多次相加 - holes = create_ordered_items_2d( - klass=MaterialHole, - num_items_x=4, - num_items_y=4, - dx=(self._size_x - 3 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中 - dy=(self._size_y - 3 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中 - dz=self._size_z, - item_dx=self._unilabos_state["hole_spacing_x"], - item_dy=self._unilabos_state["hole_spacing_y"], - size_x = 1, - size_y = 1, - size_z = 1, - ) - for item, original_item in zip(holes.items(), self.children): - original_item.location = item[1].location - - -class PlateSlot(ResourceStack): - """板槽位类 - 1个槽上能堆放8个板,移板只能操作最上方的板""" - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - max_plates: int = 8, - category: str = "plate_slot", - model: Optional[str] = None - ): - """初始化板槽位 - - Args: - name: 槽位名称 - max_plates: 最大板数量 - category: 类别 - """ - super().__init__( - name=name, - direction="z", # Z方向堆叠 - resources=[], - ) - self.max_plates = max_plates - self.category = category - - def can_add_plate(self) -> bool: - """检查是否可以添加板""" - return len(self.children) < self.max_plates - - def add_plate(self, plate: MaterialPlate) -> None: - """添加料板""" - if not self.can_add_plate(): - raise ValueError(f"槽位 {self.name} 已满,无法添加更多板") - self.assign_child_resource(plate) - - def get_top_plate(self) -> MaterialPlate: - """获取最上方的板""" - if len(self.children) == 0: - raise ValueError(f"槽位 {self.name} 为空") - return cast(MaterialPlate, self.get_top_item()) - - def take_top_plate(self) -> MaterialPlate: - """取出最上方的板""" - top_plate = self.get_top_plate() - self.unassign_child_resource(top_plate) - return top_plate - - def can_access_for_picking(self) -> bool: - """检查是否可以进行取料操作(只有最上方的板能进行取料操作)""" - return len(self.children) > 0 - - def serialize(self) -> dict: - return { - **super().serialize(), - "max_plates": self.max_plates, - } - - -class ClipMagazineHole(Container): - """子弹夹洞位类""" - - def __init__( - self, - name: str, - diameter: float, - depth: float, - max_sheets: int = 100, - category: str = "clip_magazine_hole", - ): - """初始化子弹夹洞位 - - Args: - name: 洞位名称 - diameter: 洞直径 (mm) - depth: 洞深度 (mm) - max_sheets: 最大极片数量 - category: 类别 - """ - super().__init__( - name=name, - size_x=diameter, - size_y=diameter, - size_z=depth, - category=category, - ) - self.diameter = diameter - self.depth = depth - self.max_sheets = max_sheets - self._sheets: List[ElectrodeSheet] = [] - - def can_add_sheet(self, sheet: ElectrodeSheet) -> bool: - """检查是否可以添加极片""" - return (len(self._sheets) < self.max_sheets and - sheet.diameter <= self.diameter) - - def add_sheet(self, sheet: ElectrodeSheet) -> None: - """添加极片""" - if not self.can_add_sheet(sheet): - raise ValueError(f"无法向洞位 {self.name} 添加极片") - self._sheets.append(sheet) - - def take_sheet(self) -> ElectrodeSheet: - """取出极片""" - if len(self._sheets) == 0: - raise ValueError(f"洞位 {self.name} 没有极片") - return self._sheets.pop() - - def get_sheet_count(self) -> int: - """获取极片数量""" - return len(self._sheets) - - def serialize_state(self) -> Dict[str, Any]: - return { - "sheet_count": len(self._sheets), - "sheets": [sheet.serialize() for sheet in self._sheets], - } - -# TODO: 这个要改 -class ClipMagazine(Resource): - """子弹夹类 - 有6个洞位,每个洞位放多个极片""" - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - hole_spacing: float = 25.0, - max_sheets_per_hole: int = 100, - category: str = "clip_magazine", - model: Optional[str] = None, - ): - """初始化子弹夹 - - Args: - name: 子弹夹名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - hole_diameter: 洞直径 (mm) - hole_depth: 洞深度 (mm) - hole_spacing: 洞位间距 (mm) - max_sheets_per_hole: 每个洞位最大极片数量 - category: 类别 - model: 型号 - """ - # 创建6个洞位,排成2x3布局 - holes = create_ordered_items_2d( - klass=ClipMagazineHole, - num_items_x=3, - num_items_y=2, - dx=(size_x - 2 * hole_spacing) / 2, # 居中 - dy=(size_y - hole_spacing) / 2, # 居中 - dz=size_z - 0, - item_dx=hole_spacing, - item_dy=hole_spacing, - diameter=0, - depth=0, - ) - - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=holes, - category=category, - model=model, - ) - - self.hole_diameter = hole_diameter - self.hole_depth = hole_depth - self.max_sheets_per_hole = max_sheets_per_hole - - def serialize(self) -> dict: - return { - **super().serialize(), - "hole_diameter": self.hole_diameter, - "hole_depth": self.hole_depth, - "max_sheets_per_hole": self.max_sheets_per_hole, - } -#是一种类型注解,不用self -class BatteryState(TypedDict): - """电池状态字典""" - diameter: float - height: float - assembly_pressure: float - electrolyte_volume: float - electrolyte_name: str - -class Battery(Resource): - """电池类 - 可容纳极片""" - children: List[ElectrodeSheet] = [] - - def __init__( - self, - name: str, - size_x=1, - size_y=1, - size_z=1, - category: str = "battery", - ): - """初始化电池 - - Args: - name: 电池名称 - diameter: 直径 (mm) - height: 高度 (mm) - max_volume: 最大容量 (μL) - barcode: 二维码编号 - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=1, - size_y=1, - size_z=1, - category=category, - ) - self._unilabos_state: BatteryState = BatteryState( - diameter = 1.0, - height = 1.0, - assembly_pressure = 1.0, - electrolyte_volume = 1.0, - electrolyte_name = "DP001" - ) - - def add_electrolyte_with_bottle(self, bottle: Bottle) -> bool: - to_add_name = bottle._unilabos_state["electrolyte_name"] - if bottle.aspirate_electrolyte(10): - if self.add_electrolyte(to_add_name, 10): - pass - else: - bottle._unilabos_state["electrolyte_volume"] += 10 - - def set_electrolyte(self, name: str, volume: float) -> None: - """设置电解液信息""" - self._unilabos_state["electrolyte_name"] = name - self._unilabos_state["electrolyte_volume"] = volume - #这个应该没用,不会有加了后再加的事情 - def add_electrolyte(self, name: str, volume: float) -> bool: - """添加电解液信息""" - if name != self._unilabos_state["electrolyte_name"]: - return False - self._unilabos_state["electrolyte_volume"] += volume - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - -# 电解液作为属性放进去 - -class BatteryPressSlotState(TypedDict): - """电池状态字典""" - diameter: float =20.0 - depth: float = 4.0 - -class BatteryPressSlot(Resource): - """电池压制槽类 - 设备,可容纳一个电池""" - children: List[Battery] = [] - - def __init__( - self, - name: str = "BatteryPressSlot", - category: str = "battery_press_slot", - ): - """初始化电池压制槽 - - Args: - name: 压制槽名称 - diameter: 直径 (mm) - depth: 深度 (mm) - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=10, - size_y=12, - size_z=13, - category=category, - ) - self._unilabos_state: BatteryPressSlotState = BatteryPressSlotState() - - def has_battery(self) -> bool: - """检查是否有电池""" - return len(self.children) > 0 - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - - def assign_child_resource( - self, - resource: Battery, - location: Optional[Coordinate], - reassign: bool = True, - ): - """放置极片""" - # TODO: 让高京看下槽位只有一个电池时是否这么写。 - if self.has_battery(): - raise ValueError(f"槽位已含有一个电池,无法再放置其他电池") - super().assign_child_resource(resource, location, reassign) - - # 根据children的编号取物料对象。 - def get_battery_info(self, index: int) -> Battery: - return self.children[0] - -# TODO:这个移液枪架子看一下从哪继承 -class TipBox64State(TypedDict): - """电池状态字典""" - tip_diameter: float = 5.0 - tip_length: float = 50.0 - with_tips: bool = True - -class TipBox64(TipRack): - """64孔枪头盒类""" - - children: List[TipSpot] = [] - def __init__( - self, - name: str, - size_x: float = 127.8, - size_y: float = 85.5, - size_z: float = 60.0, - category: str = "tip_box_64", - model: Optional[str] = None, - ): - """初始化64孔枪头盒 - - Args: - name: 枪头盒名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - tip_diameter: 枪头直径 (mm) - tip_length: 枪头长度 (mm) - category: 类别 - model: 型号 - with_tips: 是否带枪头 - """ - from pylabrobot.resources.tip import Tip - - # 创建8x8=64个枪头位 - def make_tip(): - return Tip( - has_filter=False, - total_tip_length=20.0, - maximal_volume=1000, # 1mL - fitting_depth=8.0, - ) - - tip_spots = create_ordered_items_2d( - klass=TipSpot, - num_items_x=8, - num_items_y=8, - dx=8.0, - dy=8.0, - dz=0.0, - item_dx=9.0, - item_dy=9.0, - size_x=10, - size_y=10, - size_z=0.0, - make_tip=make_tip, - ) - self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate() - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=tip_spots, - category=category, - model=model, - with_tips=True, - ) - - - -class WasteTipBoxstate(TypedDict): - """"废枪头盒状态字典""" - max_tips: int = 100 - tip_count: int = 0 - -#枪头不是一次性的(同一溶液则反复使用),根据寄存器判断 -class WasteTipBox(Trash): - """废枪头盒类 - 100个枪头容量""" - - def __init__( - self, - name: str, - size_x: float = 127.8, - size_y: float = 85.5, - size_z: float = 60.0, - category: str = "waste_tip_box", - model: Optional[str] = None, - ): - """初始化废枪头盒 - - Args: - name: 废枪头盒名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - max_tips: 最大枪头容量 - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - category=category, - model=model, - ) - self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate() - - def add_tip(self) -> None: - """添加废枪头""" - if self._unilabos_state["tip_count"] >= self._unilabos_state["max_tips"]: - raise ValueError(f"废枪头盒 {self.name} 已满") - self._unilabos_state["tip_count"] += 1 - - def get_tip_count(self) -> int: - """获取枪头数量""" - return self._unilabos_state["tip_count"] - - def empty(self) -> None: - """清空废枪头盒""" - self._unilabos_state["tip_count"] = 0 - - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - - -class BottleRackState(TypedDict): - """ bottle_diameter: 瓶子直径 (mm) - bottle_height: 瓶子高度 (mm) - position_spacing: 位置间距 (mm)""" - bottle_diameter: float - bottle_height: float - name_to_index: dict - - - -class BottleRack(Resource): - """瓶架类 - 12个待配位置+12个已配位置""" - children: List[Bottle] = [] - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - category: str = "bottle_rack", - model: Optional[str] = None, - ): - """初始化瓶架 - - Args: - name: 瓶架名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - category=category, - model=model, - ) - # TODO: 添加瓶位坐标映射 - self.index_to_pos = { - 0: Coordinate.zero(), - 1: Coordinate(x=1, y=2, z=3) # 添加 - } - self.name_to_index = {} - self.name_to_pos = {} - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - - # TODO: 这里有些问题要重新写一下 - def assign_child_resource(self, resource: Bottle, location=Coordinate.zero(), reassign = True): - assert len(self.children) <= 12, "瓶架已满,无法添加更多瓶子" - index = len(self.children) - location = Coordinate(x=20 + (index % 4) * 15, y=20 + (index // 4) * 15, z=0) - self.name_to_pos[resource.name] = location - self.name_to_index[resource.name] = index - return super().assign_child_resource(resource, location, reassign) - - def assign_child_resource_by_index(self, resource: Bottle, index: int): - assert 0 <= index < 12, "无效的瓶子索引" - self.name_to_index[resource.name] = index - location = self.index_to_pos[index] - return super().assign_child_resource(resource, location) - - def unassign_child_resource(self, resource: Bottle): - super().unassign_child_resource(resource) - self.index_to_pos.pop(self.name_to_index.pop(resource.name, None), None) - - # def serialize(self): - # self.children.sort(key=lambda x: self.name_to_index.get(x.name, 0)) - # return super().serialize() - - -class BottleState(TypedDict): - diameter: float - height: float - electrolyte_name: str - electrolyte_volume: float - max_volume: float - -class Bottle(Resource): - """瓶子类 - 容纳电解液""" - - def __init__( - self, - name: str, - category: str = "bottle", - ): - """初始化瓶子 - - Args: - name: 瓶子名称 - diameter: 直径 (mm) - height: 高度 (mm) - max_volume: 最大体积 (μL) - barcode: 二维码 - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=1, - size_y=1, - size_z=1, - category=category, - ) - self._unilabos_state: BottleState = BottleState() - - def aspirate_electrolyte(self, volume: float) -> bool: - current_volume = self._unilabos_state["electrolyte_volume"] - assert current_volume > volume, f"Cannot aspirate {volume}μL, only {current_volume}μL available." - self._unilabos_state["electrolyte_volume"] -= volume - return True - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - -class CoincellDeck(Deck): - """纽扣电池组装工作站台面类""" - - def __init__( - self, - name: str = "coin_cell_deck", - size_x: float = 1620.0, # 3.66m - size_y: float = 1270.0, # 1.23m - size_z: float = 500.0, - origin: Coordinate = Coordinate(0, 0, 0), - category: str = "coin_cell_deck", - ): - """初始化纽扣电池组装工作站台面 - - Args: - name: 台面名称 - size_x: 长度 (mm) - 3.66m - size_y: 宽度 (mm) - 1.23m - size_z: 高度 (mm) - origin: 原点坐标 - category: 类别 - """ - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - origin=origin, - category=category, - ) - -#if __name__ == "__main__": -# # 转移极片的测试代码 -# deck = CoincellDeck("coin_cell_deck") -# ban_cao_wei = PlateSlot("ban_cao_wei", max_plates=8) -# deck.assign_child_resource(ban_cao_wei, Coordinate(x=0, y=0, z=0)) -# -# plate_1 = MaterialPlate("plate_1", 1,1,1, fill=True) -# for i, hole in enumerate(plate_1.children): -# sheet = ElectrodeSheet(f"hole_{i}_sheet_1") -# sheet._unilabos_state = { -# "diameter": 14, -# "info": "NMC", -# "mass": 5.0, -# "material_type": "positive_electrode", -# "thickness": 0.1 -# } -# hole._unilabos_state = { -# "depth": 1.0, -# "diameter": 14, -# "info": "", -# "max_sheets": 1 -# } -# hole.assign_child_resource(sheet, Coordinate.zero()) -# plate_1._unilabos_state = { -# "hole_spacing_x": 20.0, -# "hole_spacing_y": 20.0, -# "hole_diameter": 5, -# "info": "这是第一块料板" -# } -# plate_1.update_locations() -# ban_cao_wei.assign_child_resource(plate_1, Coordinate.zero()) -# # zi_dan_jia = ClipMagazine("zi_dan_jia", 1, 1, 1) -# # deck.assign_child_resource(ban_cao_wei, Coordinate(x=200, y=200, z=0)) -# -# from unilabos.resources.graphio import * -# A = tree_to_list([resource_plr_to_ulab(deck)]) -# with open("test.json", "w") as f: -# json.dump(A, f) -# -# -#def get_plate_with_14mm_hole(name=""): -# plate = MaterialPlate(name=name) -# for i in range(4): -# for j in range(4): -# hole = MaterialHole(f"{i+1}x{j+1}") -# hole._unilabos_state["diameter"] = 14 -# hole._unilabos_state["max_sheets"] = 1 -# plate.assign_child_resource(hole) -# return plate - -import json - -if __name__ == "__main__": - #electrode1 = BatteryPressSlot() - #print(electrode1.get_size_x()) - #print(electrode1.get_size_y()) - #print(electrode1.get_size_z()) - #jipian = ElectrodeSheet() - #jipian._unilabos_state["diameter"] = 18 - #print(jipian.serialize()) - #print(jipian.serialize_state()) - - deck = CoincellDeck(size_x=1000, - size_y=1000, - size_z=900) - - #liaopan = TipBox64(name="liaopan") - - #创建一个4*4的物料板 - liaopan1 = MaterialPlate(name="liaopan1", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) - #把物料板放到桌子上 - deck.assign_child_resource(liaopan1, Coordinate(x=0, y=0, z=0)) - #创建一个极片 - for i in range(16): - jipian = ElectrodeSheet(name=f"jipian1_{i}", size_x= 12, size_y=12, size_z=0.1) - liaopan1.children[i].assign_child_resource(jipian, location=None) -# - #创建一个4*4的物料板 - liaopan2 = MaterialPlate(name="liaopan2", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) - #把物料板放到桌子上 - deck.assign_child_resource(liaopan2, Coordinate(x=500, y=0, z=0)) - - #创建一个4*4的物料板 - liaopan3 = MaterialPlate(name="电池料盘", size_x=120.8, size_y=160.5, size_z=10.0, fill=True) - #把物料板放到桌子上 - deck.assign_child_resource(liaopan3, Coordinate(x=100, y=100, z=0)) - - - - #liaopan.children[3].assign_child_resource(jipian, location=None) - print(deck) - - - from unilabos.resources.graphio import convert_resources_from_type - from unilabos.config.config import BasicConfig - BasicConfig.ak = "4d5ce6ae-7234-4639-834e-93899b9caf94" - BasicConfig.sk = "505d3b0a-620e-459a-9905-1efcffce382a" - from unilabos.app.web.client import http_client - - resources = convert_resources_from_type([deck], [Resource]) - json.dump({"nodes": resources, "links": []}, open("button_battery_station_resources_unilab.json", "w"), indent=2) - - - #print(resources) - http_client.remote_addr = "https://uni-lab.test.bohrium.com/api/v1" - - http_client.resource_add(resources) \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/button_battery_station_big.py b/unilabos/devices/workstation/coin_cell_assembly/button_battery_station_big.py deleted file mode 100644 index 765328f..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/button_battery_station_big.py +++ /dev/null @@ -1,1412 +0,0 @@ -""" -纽扣电池组装工作站物料类定义 -Button Battery Assembly Station Resource Classes -""" - -from __future__ import annotations - -from collections import OrderedDict -from typing import Any, Dict, List, Optional, TypedDict, Union, cast - -from pylabrobot.resources.coordinate import Coordinate -from pylabrobot.resources.container import Container -from pylabrobot.resources.deck import Deck -from pylabrobot.resources.itemized_resource import ItemizedResource -from pylabrobot.resources.resource import Resource -from pylabrobot.resources.resource_stack import ResourceStack -from pylabrobot.resources.tip_rack import TipRack, TipSpot -from pylabrobot.resources.trash import Trash -from pylabrobot.resources.utils import create_ordered_items_2d - - -class ElectrodeSheetState(TypedDict): - diameter: float # 直径 (mm) - thickness: float # 厚度 (mm) - mass: float # 质量 (g) - material_type: str # 材料类型(正极、负极、隔膜、弹片、垫片、铝箔等) - info: Optional[str] # 附加信息 - -class ElectrodeSheet(Resource): - """极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料""" - - def __init__( - self, - name: str = "极片", - size_x=10, - size_y=10, - size_z=10, - category: str = "electrode_sheet", - model: Optional[str] = None, - ): - """初始化极片 - - Args: - name: 极片名称 - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - category=category, - model=model, - ) - self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState( - diameter=14, - thickness=0.1, - mass=0.5, - material_type="copper", - info=None - ) - - # TODO: 这个还要不要?给self._unilabos_state赋值的? - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - #序列化 - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - -# TODO: 这个应该只能放一个极片 -class MaterialHoleState(TypedDict): - diameter: int - depth: int - max_sheets: int - info: Optional[str] # 附加信息 - -class MaterialHole(Resource): - """料板洞位类""" - children: List[ElectrodeSheet] = [] - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - category: str = "material_hole", - **kwargs - ): - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - category=category, - ) - self._unilabos_state: MaterialHoleState = MaterialHoleState( - diameter=20, - depth=10, - max_sheets=1, - info=None - ) - - def get_all_sheet_info(self): - info_list = [] - for sheet in self.children: - info_list.append(sheet._unilabos_state["info"]) - return info_list - - #这个函数函数好像没用,一般不会集中赋值质量 - def set_all_sheet_mass(self): - for sheet in self.children: - sheet._unilabos_state["mass"] = 0.5 # 示例:设置质量为0.5g - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - #移动极片前先取出对象 - def get_sheet_with_name(self, name: str) -> Optional[ElectrodeSheet]: - for sheet in self.children: - if sheet.name == name: - return sheet - return None - - def has_electrode_sheet(self) -> bool: - """检查洞位是否有极片""" - return len(self.children) > 0 - - def assign_child_resource( - self, - resource: ElectrodeSheet, - location: Optional[Coordinate], - reassign: bool = True, - ): - """放置极片""" - # TODO: 这里要改,diameter找不到,加入._unilabos_state后应该没问题 - if resource._unilabos_state["diameter"] > self._unilabos_state["diameter"]: - raise ValueError(f"极片直径 {resource._unilabos_state['diameter']} 超过洞位直径 {self._unilabos_state['diameter']}") - if len(self.children) >= self._unilabos_state["max_sheets"]: - raise ValueError(f"洞位已满,无法放置更多极片") - super().assign_child_resource(resource, location, reassign) - - # 根据children的编号取物料对象。 - def get_electrode_sheet_info(self, index: int) -> ElectrodeSheet: - return self.children[index] - - - -class MaterialPlateState(TypedDict): - hole_spacing_x: float - hole_spacing_y: float - hole_diameter: float - info: Optional[str] # 附加信息 - - - -class MaterialPlate(ItemizedResource[MaterialHole]): - """料板类 - 4x4个洞位,每个洞位放1个极片""" - - children: List[MaterialHole] - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - ordered_items: Optional[Dict[str, MaterialHole]] = None, - ordering: Optional[OrderedDict[str, str]] = None, - category: str = "material_plate", - model: Optional[str] = None, - fill: bool = False - ): - """初始化料板 - - Args: - name: 料板名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - hole_diameter: 洞直径 (mm) - hole_depth: 洞深度 (mm) - hole_spacing_x: X方向洞位间距 (mm) - hole_spacing_y: Y方向洞位间距 (mm) - number: 编号 - category: 类别 - model: 型号 - """ - self._unilabos_state: MaterialPlateState = MaterialPlateState( - hole_spacing_x=24.0, - hole_spacing_y=24.0, - hole_diameter=20.0, - info="", - ) - # 创建4x4的洞位 - # TODO: 这里要改,对应不同形状 - holes = create_ordered_items_2d( - klass=MaterialHole, - num_items_x=4, - num_items_y=4, - dx=(size_x - 4 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中 - dy=(size_y - 4 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中 - dz=size_z, - item_dx=self._unilabos_state["hole_spacing_x"], - item_dy=self._unilabos_state["hole_spacing_y"], - size_x = 16, - size_y = 16, - size_z = 16, - ) - if fill: - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=holes, - category=category, - model=model, - ) - else: - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=ordered_items, - ordering=ordering, - category=category, - model=model, - ) - - def update_locations(self): - # TODO:调多次相加 - holes = create_ordered_items_2d( - klass=MaterialHole, - num_items_x=4, - num_items_y=4, - dx=(self._size_x - 3 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中 - dy=(self._size_y - 3 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中 - dz=self._size_z, - item_dx=self._unilabos_state["hole_spacing_x"], - item_dy=self._unilabos_state["hole_spacing_y"], - size_x = 1, - size_y = 1, - size_z = 1, - ) - for item, original_item in zip(holes.items(), self.children): - original_item.location = item[1].location - - -class PlateSlot(ResourceStack): - """板槽位类 - 1个槽上能堆放8个板,移板只能操作最上方的板""" - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - max_plates: int = 8, - category: str = "plate_slot", - model: Optional[str] = None - ): - """初始化板槽位 - - Args: - name: 槽位名称 - max_plates: 最大板数量 - category: 类别 - """ - super().__init__( - name=name, - direction="z", # Z方向堆叠 - resources=[], - ) - self.max_plates = max_plates - self.category = category - - def can_add_plate(self) -> bool: - """检查是否可以添加板""" - return len(self.children) < self.max_plates - - def add_plate(self, plate: MaterialPlate) -> None: - """添加料板""" - if not self.can_add_plate(): - raise ValueError(f"槽位 {self.name} 已满,无法添加更多板") - self.assign_child_resource(plate) - - def get_top_plate(self) -> MaterialPlate: - """获取最上方的板""" - if len(self.children) == 0: - raise ValueError(f"槽位 {self.name} 为空") - return cast(MaterialPlate, self.get_top_item()) - - def take_top_plate(self) -> MaterialPlate: - """取出最上方的板""" - top_plate = self.get_top_plate() - self.unassign_child_resource(top_plate) - return top_plate - - def can_access_for_picking(self) -> bool: - """检查是否可以进行取料操作(只有最上方的板能进行取料操作)""" - return len(self.children) > 0 - - def serialize(self) -> dict: - return { - **super().serialize(), - "max_plates": self.max_plates, - } - - -class ClipMagazineHole(Container): - """子弹夹洞位类""" - children: List[ElectrodeSheet] = [] - def __init__( - self, - name: str, - diameter: float, - depth: float, - category: str = "clip_magazine_hole", - ): - """初始化子弹夹洞位 - - Args: - name: 洞位名称 - diameter: 洞直径 (mm) - depth: 洞深度 (mm) - category: 类别 - """ - super().__init__( - name=name, - size_x=diameter, - size_y=diameter, - size_z=depth, - category=category, - ) - self.diameter = diameter - self.depth = depth - - def can_add_sheet(self, sheet: ElectrodeSheet) -> bool: - """检查是否可以添加极片 - - 根据洞的深度和极片的厚度来判断是否可以添加极片 - """ - # 检查极片直径是否适合洞的直径 - if sheet._unilabos_state["diameter"] > self.diameter: - return False - - # 计算当前已添加极片的总厚度 - current_thickness = sum(s._unilabos_state["thickness"] for s in self.children) - - # 检查添加新极片后总厚度是否超过洞的深度 - if current_thickness + sheet._unilabos_state["thickness"] > self.depth: - return False - - return True - - - def assign_child_resource( - self, - resource: ElectrodeSheet, - location: Optional[Coordinate] = None, - reassign: bool = True, - ): - """放置极片到洞位中 - - Args: - resource: 要放置的极片 - location: 极片在洞位中的位置(对于洞位,通常为None) - reassign: 是否允许重新分配 - """ - # 检查是否可以添加极片 - if not self.can_add_sheet(resource): - raise ValueError(f"无法向洞位 {self.name} 添加极片:直径或厚度不匹配") - - # 调用父类方法实际执行分配 - super().assign_child_resource(resource, location, reassign) - - def unassign_child_resource(self, resource: ElectrodeSheet): - """从洞位中移除极片 - - Args: - resource: 要移除的极片 - """ - if resource not in self.children: - raise ValueError(f"极片 {resource.name} 不在洞位 {self.name} 中") - - # 调用父类方法实际执行移除 - super().unassign_child_resource(resource) - - - - def serialize_state(self) -> Dict[str, Any]: - return { - "sheet_count": len(self.children), - "sheets": [sheet.serialize() for sheet in self.children], - } -class ClipMagazine_four(ItemizedResource[ClipMagazineHole]): - """子弹夹类 - 有4个洞位,每个洞位放多个极片""" - children: List[ClipMagazineHole] - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - hole_diameter: float = 14.0, - hole_depth: float = 10.0, - hole_spacing: float = 25.0, - max_sheets_per_hole: int = 100, - category: str = "clip_magazine_four", - model: Optional[str] = None, - ): - """初始化子弹夹 - - Args: - name: 子弹夹名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - hole_diameter: 洞直径 (mm) - hole_depth: 洞深度 (mm) - hole_spacing: 洞位间距 (mm) - max_sheets_per_hole: 每个洞位最大极片数量 - category: 类别 - model: 型号 - """ - # 创建4个洞位,排成2x2布局 - holes = create_ordered_items_2d( - klass=ClipMagazineHole, - num_items_x=2, - num_items_y=2, - dx=(size_x - 2 * hole_spacing) / 2, # 居中 - dy=(size_y - hole_spacing) / 2, # 居中 - dz=size_z - 0, - item_dx=hole_spacing, - item_dy=hole_spacing, - diameter=hole_diameter, - depth=hole_depth, - ) - - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=holes, - category=category, - model=model, - ) - - # 保存洞位的直径和深度 - self.hole_diameter = hole_diameter - self.hole_depth = hole_depth - self.max_sheets_per_hole = max_sheets_per_hole - - def serialize(self) -> dict: - return { - **super().serialize(), - "hole_diameter": self.hole_diameter, - "hole_depth": self.hole_depth, - "max_sheets_per_hole": self.max_sheets_per_hole, - } -# TODO: 这个要改 -class ClipMagazine(ItemizedResource[ClipMagazineHole]): - """子弹夹类 - 有6个洞位,每个洞位放多个极片""" - children: List[ClipMagazineHole] - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - hole_diameter: float = 14.0, - hole_depth: float = 10.0, - hole_spacing: float = 25.0, - max_sheets_per_hole: int = 100, - category: str = "clip_magazine", - model: Optional[str] = None, - ): - """初始化子弹夹 - - Args: - name: 子弹夹名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - hole_diameter: 洞直径 (mm) - hole_depth: 洞深度 (mm) - hole_spacing: 洞位间距 (mm) - max_sheets_per_hole: 每个洞位最大极片数量 - category: 类别 - model: 型号 - """ - # 创建6个洞位,排成2x3布局 - holes = create_ordered_items_2d( - klass=ClipMagazineHole, - num_items_x=3, - num_items_y=2, - dx=(size_x - 2 * hole_spacing) / 2, # 居中 - dy=(size_y - hole_spacing) / 2, # 居中 - dz=size_z - 0, - item_dx=hole_spacing, - item_dy=hole_spacing, - diameter=hole_diameter, - depth=hole_depth, - ) - - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=holes, - category=category, - model=model, - ) - - # 保存洞位的直径和深度 - self.hole_diameter = hole_diameter - self.hole_depth = hole_depth - self.max_sheets_per_hole = max_sheets_per_hole - - def serialize(self) -> dict: - return { - **super().serialize(), - "hole_diameter": self.hole_diameter, - "hole_depth": self.hole_depth, - "max_sheets_per_hole": self.max_sheets_per_hole, - } -#是一种类型注解,不用self -class BatteryState(TypedDict): - """电池状态字典""" - diameter: float - height: float - - electrolyte_name: str - electrolyte_volume: float - -class Battery(Resource): - """电池类 - 可容纳极片""" - children: List[ElectrodeSheet] = [] - - def __init__( - self, - name: str, - category: str = "battery", - ): - """初始化电池 - - Args: - name: 电池名称 - diameter: 直径 (mm) - height: 高度 (mm) - max_volume: 最大容量 (μL) - barcode: 二维码编号 - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=1, - size_y=1, - size_z=1, - category=category, - ) - self._unilabos_state: BatteryState = BatteryState() - - def add_electrolyte_with_bottle(self, bottle: Bottle) -> bool: - to_add_name = bottle._unilabos_state["electrolyte_name"] - if bottle.aspirate_electrolyte(10): - if self.add_electrolyte(to_add_name, 10): - pass - else: - bottle._unilabos_state["electrolyte_volume"] += 10 - - def set_electrolyte(self, name: str, volume: float) -> None: - """设置电解液信息""" - self._unilabos_state["electrolyte_name"] = name - self._unilabos_state["electrolyte_volume"] = volume - #这个应该没用,不会有加了后再加的事情 - def add_electrolyte(self, name: str, volume: float) -> bool: - """添加电解液信息""" - if name != self._unilabos_state["electrolyte_name"]: - return False - self._unilabos_state["electrolyte_volume"] += volume - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - -# 电解液作为属性放进去 - -class BatteryPressSlotState(TypedDict): - """电池状态字典""" - diameter: float =20.0 - depth: float = 4.0 - -class BatteryPressSlot(Resource): - """电池压制槽类 - 设备,可容纳一个电池""" - children: List[Battery] = [] - - def __init__( - self, - name: str = "BatteryPressSlot", - category: str = "battery_press_slot", - ): - """初始化电池压制槽 - - Args: - name: 压制槽名称 - diameter: 直径 (mm) - depth: 深度 (mm) - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=10, - size_y=12, - size_z=13, - category=category, - ) - self._unilabos_state: BatteryPressSlotState = BatteryPressSlotState() - - def has_battery(self) -> bool: - """检查是否有电池""" - return len(self.children) > 0 - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - - def assign_child_resource( - self, - resource: Battery, - location: Optional[Coordinate], - reassign: bool = True, - ): - """放置极片""" - # TODO: 让高京看下槽位只有一个电池时是否这么写。 - if self.has_battery(): - raise ValueError(f"槽位已含有一个电池,无法再放置其他电池") - super().assign_child_resource(resource, location, reassign) - - # 根据children的编号取物料对象。 - def get_battery_info(self, index: int) -> Battery: - return self.children[0] - -# TODO:这个移液枪架子看一下从哪继承 -class TipBox64State(TypedDict): - """电池状态字典""" - tip_diameter: float = 5.0 - tip_length: float = 50.0 - with_tips: bool = True - -class TipBox64(TipRack): - """64孔枪头盒类""" - - children: List[TipSpot] = [] - def __init__( - self, - name: str, - size_x: float = 127.8, - size_y: float = 85.5, - size_z: float = 60.0, - category: str = "tip_box_64", - model: Optional[str] = None, - ): - """初始化64孔枪头盒 - - Args: - name: 枪头盒名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - tip_diameter: 枪头直径 (mm) - tip_length: 枪头长度 (mm) - category: 类别 - model: 型号 - with_tips: 是否带枪头 - """ - from pylabrobot.resources.tip import Tip - - # 创建8x8=64个枪头位 - def make_tip(): - return Tip( - has_filter=False, - total_tip_length=20.0, - maximal_volume=1000, # 1mL - fitting_depth=8.0, - ) - - tip_spots = create_ordered_items_2d( - klass=TipSpot, - num_items_x=8, - num_items_y=8, - dx=8.0, - dy=8.0, - dz=0.0, - item_dx=9.0, - item_dy=9.0, - size_x=10, - size_y=10, - size_z=0.0, - make_tip=make_tip, - ) - self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate() - # 记录网格参数用于前端渲染 - self._grid_params = { - "num_items_x": 8, - "num_items_y": 8, - "dx": 8.0, - "dy": 8.0, - "item_dx": 9.0, - "item_dy": 9.0, - } - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=tip_spots, - category=category, - model=model, - with_tips=True, - ) - - def serialize(self) -> dict: - return { - **super().serialize(), - **self._grid_params, - } - - - -class WasteTipBoxstate(TypedDict): - """"废枪头盒状态字典""" - max_tips: int = 100 - tip_count: int = 0 - -#枪头不是一次性的(同一溶液则反复使用),根据寄存器判断 -class WasteTipBox(Trash): - """废枪头盒类 - 100个枪头容量""" - - def __init__( - self, - name: str, - size_x: float = 127.8, - size_y: float = 85.5, - size_z: float = 60.0, - category: str = "waste_tip_box", - model: Optional[str] = None, - ): - """初始化废枪头盒 - - Args: - name: 废枪头盒名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - max_tips: 最大枪头容量 - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - category=category, - model=model, - ) - self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate() - - def add_tip(self) -> None: - """添加废枪头""" - if self._unilabos_state["tip_count"] >= self._unilabos_state["max_tips"]: - raise ValueError(f"废枪头盒 {self.name} 已满") - self._unilabos_state["tip_count"] += 1 - - def get_tip_count(self) -> int: - """获取枪头数量""" - return self._unilabos_state["tip_count"] - - def empty(self) -> None: - """清空废枪头盒""" - self._unilabos_state["tip_count"] = 0 - - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - -class BottleRackState(TypedDict): - """ bottle_diameter: 瓶子直径 (mm) - bottle_height: 瓶子高度 (mm) - position_spacing: 位置间距 (mm)""" - bottle_diameter: float - bottle_height: float - position_spacing: float - name_to_index: dict - - -class BottleRack(Resource): - """瓶架类 - 12个待配位置+12个已配位置""" - children: List[Resource] = [] - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - category: str = "bottle_rack", - model: Optional[str] = None, - num_items_x: int = 3, - num_items_y: int = 4, - position_spacing: float = 35.0, - orientation: str = "horizontal", - padding_x: float = 20.0, - padding_y: float = 20.0, - ): - """初始化瓶架 - - Args: - name: 瓶架名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - category=category, - model=model, - ) - # 初始化状态 - self._unilabos_state: BottleRackState = BottleRackState( - bottle_diameter=30.0, - bottle_height=100.0, - position_spacing=position_spacing, - name_to_index={}, - ) - # 基于网格生成瓶位坐标映射(居中摆放) - # 使用内边距,避免点跑到容器外(前端渲染不按mm等比缩放时更稳妥) - origin_x = padding_x - origin_y = padding_y - self.index_to_pos = {} - for j in range(num_items_y): - for i in range(num_items_x): - idx = j * num_items_x + i - if orientation == "vertical": - # 纵向:沿 y 方向优先排列 - self.index_to_pos[idx] = Coordinate( - x=origin_x + j * position_spacing, - y=origin_y + i * position_spacing, - z=0, - ) - else: - # 横向(默认):沿 x 方向优先排列 - self.index_to_pos[idx] = Coordinate( - x=origin_x + i * position_spacing, - y=origin_y + j * position_spacing, - z=0, - ) - self.name_to_index = {} - self.name_to_pos = {} - self.num_items_x = num_items_x - self.num_items_y = num_items_y - self.orientation = orientation - self.padding_x = padding_x - self.padding_y = padding_y - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update( - self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - - # TODO: 这里有些问题要重新写一下 - def assign_child_resource_old(self, resource: Resource, location=Coordinate.zero(), reassign=True): - capacity = self.num_items_x * self.num_items_y - assert len(self.children) < capacity, "瓶架已满,无法添加更多瓶子" - index = len(self.children) - location = self.index_to_pos.get(index, Coordinate.zero()) - self.name_to_pos[resource.name] = location - self.name_to_index[resource.name] = index - return super().assign_child_resource(resource, location, reassign) - - def assign_child_resource(self, resource: Resource, index: int): - capacity = self.num_items_x * self.num_items_y - assert 0 <= index < capacity, "无效的瓶子索引" - self.name_to_index[resource.name] = index - location = self.index_to_pos[index] - return super().assign_child_resource(resource, location) - - def unassign_child_resource(self, resource: Bottle): - super().unassign_child_resource(resource) - self.index_to_pos.pop(self.name_to_index.pop(resource.name, None), None) - - def serialize(self) -> dict: - return { - **super().serialize(), - "num_items_x": self.num_items_x, - "num_items_y": self.num_items_y, - "position_spacing": self._unilabos_state.get("position_spacing", 35.0), - "orientation": self.orientation, - "padding_x": self.padding_x, - "padding_y": self.padding_y, - } - - -class BottleState(TypedDict): - diameter: float - height: float - electrolyte_name: str - electrolyte_volume: float - max_volume: float - -class Bottle(Resource): - """瓶子类 - 容纳电解液""" - - def __init__( - self, - name: str, - category: str = "bottle", - ): - """初始化瓶子 - - Args: - name: 瓶子名称 - diameter: 直径 (mm) - height: 高度 (mm) - max_volume: 最大体积 (μL) - barcode: 二维码 - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=1, - size_y=1, - size_z=1, - category=category, - ) - self._unilabos_state: BottleState = BottleState() - - def aspirate_electrolyte(self, volume: float) -> bool: - current_volume = self._unilabos_state["electrolyte_volume"] - assert current_volume > volume, f"Cannot aspirate {volume}μL, only {current_volume}μL available." - self._unilabos_state["electrolyte_volume"] -= volume - return True - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - -class CoincellDeck(Deck): - """纽扣电池组装工作站台面类""" - - def __init__( - self, - name: str = "coin_cell_deck", - size_x: float = 1620.0, # 3.66m - size_y: float = 1270.0, # 1.23m - size_z: float = 500.0, - origin: Coordinate = Coordinate(0, 0, 0), - category: str = "coin_cell_deck", - ): - """初始化纽扣电池组装工作站台面 - - Args: - name: 台面名称 - size_x: 长度 (mm) - 3.66m - size_y: 宽度 (mm) - 1.23m - size_z: 高度 (mm) - origin: 原点坐标 - category: 类别 - """ - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - origin=origin, - category=category, - ) - -#if __name__ == "__main__": -# # 转移极片的测试代码 -# deck = CoincellDeck("coin_cell_deck") -# ban_cao_wei = PlateSlot("ban_cao_wei", max_plates=8) -# deck.assign_child_resource(ban_cao_wei, Coordinate(x=0, y=0, z=0)) -# -# plate_1 = MaterialPlate("plate_1", 1,1,1, fill=True) -# for i, hole in enumerate(plate_1.children): -# sheet = ElectrodeSheet(f"hole_{i}_sheet_1") -# sheet._unilabos_state = { -# "diameter": 14, -# "info": "NMC", -# "mass": 5.0, -# "material_type": "positive_electrode", -# "thickness": 0.1 -# } -# hole._unilabos_state = { -# "depth": 1.0, -# "diameter": 14, -# "info": "", -# "max_sheets": 1 -# } -# hole.assign_child_resource(sheet, Coordinate.zero()) -# plate_1._unilabos_state = { -# "hole_spacing_x": 20.0, -# "hole_spacing_y": 20.0, -# "hole_diameter": 5, -# "info": "这是第一块料板" -# } -# plate_1.update_locations() -# ban_cao_wei.assign_child_resource(plate_1, Coordinate.zero()) -# # zi_dan_jia = ClipMagazine("zi_dan_jia", 1, 1, 1) -# # deck.assign_child_resource(ban_cao_wei, Coordinate(x=200, y=200, z=0)) -# -# from unilabos.resources.graphio import * -# A = tree_to_list([resource_plr_to_ulab(deck)]) -# with open("test.json", "w") as f: -# json.dump(A, f) -# -# -#def get_plate_with_14mm_hole(name=""): -# plate = MaterialPlate(name=name) -# for i in range(4): -# for j in range(4): -# hole = MaterialHole(f"{i+1}x{j+1}") -# hole._unilabos_state["diameter"] = 14 -# hole._unilabos_state["max_sheets"] = 1 -# plate.assign_child_resource(hole) -# return plate - -def create_a_liaopan(): - liaopan = MaterialPlate(name="liaopan", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) - for i in range(16): - jipian = ElectrodeSheet(name=f"jipian_{i}", size_x= 12, size_y=12, size_z=0.1) - liaopan1.children[i].assign_child_resource(jipian, location=None) - return liaopan - -def create_a_coin_cell_deck(): - deck = Deck(size_x=1200, - size_y=800, - size_z=900) - - #liaopan = TipBox64(name="liaopan") - - #创建一个4*4的物料板 - liaopan1 = MaterialPlate(name="liaopan1", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) - #把物料板放到桌子上 - deck.assign_child_resource(liaopan1, Coordinate(x=0, y=0, z=0)) - #创建一个极片 - for i in range(16): - jipian = ElectrodeSheet(name=f"jipian_{i}", size_x= 12, size_y=12, size_z=0.1) - liaopan1.children[i].assign_child_resource(jipian, location=None) - #创建一个4*4的物料板 - liaopan2 = MaterialPlate(name="liaopan2", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) - #把物料板放到桌子上 - deck.assign_child_resource(liaopan2, Coordinate(x=500, y=0, z=0)) - - #创建一个4*4的物料板 - liaopan3 = MaterialPlate(name="liaopan3", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) - #把物料板放到桌子上 - deck.assign_child_resource(liaopan3, Coordinate(x=1000, y=0, z=0)) - - print(deck) - - return deck - - -def create_a_full_coin_cell_deck(): - deck = CoincellDeck() - """======================================子弹夹============================================""" - zip_dan_jia = ClipMagazine_four("zi_dan_jia", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia, Coordinate(x=1400, y=50, z=0)) - zip_dan_jia2 = ClipMagazine_four("zi_dan_jia2", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia2, Coordinate(x=1600, y=200, z=0)) - zip_dan_jia3 = ClipMagazine("zi_dan_jia3", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia3, Coordinate(x=1500, y=200, z=0)) - zip_dan_jia4 = ClipMagazine("zi_dan_jia4", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia4, Coordinate(x=1500, y=300, z=0)) - zip_dan_jia5 = ClipMagazine("zi_dan_jia5", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia5, Coordinate(x=1600, y=300, z=0)) - zip_dan_jia6 = ClipMagazine("zi_dan_jia6", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia6, Coordinate(x=1530, y=500, z=0)) - zip_dan_jia7 = ClipMagazine("zi_dan_jia7", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia7, Coordinate(x=1180, y=400, z=0)) - zip_dan_jia8 = ClipMagazine("zi_dan_jia8", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia8, Coordinate(x=1280, y=400, z=0)) - for i in range(4): - jipian = ElectrodeSheet(name=f"zi_dan_jia_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia2.children[i].assign_child_resource(jipian, location=None) - for i in range(4): - jipian2 = ElectrodeSheet(name=f"zi_dan_jia2_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia.children[i].assign_child_resource(jipian2, location=None) - for i in range(6): - jipian3 = ElectrodeSheet(name=f"zi_dan_jia3_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia3.children[i].assign_child_resource(jipian3, location=None) - for i in range(6): - jipian4 = ElectrodeSheet(name=f"zi_dan_jia4_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia4.children[i].assign_child_resource(jipian4, location=None) - for i in range(6): - jipian5 = ElectrodeSheet(name=f"zi_dan_jia5_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia5.children[i].assign_child_resource(jipian5, location=None) - for i in range(6): - jipian6 = ElectrodeSheet(name=f"zi_dan_jia6_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia6.children[i].assign_child_resource(jipian6, location=None) - for i in range(6): - jipian7 = ElectrodeSheet(name=f"zi_dan_jia7_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia7.children[i].assign_child_resource(jipian7, location=None) - for i in range(6): - jipian8 = ElectrodeSheet(name=f"zi_dan_jia8_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia8.children[i].assign_child_resource(jipian8, location=None) - """======================================子弹夹============================================""" - #liaopan = TipBox64(name="liaopan") - """======================================物料板============================================""" - #创建一个4*4的物料板 - liaopan1 = MaterialPlate(name="liaopan1", size_x=120, size_y=100, size_z=10.0, fill=True) - deck.assign_child_resource(liaopan1, Coordinate(x=1010, y=50, z=0)) - for i in range(16): - jipian_1 = ElectrodeSheet(name=f"{liaopan1.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - liaopan1.children[i].assign_child_resource(jipian_1, location=None) - - liaopan2 = MaterialPlate(name="liaopan2", size_x=120, size_y=100, size_z=10.0, fill=True) - deck.assign_child_resource(liaopan2, Coordinate(x=1130, y=50, z=0)) - - liaopan3 = MaterialPlate(name="liaopan3", size_x=120, size_y=100, size_z=10.0, fill=True) - deck.assign_child_resource(liaopan3, Coordinate(x=1250, y=50, z=0)) - - liaopan4 = MaterialPlate(name="liaopan4", size_x=120, size_y=100, size_z=10.0, fill=True) - deck.assign_child_resource(liaopan4, Coordinate(x=1010, y=150, z=0)) - for i in range(16): - jipian_4 = ElectrodeSheet(name=f"{liaopan4.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - liaopan4.children[i].assign_child_resource(jipian_4, location=None) - liaopan5 = MaterialPlate(name="liaopan5", size_x=120, size_y=100, size_z=10.0, fill=True) - deck.assign_child_resource(liaopan5, Coordinate(x=1130, y=150, z=0)) - liaopan6 = MaterialPlate(name="liaopan6", size_x=120, size_y=100, size_z=10.0, fill=True) - deck.assign_child_resource(liaopan6, Coordinate(x=1250, y=150, z=0)) - #liaopan.children[3].assign_child_resource(jipian, location=None) - """======================================物料板============================================""" - """======================================瓶架,移液枪============================================""" - # 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒 - bottle_rack_3x4 = BottleRack( - name="bottle_rack_3x4", - size_x=210.0, - size_y=140.0, - size_z=100.0, - num_items_x=3, - num_items_y=4, - position_spacing=35.0, - orientation="vertical", - ) - deck.assign_child_resource(bottle_rack_3x4, Coordinate(x=100, y=200, z=0)) - - bottle_rack_6x2 = BottleRack( - name="bottle_rack_6x2", - size_x=120.0, - size_y=250.0, - size_z=100.0, - num_items_x=6, - num_items_y=2, - position_spacing=35.0, - orientation="vertical", - ) - deck.assign_child_resource(bottle_rack_6x2, Coordinate(x=300, y=300, z=0)) - - bottle_rack_6x2_2 = BottleRack( - name="bottle_rack_6x2_2", - size_x=120.0, - size_y=250.0, - size_z=100.0, - num_items_x=6, - num_items_y=2, - position_spacing=35.0, - orientation="vertical", - ) - deck.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=430, y=300, z=0)) - - - # 将 ElectrodeSheet 放满 3x4 与 6x2 的所有孔位 - for idx in range(bottle_rack_3x4.num_items_x * bottle_rack_3x4.num_items_y): - sheet = ElectrodeSheet(name=f"sheet_3x4_{idx}", size_x=12, size_y=12, size_z=0.1) - bottle_rack_3x4.assign_child_resource(sheet, index=idx) - - for idx in range(bottle_rack_6x2.num_items_x * bottle_rack_6x2.num_items_y): - sheet = ElectrodeSheet(name=f"sheet_6x2_{idx}", size_x=12, size_y=12, size_z=0.1) - bottle_rack_6x2.assign_child_resource(sheet, index=idx) - - tip_box = TipBox64(name="tip_box_64") - deck.assign_child_resource(tip_box, Coordinate(x=300, y=100, z=0)) - - waste_tip_box = WasteTipBox(name="waste_tip_box") - deck.assign_child_resource(waste_tip_box, Coordinate(x=300, y=200, z=0)) - """======================================瓶架,移液枪============================================""" - print(deck) - return deck - - - - -import json - -if __name__ == "__main__": - electrode1 = BatteryPressSlot() - #print(electrode1.get_size_x()) - #print(electrode1.get_size_y()) - #print(electrode1.get_size_z()) - #jipian = ElectrodeSheet() - #jipian._unilabos_state["diameter"] = 18 - #print(jipian.serialize()) - #print(jipian.serialize_state()) - - deck = CoincellDeck() - """======================================子弹夹============================================""" - zip_dan_jia = ClipMagazine_four("zi_dan_jia", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia, Coordinate(x=1400, y=50, z=0)) - zip_dan_jia2 = ClipMagazine_four("zi_dan_jia2", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia2, Coordinate(x=1600, y=200, z=0)) - zip_dan_jia3 = ClipMagazine("zi_dan_jia3", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia3, Coordinate(x=1500, y=200, z=0)) - zip_dan_jia4 = ClipMagazine("zi_dan_jia4", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia4, Coordinate(x=1500, y=300, z=0)) - zip_dan_jia5 = ClipMagazine("zi_dan_jia5", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia5, Coordinate(x=1600, y=300, z=0)) - zip_dan_jia6 = ClipMagazine("zi_dan_jia6", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia6, Coordinate(x=1530, y=500, z=0)) - zip_dan_jia7 = ClipMagazine("zi_dan_jia7", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia7, Coordinate(x=1180, y=400, z=0)) - zip_dan_jia8 = ClipMagazine("zi_dan_jia8", 80, 80, 10) - deck.assign_child_resource(zip_dan_jia8, Coordinate(x=1280, y=400, z=0)) - for i in range(4): - jipian = ElectrodeSheet(name=f"zi_dan_jia_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia2.children[i].assign_child_resource(jipian, location=None) - for i in range(4): - jipian2 = ElectrodeSheet(name=f"zi_dan_jia2_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia.children[i].assign_child_resource(jipian2, location=None) - for i in range(6): - jipian3 = ElectrodeSheet(name=f"zi_dan_jia3_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia3.children[i].assign_child_resource(jipian3, location=None) - for i in range(6): - jipian4 = ElectrodeSheet(name=f"zi_dan_jia4_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia4.children[i].assign_child_resource(jipian4, location=None) - for i in range(6): - jipian5 = ElectrodeSheet(name=f"zi_dan_jia5_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia5.children[i].assign_child_resource(jipian5, location=None) - for i in range(6): - jipian6 = ElectrodeSheet(name=f"zi_dan_jia6_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia6.children[i].assign_child_resource(jipian6, location=None) - for i in range(6): - jipian7 = ElectrodeSheet(name=f"zi_dan_jia7_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia7.children[i].assign_child_resource(jipian7, location=None) - for i in range(6): - jipian8 = ElectrodeSheet(name=f"zi_dan_jia8_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia8.children[i].assign_child_resource(jipian8, location=None) - """======================================子弹夹============================================""" - #liaopan = TipBox64(name="liaopan") - """======================================物料板============================================""" - #创建一个4*4的物料板 - liaopan1 = MaterialPlate(name="liaopan1", size_x=120, size_y=100, size_z=10.0, fill=True) - deck.assign_child_resource(liaopan1, Coordinate(x=1010, y=50, z=0)) - for i in range(16): - jipian_1 = ElectrodeSheet(name=f"{liaopan1.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - liaopan1.children[i].assign_child_resource(jipian_1, location=None) - - liaopan2 = MaterialPlate(name="liaopan2", size_x=120, size_y=100, size_z=10.0, fill=True) - deck.assign_child_resource(liaopan2, Coordinate(x=1130, y=50, z=0)) - - liaopan3 = MaterialPlate(name="liaopan3", size_x=120, size_y=100, size_z=10.0, fill=True) - deck.assign_child_resource(liaopan3, Coordinate(x=1250, y=50, z=0)) - - liaopan4 = MaterialPlate(name="liaopan4", size_x=120, size_y=100, size_z=10.0, fill=True) - deck.assign_child_resource(liaopan4, Coordinate(x=1010, y=150, z=0)) - for i in range(16): - jipian_4 = ElectrodeSheet(name=f"{liaopan4.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - liaopan4.children[i].assign_child_resource(jipian_4, location=None) - liaopan5 = MaterialPlate(name="liaopan5", size_x=120, size_y=100, size_z=10.0, fill=True) - deck.assign_child_resource(liaopan5, Coordinate(x=1130, y=150, z=0)) - liaopan6 = MaterialPlate(name="liaopan6", size_x=120, size_y=100, size_z=10.0, fill=True) - deck.assign_child_resource(liaopan6, Coordinate(x=1250, y=150, z=0)) - #liaopan.children[3].assign_child_resource(jipian, location=None) - """======================================物料板============================================""" - """======================================瓶架,移液枪============================================""" - # 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒 - bottle_rack_3x4 = BottleRack( - name="bottle_rack_3x4", - size_x=210.0, - size_y=140.0, - size_z=100.0, - num_items_x=3, - num_items_y=4, - position_spacing=35.0, - orientation="vertical", - ) - deck.assign_child_resource(bottle_rack_3x4, Coordinate(x=100, y=200, z=0)) - - bottle_rack_6x2 = BottleRack( - name="bottle_rack_6x2", - size_x=120.0, - size_y=250.0, - size_z=100.0, - num_items_x=6, - num_items_y=2, - position_spacing=35.0, - orientation="vertical", - ) - deck.assign_child_resource(bottle_rack_6x2, Coordinate(x=300, y=300, z=0)) - - bottle_rack_6x2_2 = BottleRack( - name="bottle_rack_6x2_2", - size_x=120.0, - size_y=250.0, - size_z=100.0, - num_items_x=6, - num_items_y=2, - position_spacing=35.0, - orientation="vertical", - ) - deck.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=430, y=300, z=0)) - - - # 将 ElectrodeSheet 放满 3x4 与 6x2 的所有孔位 - for idx in range(bottle_rack_3x4.num_items_x * bottle_rack_3x4.num_items_y): - sheet = ElectrodeSheet(name=f"sheet_3x4_{idx}", size_x=12, size_y=12, size_z=0.1) - bottle_rack_3x4.assign_child_resource(sheet, index=idx) - - for idx in range(bottle_rack_6x2.num_items_x * bottle_rack_6x2.num_items_y): - sheet = ElectrodeSheet(name=f"sheet_6x2_{idx}", size_x=12, size_y=12, size_z=0.1) - bottle_rack_6x2.assign_child_resource(sheet, index=idx) - - tip_box = TipBox64(name="tip_box_64") - deck.assign_child_resource(tip_box, Coordinate(x=300, y=100, z=0)) - - waste_tip_box = WasteTipBox(name="waste_tip_box") - deck.assign_child_resource(waste_tip_box, Coordinate(x=300, y=200, z=0)) - """======================================瓶架,移液枪============================================""" - print(deck) - - - from unilabos.resources.graphio import convert_resources_from_type - from unilabos.config.config import BasicConfig - BasicConfig.ak = "beb0c15f-2279-46a1-aba5-00eaf89aef55" - BasicConfig.sk = "15d4f25e-3512-4f9c-9bfb-43ab85e7b561" - from unilabos.app.web.client import http_client - - resources = convert_resources_from_type([deck], [Resource]) - - # 检查序列化后的资源 - - json.dump({"nodes": resources, "links": []}, open("button_battery_station_resources_unilab.json", "w"), indent=2) - - - #print(resources) - http_client.remote_addr = "https://uni-lab.test.bohrium.com/api/v1" - - http_client.resource_add(resources) \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/button_battery_station_resources_unilab_big.json b/unilabos/devices/workstation/coin_cell_assembly/button_battery_station_resources_unilab_big.json deleted file mode 100644 index 9468817..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/button_battery_station_resources_unilab_big.json +++ /dev/null @@ -1,1895 +0,0 @@ -{ - "nodes": [ - { - "id": "coin_cell_deck", - "name": "coin_cell_deck", - "sample_id": null, - "children": [ - "liaopan1", - "liaopan2" - ], - "parent": null, - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "CoincellDeck", - "size_x": 1620.0, - "size_y": 1270.0, - "size_z": 500.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "coin_cell_deck", - "barcode": null - }, - "data": {} - }, - { - "id": "liaopan1", - "name": "liaopan1", - "sample_id": null, - "children": [ - "liaopan1_materialhole_0_0", - "liaopan1_materialhole_0_1", - "liaopan1_materialhole_0_2", - "liaopan1_materialhole_0_3", - "liaopan1_materialhole_1_0", - "liaopan1_materialhole_1_1", - "liaopan1_materialhole_1_2", - "liaopan1_materialhole_1_3", - "liaopan1_materialhole_2_0", - "liaopan1_materialhole_2_1", - "liaopan1_materialhole_2_2", - "liaopan1_materialhole_2_3", - "liaopan1_materialhole_3_0", - "liaopan1_materialhole_3_1", - "liaopan1_materialhole_3_2", - "liaopan1_materialhole_3_3" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120.8, - "size_y": 120.5, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "liaopan1_materialhole_0_0", - "B1": "liaopan1_materialhole_0_1", - "C1": "liaopan1_materialhole_0_2", - "D1": "liaopan1_materialhole_0_3", - "A2": "liaopan1_materialhole_1_0", - "B2": "liaopan1_materialhole_1_1", - "C2": "liaopan1_materialhole_1_2", - "D2": "liaopan1_materialhole_1_3", - "A3": "liaopan1_materialhole_2_0", - "B3": "liaopan1_materialhole_2_1", - "C3": "liaopan1_materialhole_2_2", - "D3": "liaopan1_materialhole_2_3", - "A4": "liaopan1_materialhole_3_0", - "B4": "liaopan1_materialhole_3_1", - "C4": "liaopan1_materialhole_3_2", - "D4": "liaopan1_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "liaopan1_materialhole_0_0", - "name": "liaopan1_materialhole_0_0", - "sample_id": null, - "children": [ - "jipian_0" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_0", - "name": "jipian_0", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_1", - "name": "liaopan1_materialhole_0_1", - "sample_id": null, - "children": [], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_2", - "name": "liaopan1_materialhole_0_2", - "sample_id": null, - "children": [ - "jipian_2" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_2", - "name": "jipian_2", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_3", - "name": "liaopan1_materialhole_0_3", - "sample_id": null, - "children": [ - "jipian_3" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_3", - "name": "jipian_3", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_0", - "name": "liaopan1_materialhole_1_0", - "sample_id": null, - "children": [ - "jipian_4" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_4", - "name": "jipian_4", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_1", - "name": "liaopan1_materialhole_1_1", - "sample_id": null, - "children": [ - "jipian_5" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_5", - "name": "jipian_5", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_2", - "name": "liaopan1_materialhole_1_2", - "sample_id": null, - "children": [ - "jipian_6" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_6", - "name": "jipian_6", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_3", - "name": "liaopan1_materialhole_1_3", - "sample_id": null, - "children": [ - "jipian_7" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_7", - "name": "jipian_7", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_0", - "name": "liaopan1_materialhole_2_0", - "sample_id": null, - "children": [ - "jipian_8" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_8", - "name": "jipian_8", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_1", - "name": "liaopan1_materialhole_2_1", - "sample_id": null, - "children": [ - "jipian_9" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_9", - "name": "jipian_9", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_2", - "name": "liaopan1_materialhole_2_2", - "sample_id": null, - "children": [ - "jipian_10" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_10", - "name": "jipian_10", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_3", - "name": "liaopan1_materialhole_2_3", - "sample_id": null, - "children": [ - "jipian_11" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_11", - "name": "jipian_11", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_0", - "name": "liaopan1_materialhole_3_0", - "sample_id": null, - "children": [ - "jipian_12" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_12", - "name": "jipian_12", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_1", - "name": "liaopan1_materialhole_3_1", - "sample_id": null, - "children": [ - "jipian_13" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_13", - "name": "jipian_13", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_2", - "name": "liaopan1_materialhole_3_2", - "sample_id": null, - "children": [ - "jipian_14" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_14", - "name": "jipian_14", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_3", - "name": "liaopan1_materialhole_3_3", - "sample_id": null, - "children": [ - "jipian_15" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_15", - "name": "jipian_15", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan2", - "name": "liaopan2", - "sample_id": null, - "children": [ - "liaopan2_materialhole_0_0", - "liaopan2_materialhole_0_1", - "liaopan2_materialhole_0_2", - "liaopan2_materialhole_0_3", - "liaopan2_materialhole_1_0", - "liaopan2_materialhole_1_1", - "liaopan2_materialhole_1_2", - "liaopan2_materialhole_1_3", - "liaopan2_materialhole_2_0", - "liaopan2_materialhole_2_1", - "liaopan2_materialhole_2_2", - "liaopan2_materialhole_2_3", - "liaopan2_materialhole_3_0", - "liaopan2_materialhole_3_1", - "liaopan2_materialhole_3_2", - "liaopan2_materialhole_3_3" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 500, - "y": 0, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120.8, - "size_y": 120.5, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "liaopan2_materialhole_0_0", - "B1": "liaopan2_materialhole_0_1", - "C1": "liaopan2_materialhole_0_2", - "D1": "liaopan2_materialhole_0_3", - "A2": "liaopan2_materialhole_1_0", - "B2": "liaopan2_materialhole_1_1", - "C2": "liaopan2_materialhole_1_2", - "D2": "liaopan2_materialhole_1_3", - "A3": "liaopan2_materialhole_2_0", - "B3": "liaopan2_materialhole_2_1", - "C3": "liaopan2_materialhole_2_2", - "D3": "liaopan2_materialhole_2_3", - "A4": "liaopan2_materialhole_3_0", - "B4": "liaopan2_materialhole_3_1", - "C4": "liaopan2_materialhole_3_2", - "D4": "liaopan2_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "liaopan2_materialhole_0_0", - "name": "liaopan2_materialhole_0_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_1", - "name": "liaopan2_materialhole_0_1", - "sample_id": null, - "children": [ - "jipian_1" - ], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_1", - "name": "jipian_1", - "sample_id": null, - "children": [], - "parent": "liaopan2_materialhole_0_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_2", - "name": "liaopan2_materialhole_0_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_3", - "name": "liaopan2_materialhole_0_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_0", - "name": "liaopan2_materialhole_1_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_1", - "name": "liaopan2_materialhole_1_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_2", - "name": "liaopan2_materialhole_1_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_3", - "name": "liaopan2_materialhole_1_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_0", - "name": "liaopan2_materialhole_2_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_1", - "name": "liaopan2_materialhole_2_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_2", - "name": "liaopan2_materialhole_2_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_3", - "name": "liaopan2_materialhole_2_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_0", - "name": "liaopan2_materialhole_3_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_1", - "name": "liaopan2_materialhole_3_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_2", - "name": "liaopan2_materialhole_3_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_3", - "name": "liaopan2_materialhole_3_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - } - ], - "links": [] -} \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/cellconfig.py b/unilabos/devices/workstation/coin_cell_assembly/cellconfig.py deleted file mode 100644 index 957f62d..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/cellconfig.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env python -#coding=utf-8 - -ENV = 'pro'#'test' -class MQConfig: - """MQTT 配置类""" - lab_id: str = '9F05593C' - instance_id: str = 'mqtt-cn-dsr48m6jy02' - - group_id: str = 'GID_prod' - broker_url: str = 'mqtt-cn-dsr48m6jy02.mqtt.aliyuncs.com' - port: int = 8883 - ca_content: str = '''-----BEGIN CERTIFICATE----- -MIID3jCCAsagAwIBAgIUDyIgmg4qZtMPa8r2Vvn1b1fgJ+YwDQYJKoZIhvcNAQEL -BQAwSzELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkJKMQswCQYDVQQHDAJCSjEQMA4G -A1UECgwHZHAudGVjaDEQMA4GA1UECwwHdW5pLWxhYjAeFw0yNTA1MDYxNTE0Mjda -Fw0zNTA1MDQxNTE0MjdaMEsxCzAJBgNVBAYTAkNOMQswCQYDVQQIDAJCSjELMAkG -A1UEBwwCQkoxEDAOBgNVBAoMB2RwLnRlY2gxEDAOBgNVBAsMB3VuaS1sYWIwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDnTBywX+6DJ2n+prNKvylBBJF6 -NHQrCt2cztZfswHsW4QhAbDddp4PRzNVzKtIfHX5ZrXGbxNT1/TqQYXKiFjKbfPC -VHTrS6+95LP3MxNTlBWHP6d2uI45KwrGgQ7D1uPDG1wZsfuJxvOkfAIxZRCDUMJr -erYYK/p2/GVMAO5YKE7wENUMN+iLfVQRqQJRgte9z0B35DxUeOUblJDun0Dpl/6L -0km/YRrjUKA/5+u/h+Ko9+36L1DAi+9rm3eyp+BQHBy5aiVhAG6uAJeMjbZMxwxz -ixg9cWNxP1BW+aQQzixbEQ+YlO9+w/soJkLstiK7jF8uIg2QvmNUKNlqab0pAgMB -AAGjgbkwgbYwHQYDVR0OBBYEFAqg0r7f6ngWODyVxVWHWM06b8wDMIGGBgNVHSME -fzB9gBQKoNK+3+p4Fjg8lcVVh1jNOm/MA6FPpE0wSzELMAkGA1UEBhMCQ04xCzAJ -BgNVBAgMAkJKMQswCQYDVQQHDAJCSjEQMA4GA1UECgwHZHAudGVjaDEQMA4GA1UE -CwwHdW5pLWxhYoIUDyIgmg4qZtMPa8r2Vvn1b1fgJ+YwDAYDVR0TBAUwAwEB/zAN -BgkqhkiG9w0BAQsFAAOCAQEAMylGHHhRCI8JLTizxqk2HaOFkF/WfnYC3XyNx3bK -9KqwVcvaqES+C058lits5nCV1qjjSnKt6xU11S8C6E28Kazh+wMqnSw63fz4UOY5 -4cekPCPy8XcWlOY6UW2N27GR0c9JDo9ovruOn1Y4KjATpAQI4W2tPAQ2gCVSNpu1 -bw5uw35yJSRzdQIHlsVbslvj2wcugK3GZHmmxJK+q9ww7G6xXtE2Y0+vl6AZRj+I -lcTy5TNNDZiiboIlAt+K3m4hxzSgGPbmFPJX3Lw3i+YMR/0PrWfXqxZgicO/V6/d -SgGBqq/tH1caiaEjCFudSZcOiZvHIlb09O4qL7mCtWEiEQ== ------END CERTIFICATE----- -''' - cert_content: str = '''-----BEGIN CERTIFICATE----- -MIIDlTCCAn2gAwIBAgIUa/ce6dpJ8K7XNvT0LknVmLgfJMIwDQYJKoZIhvcNAQEL -BQAwSzELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkJKMQswCQYDVQQHDAJCSjEQMA4G -A1UECgwHZHAudGVjaDEQMA4GA1UECwwHdW5pLWxhYjAeFw0yNTA4MTMwNDQ0MDVa -Fw0yNjEyMjYwNDQ0MDVaMHoxCzAJBgNVBAYTAkNOMQswCQYDVQQIDAJCSjELMAkG -A1UEBwwCQkoxDDAKBgNVBAoMA0RQVDENMAsGA1UECwwETVFUVDEhMB8GCSqGSIb3 -DQEJARYSaHVhaGFpbWluZ0BkcC50ZWNoMREwDwYDVQQDDAg0NzJBMjZBQzCCASIw -DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPc4NHXcwaFX3jSN6DnDYoY7ON6c -AeVIlcQp3CMHnulh4t3I3Fnsyelpc809s7l5vEpAjMIuZ40DJKZQmV9ckmeylMiY -bAk851+i8YcRQPeYYY7Ggt0sfkY3TWIIqptZtlIhXtkTCWw6xpHAPkYxqNTiUN/0 -vwQWwiBS7WqD8NVjNhhHootYLsMjnQYc162L8nUwzG2pjB3UYqOldC3FkHXvBkG2 -Oeex8VM8Urblv0huCmoFRyuMmNol0QWqp+6nwAgdvf89Z38NJByPI9VHaBB/VV1F -HiAZe3H8Ph7wzgUSXBuVHJ4BaeJbg4+ax6BccpaQn26jgpJGUEj+YR+NwdMCAwEA -AaNCMEAwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCAvQwIAYDVR0lAQH/BBYw -FAYIKwYBBQUHAwIGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4IBAQAZaF8puP0/ -OcRM7Gcd4LrF8H/WG0Q7WM0T9BWGvee6A+Fcd4ajBC0S0tIfdsfYat0+g4U57jrr -vaQeZGFKc4YKVui8vSuth82fcsFk5fpyhz4JJRggzeoby+0gNx9eYDJwLIvbVy4Y -2LKGq+rsO07QF54jtwB4WpDNFnEIadXyjPBsMy/0Ssbetp827WYZygXYyAcUlCfN -Wns7K0phfZJwIMQgPs3d4mGwCC+xaRIB3GGjUGFXV1sFItjkTUHCvm+phw/MTpRp -pauplyDcWYux7z1dKhbuHElzCEqxZNwyI0nGJlRFP13Oo+jnuDO7gQh2lyz/AFyX -KyTA3xFZduHO ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID3jCCAsagAwIBAgIUDyIgmg4qZtMPa8r2Vvn1b1fgJ+YwDQYJKoZIhvcNAQEL -BQAwSzELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkJKMQswCQYDVQQHDAJCSjEQMA4G -A1UECgwHZHAudGVjaDEQMA4GA1UECwwHdW5pLWxhYjAeFw0yNTA1MDYxNTE0Mjda -Fw0zNTA1MDQxNTE0MjdaMEsxCzAJBgNVBAYTAkNOMQswCQYDVQQIDAJCSjELMAkG -A1UEBwwCQkoxEDAOBgNVBAoMB2RwLnRlY2gxEDAOBgNVBAsMB3VuaS1sYWIwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDnTBywX+6DJ2n+prNKvylBBJF6 -NHQrCt2cztZfswHsW4QhAbDddp4PRzNVzKtIfHX5ZrXGbxNT1/TqQYXKiFjKbfPC -VHTrS6+95LP3MxNTlBWHP6d2uI45KwrGgQ7D1uPDG1wZsfuJxvOkfAIxZRCDUMJr -erYYK/p2/GVMAO5YKE7wENUMN+iLfVQRqQJRgte9z0B35DxUeOUblJDun0Dpl/6L -0km/YRrjUKA/5+u/h+Ko9+36L1DAi+9rm3eyp+BQHBy5aiVhAG6uAJeMjbZMxwxz -ixg9cWNxP1BW+aQQzixbEQ+YlO9+w/soJkLstiK7jF8uIg2QvmNUKNlqab0pAgMB -AAGjgbkwgbYwHQYDVR0OBBYEFAqg0r7f6ngWODyVxVWHWM06b8wDMIGGBgNVHSME -fzB9gBQKoNK+3+p4Fjg8lcVVh1jNOm/MA6FPpE0wSzELMAkGA1UEBhMCQ04xCzAJ -BgNVBAgMAkJKMQswCQYDVQQHDAJCSjEQMA4GA1UECgwHZHAudGVjaDEQMA4GA1UE -CwwHdW5pLWxhYoIUDyIgmg4qZtMPa8r2Vvn1b1fgJ+YwDAYDVR0TBAUwAwEB/zAN -BgkqhkiG9w0BAQsFAAOCAQEAMylGHHhRCI8JLTizxqk2HaOFkF/WfnYC3XyNx3bK -9KqwVcvaqES+C058lits5nCV1qjjSnKt6xU11S8C6E28Kazh+wMqnSw63fz4UOY5 -4cekPCPy8XcWlOY6UW2N27GR0c9JDo9ovruOn1Y4KjATpAQI4W2tPAQ2gCVSNpu1 -bw5uw35yJSRzdQIHlsVbslvj2wcugK3GZHmmxJK+q9ww7G6xXtE2Y0+vl6AZRj+I -lcTy5TNNDZiiboIlAt+K3m4hxzSgGPbmFPJX3Lw3i+YMR/0PrWfXqxZgicO/V6/d -SgGBqq/tH1caiaEjCFudSZcOiZvHIlb09O4qL7mCtWEiEQ== ------END CERTIFICATE----- -''' - key_content: str = '''-----BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEA9zg0ddzBoVfeNI3oOcNihjs43pwB5UiVxCncIwee6WHi3cjc -WezJ6WlzzT2zuXm8SkCMwi5njQMkplCZX1ySZ7KUyJhsCTznX6LxhxFA95hhjsaC -3Sx+RjdNYgiqm1m2UiFe2RMJbDrGkcA+RjGo1OJQ3/S/BBbCIFLtaoPw1WM2GEei -i1guwyOdBhzXrYvydTDMbamMHdRio6V0LcWQde8GQbY557HxUzxStuW/SG4KagVH -K4yY2iXRBaqn7qfACB29/z1nfw0kHI8j1UdoEH9VXUUeIBl7cfw+HvDOBRJcG5Uc -ngFp4luDj5rHoFxylpCfbqOCkkZQSP5hH43B0wIDAQABAoIBAAPzz0ZUcqmR1Eva -5PH98gQzp2wB9snLY86HY3Z/JVAPf5Ht9sbAUWHhT8PVoWpIasSmFbuJxz6DRk3S -M8VVVipxxgcTWqo/JOD4HZiCNfcRru6+5dHxZ4p2B/n4EWfoy+KyEZkgd5jQFONj -jIX+rDR3qZzFqoBRhQSHLuD+i66eZ7l1LOqsnk51r3nTCnGmdyV8fll56MMB5D6+ -8LN2rwbmSYX/UIBBqHUthgEt2onFNaetTLgSa3RSNGZ3xEZt4N32vw1SARxItuso -npAAY77POMUwWe3666fETI+yr/gJuppvTF4sQUXy7I4iz7I18n2SYivHabdgnk6H -7y1TcGECgYEA/NbBLFz1YPOQiT6TsuitIlfWcFWXYI4yHh/Mwwm/heHV683HrUti -RSHWbFxggW70BYJbGAQprEe9UIRVdP9YNi3aPeN5WNfnTFHlN2HRiKenlETM1tw9 -yaSWjNbAyc2ka+l1EblMJy92xoCkErS8riEPW83o+3+LqJwnjsJ8tVECgYEA+k93 -AyNpXuOZldAoqSHF3wHgzgd2jhfVdQcNlz9sLfT8TAdoR01mdBtdytcYAH+FHplW -wlkCfpT1RPf3fEd0Asy727pJnL9v/QfY/BB+vfgWKUQg9CWNIevwItCaTNOSekis -lKl5dxNGOyouU7rPbTj9BC26OHA50Z3vLMKmi+MCgYEAy0Sb6N6TJ26pNK1qcNs+ -1e1oKMem+6lWAYHvTJ35q9jz8q9taJTCXHHnwRZDP8vDwuoZ8iTmm+rQ+HprebQP -Zv9WBYtrc1GgUmtErFGn8wVWZI0rYVGPGx2HK5M7SwJYvajixW0DHD28b7ncLm2/ -gv5xKo1QUWEpFlT0OIGDYQECgYEA8WRlH6+s1Iel++ZM8B7T1ibXh5mG6a1ue3eb -0bqmNwPFtASIugqYvWwO3ajlSsWvuTyjgLWaRDye9C42i7HU3UZX/KUAjJvKAjjp -Nt0pfUadCJrdNNZp7sa8RLbrtx9qaWdgl9WAgCckWbZqCvFjTK/iwX7f0cHY4J/w -ojftqYUCgYARM6YaEJuBJEBmZV1I0rweiguqWssZz2j1awSlsfYxckwnci4VtSMI -D/sp0Wp0yn2N4cgqp49BFD0rCQCTsASVICEf9HWdMQXsUhzWsz4SjVEhjWWC1VAk -sEL+BOcbsHy3qMbV2uKBHrhuZShDdy5KtCm9TB+7zTWyDVHwE24nig== ------END RSA PRIVATE KEY----- -''' - -# HTTP配置 -class HTTPConfig: - remote_addr = "https://uni-lab.test.bohrium.com/api/v1" diff --git a/unilabos/devices/workstation/coin_cell_assembly/celljson.json b/unilabos/devices/workstation/coin_cell_assembly/celljson.json deleted file mode 100644 index ad90417..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/celljson.json +++ /dev/null @@ -1,1332 +0,0 @@ -{ - "nodes": [ - { - "id": "BatteryStation", - "name": "扣电工作站", - "children": [ - "coin_cell_deck" - ], - "parent": null, - "type": "device", - "class": "bettery_station_registry", - "position": { - "x": 600, - "y": 400, - "z": 0 - }, - "config": { - "debug_mode": false, - "_comment": "protocol_type接外部工站固定写法字段,一般为空,station_resource写法也固定", - "protocol_type": [], - "station_resource": { - "data": { - "_resource_child_name": "coin_cell_deck", - "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" - } - }, - - "address": "192.168.1.20", - "port": 502 - }, - "data": {} - }, - { - "id": "coin_cell_deck", - "name": "coin_cell_deck", - "sample_id": null, - "children": [ - "ban_cao_wei" - ], - "parent": "BatteryStation", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "CoincellDeck", - "size_x": 1620.0, - "size_y": 1270.0, - "size_z": 500.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "coin_cell_deck", - "barcode": null - }, - "data": {} - }, - { - "id": "ban_cao_wei", - "name": "ban_cao_wei", - "sample_id": null, - "children": [ - "plate_1" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "PlateSlot", - "size_x": 0, - "size_y": 0, - "size_z": 0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "plate_slot", - "model": null, - "barcode": null, - "max_plates": 8 - }, - "data": {} - }, - { - "id": "plate_1", - "name": "plate_1", - "sample_id": null, - "children": [ - "plate_1_materialhole_0_0", - "plate_1_materialhole_0_1", - "plate_1_materialhole_0_2", - "plate_1_materialhole_0_3", - "plate_1_materialhole_1_0", - "plate_1_materialhole_1_1", - "plate_1_materialhole_1_2", - "plate_1_materialhole_1_3", - "plate_1_materialhole_2_0", - "plate_1_materialhole_2_1", - "plate_1_materialhole_2_2", - "plate_1_materialhole_2_3", - "plate_1_materialhole_3_0", - "plate_1_materialhole_3_1", - "plate_1_materialhole_3_2", - "plate_1_materialhole_3_3" - ], - "parent": "ban_cao_wei", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "plate_1_materialhole_0_0", - "B1": "plate_1_materialhole_0_1", - "C1": "plate_1_materialhole_0_2", - "D1": "plate_1_materialhole_0_3", - "A2": "plate_1_materialhole_1_0", - "B2": "plate_1_materialhole_1_1", - "C2": "plate_1_materialhole_1_2", - "D2": "plate_1_materialhole_1_3", - "A3": "plate_1_materialhole_2_0", - "B3": "plate_1_materialhole_2_1", - "C3": "plate_1_materialhole_2_2", - "D3": "plate_1_materialhole_2_3", - "A4": "plate_1_materialhole_3_0", - "B4": "plate_1_materialhole_3_1", - "C4": "plate_1_materialhole_3_2", - "D4": "plate_1_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "plate_1_materialhole_0_0", - "name": "plate_1_materialhole_0_0", - "sample_id": null, - "children": [ - "hole_0_sheet_1" - ], - "parent": "plate_1", - "type": "container", - "class": "", - "position": { - "x": -29.5, - "y": 30.5, - "z": 1 - }, - "config": { - "type": "MaterialHole", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "depth": 1.0, - "diameter": 14, - "info": "", - "max_sheets": 1 - } - }, - { - "id": "hole_0_sheet_1", - "name": "hole_0_sheet_1", - "sample_id": null, - "children": [], - "parent": "plate_1_materialhole_0_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "info": "NMC", - "mass": 5.0, - "material_type": "positive_electrode", - "thickness": 0.1 - } - }, - { - "id": "plate_1_materialhole_0_1", - "name": "plate_1_materialhole_0_1", - "sample_id": null, - "children": [ - "hole_1_sheet_1" - ], - "parent": "plate_1", - "type": "container", - "class": "", - "position": { - "x": -29.5, - "y": 10.5, - "z": 1 - }, - "config": { - "type": "MaterialHole", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "depth": 1.0, - "diameter": 14, - "info": "", - "max_sheets": 1 - } - }, - { - "id": "hole_1_sheet_1", - "name": "hole_1_sheet_1", - "sample_id": null, - "children": [], - "parent": "plate_1_materialhole_0_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "info": "NMC", - "mass": 5.0, - "material_type": "positive_electrode", - "thickness": 0.1 - } - }, - { - "id": "plate_1_materialhole_0_2", - "name": "plate_1_materialhole_0_2", - "sample_id": null, - "children": [ - "hole_2_sheet_1" - ], - "parent": "plate_1", - "type": "container", - "class": "", - "position": { - "x": -29.5, - "y": -9.5, - "z": 1 - }, - "config": { - "type": "MaterialHole", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "depth": 1.0, - "diameter": 14, - "info": "", - "max_sheets": 1 - } - }, - { - "id": "hole_2_sheet_1", - "name": "hole_2_sheet_1", - "sample_id": null, - "children": [], - "parent": "plate_1_materialhole_0_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "info": "NMC", - "mass": 5.0, - "material_type": "positive_electrode", - "thickness": 0.1 - } - }, - { - "id": "plate_1_materialhole_0_3", - "name": "plate_1_materialhole_0_3", - "sample_id": null, - "children": [ - "hole_3_sheet_1" - ], - "parent": "plate_1", - "type": "container", - "class": "", - "position": { - "x": -29.5, - "y": -29.5, - "z": 1 - }, - "config": { - "type": "MaterialHole", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "depth": 1.0, - "diameter": 14, - "info": "", - "max_sheets": 1 - } - }, - { - "id": "hole_3_sheet_1", - "name": "hole_3_sheet_1", - "sample_id": null, - "children": [], - "parent": "plate_1_materialhole_0_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "info": "NMC", - "mass": 5.0, - "material_type": "positive_electrode", - "thickness": 0.1 - } - }, - { - "id": "plate_1_materialhole_1_0", - "name": "plate_1_materialhole_1_0", - "sample_id": null, - "children": [ - "hole_4_sheet_1" - ], - "parent": "plate_1", - "type": "container", - "class": "", - "position": { - "x": -9.5, - "y": 30.5, - "z": 1 - }, - "config": { - "type": "MaterialHole", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "depth": 1.0, - "diameter": 14, - "info": "", - "max_sheets": 1 - } - }, - { - "id": "hole_4_sheet_1", - "name": "hole_4_sheet_1", - "sample_id": null, - "children": [], - "parent": "plate_1_materialhole_1_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "info": "NMC", - "mass": 5.0, - "material_type": "positive_electrode", - "thickness": 0.1 - } - }, - { - "id": "plate_1_materialhole_1_1", - "name": "plate_1_materialhole_1_1", - "sample_id": null, - "children": [ - "hole_5_sheet_1" - ], - "parent": "plate_1", - "type": "container", - "class": "", - "position": { - "x": -9.5, - "y": 10.5, - "z": 1 - }, - "config": { - "type": "MaterialHole", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "depth": 1.0, - "diameter": 14, - "info": "", - "max_sheets": 1 - } - }, - { - "id": "hole_5_sheet_1", - "name": "hole_5_sheet_1", - "sample_id": null, - "children": [], - "parent": "plate_1_materialhole_1_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "info": "NMC", - "mass": 5.0, - "material_type": "positive_electrode", - "thickness": 0.1 - } - }, - { - "id": "plate_1_materialhole_1_2", - "name": "plate_1_materialhole_1_2", - "sample_id": null, - "children": [ - "hole_6_sheet_1" - ], - "parent": "plate_1", - "type": "container", - "class": "", - "position": { - "x": -9.5, - "y": -9.5, - "z": 1 - }, - "config": { - "type": "MaterialHole", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "depth": 1.0, - "diameter": 14, - "info": "", - "max_sheets": 1 - } - }, - { - "id": "hole_6_sheet_1", - "name": "hole_6_sheet_1", - "sample_id": null, - "children": [], - "parent": "plate_1_materialhole_1_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "info": "NMC", - "mass": 5.0, - "material_type": "positive_electrode", - "thickness": 0.1 - } - }, - { - "id": "plate_1_materialhole_1_3", - "name": "plate_1_materialhole_1_3", - "sample_id": null, - "children": [ - "hole_7_sheet_1" - ], - "parent": "plate_1", - "type": "container", - "class": "", - "position": { - "x": -9.5, - "y": -29.5, - "z": 1 - }, - "config": { - "type": "MaterialHole", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "depth": 1.0, - "diameter": 14, - "info": "", - "max_sheets": 1 - } - }, - { - "id": "hole_7_sheet_1", - "name": "hole_7_sheet_1", - "sample_id": null, - "children": [], - "parent": "plate_1_materialhole_1_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "info": "NMC", - "mass": 5.0, - "material_type": "positive_electrode", - "thickness": 0.1 - } - }, - { - "id": "plate_1_materialhole_2_0", - "name": "plate_1_materialhole_2_0", - "sample_id": null, - "children": [ - "hole_8_sheet_1" - ], - "parent": "plate_1", - "type": "container", - "class": "", - "position": { - "x": 10.5, - "y": 30.5, - "z": 1 - }, - "config": { - "type": "MaterialHole", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "depth": 1.0, - "diameter": 14, - "info": "", - "max_sheets": 1 - } - }, - { - "id": "hole_8_sheet_1", - "name": "hole_8_sheet_1", - "sample_id": null, - "children": [], - "parent": "plate_1_materialhole_2_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "info": "NMC", - "mass": 5.0, - "material_type": "positive_electrode", - "thickness": 0.1 - } - }, - { - "id": "plate_1_materialhole_2_1", - "name": "plate_1_materialhole_2_1", - "sample_id": null, - "children": [ - "hole_9_sheet_1" - ], - "parent": "plate_1", - "type": "container", - "class": "", - "position": { - "x": 10.5, - "y": 10.5, - "z": 1 - }, - "config": { - "type": "MaterialHole", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "depth": 1.0, - "diameter": 14, - "info": "", - "max_sheets": 1 - } - }, - { - "id": "hole_9_sheet_1", - "name": "hole_9_sheet_1", - "sample_id": null, - "children": [], - "parent": "plate_1_materialhole_2_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "info": "NMC", - "mass": 5.0, - "material_type": "positive_electrode", - "thickness": 0.1 - } - }, - { - "id": "plate_1_materialhole_2_2", - "name": "plate_1_materialhole_2_2", - "sample_id": null, - "children": [ - "hole_10_sheet_1" - ], - "parent": "plate_1", - "type": "container", - "class": "", - "position": { - "x": 10.5, - "y": -9.5, - "z": 1 - }, - "config": { - "type": "MaterialHole", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "depth": 1.0, - "diameter": 14, - "info": "", - "max_sheets": 1 - } - }, - { - "id": "hole_10_sheet_1", - "name": "hole_10_sheet_1", - "sample_id": null, - "children": [], - "parent": "plate_1_materialhole_2_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "info": "NMC", - "mass": 5.0, - "material_type": "positive_electrode", - "thickness": 0.1 - } - }, - { - "id": "plate_1_materialhole_2_3", - "name": "plate_1_materialhole_2_3", - "sample_id": null, - "children": [ - "hole_11_sheet_1" - ], - "parent": "plate_1", - "type": "container", - "class": "", - "position": { - "x": 10.5, - "y": -29.5, - "z": 1 - }, - "config": { - "type": "MaterialHole", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "depth": 1.0, - "diameter": 14, - "info": "", - "max_sheets": 1 - } - }, - { - "id": "hole_11_sheet_1", - "name": "hole_11_sheet_1", - "sample_id": null, - "children": [], - "parent": "plate_1_materialhole_2_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "info": "NMC", - "mass": 5.0, - "material_type": "positive_electrode", - "thickness": 0.1 - } - }, - { - "id": "plate_1_materialhole_3_0", - "name": "plate_1_materialhole_3_0", - "sample_id": null, - "children": [ - "hole_12_sheet_1" - ], - "parent": "plate_1", - "type": "container", - "class": "", - "position": { - "x": 30.5, - "y": 30.5, - "z": 1 - }, - "config": { - "type": "MaterialHole", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "depth": 1.0, - "diameter": 14, - "info": "", - "max_sheets": 1 - } - }, - { - "id": "hole_12_sheet_1", - "name": "hole_12_sheet_1", - "sample_id": null, - "children": [], - "parent": "plate_1_materialhole_3_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "info": "NMC", - "mass": 5.0, - "material_type": "positive_electrode", - "thickness": 0.1 - } - }, - { - "id": "plate_1_materialhole_3_1", - "name": "plate_1_materialhole_3_1", - "sample_id": null, - "children": [ - "hole_13_sheet_1" - ], - "parent": "plate_1", - "type": "container", - "class": "", - "position": { - "x": 30.5, - "y": 10.5, - "z": 1 - }, - "config": { - "type": "MaterialHole", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "depth": 1.0, - "diameter": 14, - "info": "", - "max_sheets": 1 - } - }, - { - "id": "hole_13_sheet_1", - "name": "hole_13_sheet_1", - "sample_id": null, - "children": [], - "parent": "plate_1_materialhole_3_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "info": "NMC", - "mass": 5.0, - "material_type": "positive_electrode", - "thickness": 0.1 - } - }, - { - "id": "plate_1_materialhole_3_2", - "name": "plate_1_materialhole_3_2", - "sample_id": null, - "children": [ - "hole_14_sheet_1" - ], - "parent": "plate_1", - "type": "container", - "class": "", - "position": { - "x": 30.5, - "y": -9.5, - "z": 1 - }, - "config": { - "type": "MaterialHole", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "depth": 1.0, - "diameter": 14, - "info": "", - "max_sheets": 1 - } - }, - { - "id": "hole_14_sheet_1", - "name": "hole_14_sheet_1", - "sample_id": null, - "children": [], - "parent": "plate_1_materialhole_3_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "info": "NMC", - "mass": 5.0, - "material_type": "positive_electrode", - "thickness": 0.1 - } - }, - { - "id": "plate_1_materialhole_3_3", - "name": "plate_1_materialhole_3_3", - "sample_id": null, - "children": [ - "hole_15_sheet_1" - ], - "parent": "plate_1", - "type": "container", - "class": "", - "position": { - "x": 30.5, - "y": -29.5, - "z": 1 - }, - "config": { - "type": "MaterialHole", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "depth": 1.0, - "diameter": 14, - "info": "", - "max_sheets": 1 - } - }, - { - "id": "hole_15_sheet_1", - "name": "hole_15_sheet_1", - "sample_id": null, - "children": [], - "parent": "plate_1_materialhole_3_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 1, - "size_y": 1, - "size_z": 1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "info": "NMC", - "mass": 5.0, - "material_type": "positive_electrode", - "thickness": 0.1 - } - } - ], - "links": [] -} \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py deleted file mode 100644 index eb23be5..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ /dev/null @@ -1,1163 +0,0 @@ -import csv -import json -import os -import threading -import time -from datetime import datetime -from typing import Any, Dict, Optional -from pylabrobot.resources import Resource as PLRResource -from unilabos_msgs.msg import Resource -from unilabos.device_comms.modbus_plc.client import ModbusTcpClient -from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import MaterialHole, MaterialPlate -from unilabos.devices.workstation.workstation_base import WorkstationBase -from unilabos.device_comms.modbus_plc.client import TCPClient, ModbusNode, PLCWorkflow, ModbusWorkflow, WorkflowAction, BaseClient -from unilabos.device_comms.modbus_plc.modbus import DeviceType, Base as ModbusNodeBase, DataType, WorderOrder -from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import * -from unilabos.ros.nodes.base_device_node import ROS2DeviceNode, BaseROS2DeviceNode -from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode - -#构建物料系统 - -class CoinCellAssemblyWorkstation(WorkstationBase): - def __init__( - self, - station_resource: CoincellDeck, - address: str = "192.168.1.20", - port: str = "502", - debug_mode: bool = True, - *args, - **kwargs, - ): - super().__init__( - #桌子 - station_resource=station_resource, - *args, - **kwargs, - ) - self.debug_mode = debug_mode - self.station_resource = station_resource - """ 连接初始化 """ - modbus_client = TCPClient(addr=address, port=port) - print("modbus_client", modbus_client) - if not debug_mode: - modbus_client.client.connect() - count = 100 - while count >0: - count -=1 - if modbus_client.client.is_socket_open(): - break - time.sleep(2) - if not modbus_client.client.is_socket_open(): - raise ValueError('modbus tcp connection failed') - else: - print("测试模式,跳过连接") - - """ 工站的配置 """ - self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_a.csv')) - self.client = modbus_client.register_node_list(self.nodes) - self.success = False - self.allow_data_read = False #允许读取函数运行标志位 - self.csv_export_thread = None - self.csv_export_running = False - self.csv_export_file = None - self.coin_num_N = 0 #已组装电池数量 - #创建一个物料台面,包含两个极片板 - #self.deck = create_a_coin_cell_deck() - - #self._ros_node.update_resource(self.deck) - - #ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ - # "resources": [self.deck] - #}) - - - def post_init(self, ros_node: ROS2WorkstationNode): - self._ros_node = ros_node - #self.deck = create_a_coin_cell_deck() - ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ - "resources": [self.station_resource] - }) - - # 批量操作在这里写 - async def change_hole_sheet_to_2(self, hole: MaterialHole): - hole._unilabos_state["max_sheets"] = 2 - return await self._ros_node.update_resource(hole) - - - async def fill_plate(self): - plate_1: MaterialPlate = self.station_resource.children[0].children[0] - #plate_1 - return await self._ros_node.update_resource(plate_1) - - #def run_assembly(self, wf_name: str, resource: PLRResource, params: str = "\{\}"): - # """启动工作流""" - # self.current_workflow_status = WorkflowStatus.RUNNING - # logger.info(f"工作站 {self.device_id} 启动工作流: {wf_name}") -# - # # TODO: 实现工作流逻辑 -# - # anode_sheet = self.deck.get_resource("anode_sheet") - - """ Action逻辑代码 """ - def _sys_start_cmd(self, cmd=None): - """设备启动命令 (可读写)""" - if cmd is not None: # 写入模式 - self.success = False - node = self.client.use_node('COIL_SYS_START_CMD') - ret = node.write(cmd) - print(ret) - self.success = True - return self.success - else: # 读取模式 - cmd_feedback, read_err = self.client.use_node('COIL_SYS_START_CMD').read(1) - return cmd_feedback[0] - - def _sys_stop_cmd(self, cmd=None): - """设备停止命令 (可读写)""" - if cmd is not None: # 写入模式 - self.success = False - node = self.client.use_node('COIL_SYS_STOP_CMD') - node.write(cmd) - self.success = True - return self.success - else: # 读取模式 - cmd_feedback, read_err = self.client.use_node('COIL_SYS_STOP_CMD').read(1) - return cmd_feedback[0] - - def _sys_reset_cmd(self, cmd=None): - """设备复位命令 (可读写)""" - if cmd is not None: - self.success = False - self.client.use_node('COIL_SYS_RESET_CMD').write(cmd) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('COIL_SYS_RESET_CMD').read(1) - return cmd_feedback[0] - - def _sys_hand_cmd(self, cmd=None): - """手动模式命令 (可读写)""" - if cmd is not None: - self.success = False - self.client.use_node('COIL_SYS_HAND_CMD').write(cmd) - self.success = True - print("步骤0") - return self.success - else: - cmd_feedback, read_err = self.client.use_node('COIL_SYS_HAND_CMD').read(1) - return cmd_feedback[0] - - def _sys_auto_cmd(self, cmd=None): - """自动模式命令 (可读写)""" - if cmd is not None: - self.success = False - self.client.use_node('COIL_SYS_AUTO_CMD').write(cmd) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('COIL_SYS_AUTO_CMD').read(1) - return cmd_feedback[0] - - def _sys_init_cmd(self, cmd=None): - """初始化命令 (可读写)""" - if cmd is not None: - self.success = False - self.client.use_node('COIL_SYS_INIT_CMD').write(cmd) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('COIL_SYS_INIT_CMD').read(1) - return cmd_feedback[0] - - def _unilab_send_msg_succ_cmd(self, cmd=None): - """UNILAB发送配方完毕 (可读写)""" - if cmd is not None: - self.success = False - self.client.use_node('COIL_UNILAB_SEND_MSG_SUCC_CMD').write(cmd) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('COIL_UNILAB_SEND_MSG_SUCC_CMD').read(1) - return cmd_feedback[0] - - def _unilab_rec_msg_succ_cmd(self, cmd=None): - """UNILAB接收测试电池数据完毕 (可读写)""" - if cmd is not None: - self.success = False - self.client.use_node('COIL_UNILAB_REC_MSG_SUCC_CMD').write(cmd) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('COIL_UNILAB_REC_MSG_SUCC_CMD').read(1) - return cmd_feedback - - - # ====================== 命令类指令(REG_x_) ====================== - def _unilab_send_msg_electrolyte_num(self, num=None): - """UNILAB写电解液使用瓶数(可读写)""" - if num is not None: - self.success = False - ret = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').write(num) - print(ret) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').read(1) - return cmd_feedback[0] - - def _unilab_send_msg_electrolyte_use_num(self, use_num=None): - """UNILAB写单次电解液使用瓶数(可读写)""" - if use_num is not None: - self.success = False - self.client.use_node('REG_MSG_ELECTROLYTE_USE_NUM').write(use_num) - self.success = True - return self.success - else: - return False - - def _unilab_send_msg_assembly_type(self, num=None): - """UNILAB写组装参数""" - if num is not None: - self.success = False - self.client.use_node('REG_MSG_ASSEMBLY_TYPE').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('REG_MSG_ASSEMBLY_TYPE').read(1) - return cmd_feedback[0] - - def _unilab_send_msg_electrolyte_vol(self, vol=None): - """UNILAB写电解液吸取量参数""" - if vol is not None: - self.success = False - self.client.use_node('REG_MSG_ELECTROLYTE_VOLUME').write(vol, data_type=DataType.FLOAT32, word_order=WorderOrder.LITTLE) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_VOLUME').read(2, word_order=WorderOrder.LITTLE) - return cmd_feedback[0] - - def _unilab_send_msg_assembly_pressure(self, vol=None): - """UNILAB写电池压制力""" - if vol is not None: - self.success = False - self.client.use_node('REG_MSG_ASSEMBLY_PRESSURE').write(vol, data_type=DataType.FLOAT32, word_order=WorderOrder.LITTLE) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('REG_MSG_ASSEMBLY_PRESSURE').read(2, word_order=WorderOrder.LITTLE) - return cmd_feedback[0] - - # ==================== 0905新增内容(COIL_x_STATUS) ==================== - def _unilab_send_electrolyte_bottle_num(self, num=None): - """UNILAB发送电解液瓶数完毕""" - if num is not None: - self.success = False - self.client.use_node('UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM').read(1) - return cmd_feedback[0] - - def _unilab_rece_electrolyte_bottle_num(self, num=None): - """设备请求接受电解液瓶数""" - if num is not None: - self.success = False - self.client.use_node('UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM').read(1) - return cmd_feedback[0] - - def _reg_msg_electrolyte_num(self, num=None): - """电解液已使用瓶数""" - if num is not None: - self.success = False - self.client.use_node('REG_MSG_ELECTROLYTE_NUM').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').read(1) - return cmd_feedback[0] - - def _reg_data_electrolyte_use_num(self, num=None): - """单瓶电解液完成组装数""" - if num is not None: - self.success = False - self.client.use_node('REG_DATA_ELECTROLYTE_USE_NUM').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_USE_NUM').read(1) - return cmd_feedback[0] - - def _unilab_send_finished_cmd(self, num=None): - """Unilab发送已知一组组装完成信号""" - if num is not None: - self.success = False - self.client.use_node('UNILAB_SEND_FINISHED_CMD').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('UNILAB_SEND_FINISHED_CMD').read(1) - return cmd_feedback[0] - - def _unilab_rece_finished_cmd(self, num=None): - """Unilab接收已知一组组装完成信号""" - if num is not None: - self.success = False - self.client.use_node('UNILAB_RECE_FINISHED_CMD').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('UNILAB_RECE_FINISHED_CMD').read(1) - return cmd_feedback[0] - - - - # ==================== 状态类属性(COIL_x_STATUS) ==================== - def _sys_start_status(self) -> bool: - """设备启动中( BOOL)""" - status, read_err = self.client.use_node('COIL_SYS_START_STATUS').read(1) - return status[0] - - def _sys_stop_status(self) -> bool: - """设备停止中( BOOL)""" - status, read_err = self.client.use_node('COIL_SYS_STOP_STATUS').read(1) - return status[0] - - def _sys_reset_status(self) -> bool: - """设备复位中( BOOL)""" - status, read_err = self.client.use_node('COIL_SYS_RESET_STATUS').read(1) - return status[0] - - def _sys_init_status(self) -> bool: - """设备初始化完成( BOOL)""" - status, read_err = self.client.use_node('COIL_SYS_INIT_STATUS').read(1) - return status[0] - - # 查找资源 - def modify_deck_name(self, resource_name: str): - # figure_res = self._ros_node.resource_tracker.figure_resource({"name": resource_name}) - # print(f"!!! figure_res: {type(figure_res)}") - self.station_resource.children[1] - return - - @property - def sys_status(self) -> str: - if self.debug_mode: - return "设备调试模式" - if self._sys_start_status(): - return "设备启动中" - elif self._sys_stop_status(): - return "设备停止中" - elif self._sys_reset_status(): - return "设备复位中" - elif self._sys_init_status(): - return "设备初始化中" - else: - return "未知状态" - - def _sys_hand_status(self) -> bool: - """设备手动模式( BOOL)""" - status, read_err = self.client.use_node('COIL_SYS_HAND_STATUS').read(1) - return status[0] - - def _sys_auto_status(self) -> bool: - """设备自动模式( BOOL)""" - status, read_err = self.client.use_node('COIL_SYS_AUTO_STATUS').read(1) - return status[0] - - @property - def sys_mode(self) -> str: - if self.debug_mode: - return "设备调试模式" - if self._sys_hand_status(): - return "设备手动模式" - elif self._sys_auto_status(): - return "设备自动模式" - else: - return "未知模式" - - @property - def request_rec_msg_status(self) -> bool: - """设备请求接受配方( BOOL)""" - if self.debug_mode: - return True - status, read_err = self.client.use_node('COIL_REQUEST_REC_MSG_STATUS').read(1) - return status[0] - - @property - def request_send_msg_status(self) -> bool: - """设备请求发送测试数据( BOOL)""" - if self.debug_mode: - return True - status, read_err = self.client.use_node('COIL_REQUEST_SEND_MSG_STATUS').read(1) - return status[0] - - # ======================= 其他属性(特殊功能) ======================== - ''' - @property - def warning_1(self) -> bool: - status, read_err = self.client.use_node('COIL_WARNING_1').read(1) - return status[0] - ''' - # ===================== 生产数据区 ====================== - - @property - def data_assembly_coin_cell_num(self) -> int: - """已完成电池数量 (INT16)""" - if self.debug_mode: - return 0 - num, read_err = self.client.use_node('REG_DATA_ASSEMBLY_COIN_CELL_NUM').read(1) - return num - - @property - def data_assembly_time(self) -> float: - """单颗电池组装时间 (秒, REAL/FLOAT32)""" - if self.debug_mode: - return 0 - time, read_err = self.client.use_node('REG_DATA_ASSEMBLY_PER_TIME').read(2, word_order=WorderOrder.LITTLE) - return time - - @property - def data_open_circuit_voltage(self) -> float: - """开路电压值 (FLOAT32)""" - if self.debug_mode: - return 0 - vol, read_err = self.client.use_node('REG_DATA_OPEN_CIRCUIT_VOLTAGE').read(2, word_order=WorderOrder.LITTLE) - return vol - - @property - def data_axis_x_pos(self) -> float: - """分液X轴当前位置 (FLOAT32)""" - if self.debug_mode: - return 0 - pos, read_err = self.client.use_node('REG_DATA_AXIS_X_POS').read(2, word_order=WorderOrder.LITTLE) - return pos - - @property - def data_axis_y_pos(self) -> float: - """分液Y轴当前位置 (FLOAT32)""" - if self.debug_mode: - return 0 - pos, read_err = self.client.use_node('REG_DATA_AXIS_Y_POS').read(2, word_order=WorderOrder.LITTLE) - return pos - - @property - def data_axis_z_pos(self) -> float: - """分液Z轴当前位置 (FLOAT32)""" - if self.debug_mode: - return 0 - pos, read_err = self.client.use_node('REG_DATA_AXIS_Z_POS').read(2, word_order=WorderOrder.LITTLE) - return pos - - @property - def data_pole_weight(self) -> float: - """当前电池正极片称重数据 (FLOAT32)""" - if self.debug_mode: - return 0 - weight, read_err = self.client.use_node('REG_DATA_POLE_WEIGHT').read(2, word_order=WorderOrder.LITTLE) - return weight - - @property - def data_assembly_pressure(self) -> int: - """当前电池压制力 (INT16)""" - if self.debug_mode: - return 0 - pressure, read_err = self.client.use_node('REG_DATA_ASSEMBLY_PRESSURE').read(1) - return pressure - - @property - def data_electrolyte_volume(self) -> int: - """当前电解液加注量 (INT16)""" - if self.debug_mode: - return 0 - vol, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_VOLUME').read(1) - return vol - - @property - def data_coin_num(self) -> int: - """当前电池数量 (INT16)""" - if self.debug_mode: - return 0 - num, read_err = self.client.use_node('REG_DATA_COIN_NUM').read(1) - return num - - @property - def data_coin_cell_code(self) -> str: - """电池二维码序列号 (STRING)""" - try: - # 尝试不同的字节序读取 - code_little, read_err = self.client.use_node('REG_DATA_COIN_CELL_CODE').read(10, word_order=WorderOrder.LITTLE) - print(code_little) - clean_code = code_little[-8:][::-1] - return clean_code - except Exception as e: - print(f"读取电池二维码失败: {e}") - return "N/A" - - - @property - def data_electrolyte_code(self) -> str: - try: - # 尝试不同的字节序读取 - code_little, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_CODE').read(10, word_order=WorderOrder.LITTLE) - print(code_little) - clean_code = code_little[-8:][::-1] - return clean_code - except Exception as e: - print(f"读取电解液二维码失败: {e}") - return "N/A" - - # ===================== 环境监控区 ====================== - @property - def data_glove_box_pressure(self) -> float: - """手套箱压力 (bar, FLOAT32)""" - if self.debug_mode: - return 0 - status, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_PRESSURE').read(2, word_order=WorderOrder.LITTLE) - return status - - @property - def data_glove_box_o2_content(self) -> float: - """手套箱氧含量 (ppm, FLOAT32)""" - if self.debug_mode: - return 0 - value, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_O2_CONTENT').read(2, word_order=WorderOrder.LITTLE) - return value - - @property - def data_glove_box_water_content(self) -> float: - """手套箱水含量 (ppm, FLOAT32)""" - if self.debug_mode: - return 0 - value, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_WATER_CONTENT').read(2, word_order=WorderOrder.LITTLE) - return value - -# @property -# def data_stack_vision_code(self) -> int: -# """物料堆叠复检图片编码 (INT16)""" -# if self.debug_mode: -# return 0 -# code, read_err = self.client.use_node('REG_DATA_STACK_VISON_CODE').read(1) -# #code, _ = self.client.use_node('REG_DATA_STACK_VISON_CODE').read(1).type -# print(f"读取物料堆叠复检图片编码", {code}, "error", type(code)) -# #print(code.type) -# # print(read_err) -# return int(code) - - def func_pack_device_init(self): - #切换手动模式 - print("切换手动模式") - self._sys_hand_cmd(True) - time.sleep(1) - while (self._sys_hand_status()) == False: - print("waiting for hand_cmd") - time.sleep(1) - #设备初始化 - self._sys_init_cmd(True) - time.sleep(1) - #sys_init_status为bool值,不加括号 - while (self._sys_init_status())== False: - print("waiting for init_cmd") - time.sleep(1) - #手动按钮置回False - self._sys_hand_cmd(False) - time.sleep(1) - while (self._sys_hand_cmd()) == True: - print("waiting for hand_cmd to False") - time.sleep(1) - #初始化命令置回False - self._sys_init_cmd(False) - time.sleep(1) - while (self._sys_init_cmd()) == True: - print("waiting for init_cmd to False") - time.sleep(1) - - def func_pack_device_auto(self): - #切换自动 - print("切换自动模式") - self._sys_auto_cmd(True) - time.sleep(1) - while (self._sys_auto_status()) == False: - print("waiting for auto_status") - time.sleep(1) - #自动按钮置False - self._sys_auto_cmd(False) - time.sleep(1) - while (self._sys_auto_cmd()) == True: - print("waiting for auto_cmd") - time.sleep(1) - - def func_pack_device_start(self): - #切换自动 - print("启动") - self._sys_start_cmd(True) - time.sleep(1) - while (self._sys_start_status()) == False: - print("waiting for start_status") - time.sleep(1) - #自动按钮置False - self._sys_start_cmd(False) - time.sleep(1) - while (self._sys_start_cmd()) == True: - print("waiting for start_cmd") - time.sleep(1) - - def func_pack_send_bottle_num(self, bottle_num): - bottle_num = int(bottle_num) - #发送电解液平台数 - print("启动") - while (self._unilab_rece_electrolyte_bottle_num()) == False: - print("waiting for rece_electrolyte_bottle_num to True") - # self.client.use_node('8520').write(True) - time.sleep(1) - #发送电解液瓶数为2 - self._reg_msg_electrolyte_num(bottle_num) - time.sleep(1) - #完成信号置True - self._unilab_send_electrolyte_bottle_num(True) - time.sleep(1) - #检测到依华已接收 - while (self._unilab_rece_electrolyte_bottle_num()) == True: - print("waiting for rece_electrolyte_bottle_num to False") - time.sleep(1) - #完成信号置False - self._unilab_send_electrolyte_bottle_num(False) - time.sleep(1) - #自动按钮置False - - - # 下发参数 - #def func_pack_send_msg_cmd(self, elec_num: int, elec_use_num: int, elec_vol: float, assembly_type: int, assembly_pressure: int) -> bool: - # """UNILAB写参数""" - # while (self.request_rec_msg_status) == False: - # print("wait for res_msg") - # time.sleep(1) - # self.success = False - # self._unilab_send_msg_electrolyte_num(elec_num) - # time.sleep(1) - # self._unilab_send_msg_electrolyte_use_num(elec_use_num) - # time.sleep(1) - # self._unilab_send_msg_electrolyte_vol(elec_vol) - # time.sleep(1) - # self._unilab_send_msg_assembly_type(assembly_type) - # time.sleep(1) - # self._unilab_send_msg_assembly_pressure(assembly_pressure) - # time.sleep(1) - # self._unilab_send_msg_succ_cmd(True) - # time.sleep(1) - # self._unilab_send_msg_succ_cmd(False) - # #将允许读取标志位置True - # self.allow_data_read = True - # self.success = True - # return self.success - - def func_pack_send_msg_cmd(self, elec_use_num) -> bool: - """UNILAB写参数""" - while (self.request_rec_msg_status) == False: - print("wait for request_rec_msg_status to True") - time.sleep(1) - self.success = False - #self._unilab_send_msg_electrolyte_num(elec_num) - time.sleep(1) - self._unilab_send_msg_electrolyte_use_num(elec_use_num) - time.sleep(1) - self._unilab_send_msg_succ_cmd(True) - time.sleep(1) - while (self.request_rec_msg_status) == True: - print("wait for request_rec_msg_status to False") - time.sleep(1) - self._unilab_send_msg_succ_cmd(False) - #将允许读取标志位置True - self.allow_data_read = True - self.success = True - return self.success - - def func_pack_get_msg_cmd(self, file_path: str="D:\\coin_cell_data") -> bool: - """UNILAB读参数""" - while self.request_send_msg_status == False: - print("waiting for send_read_msg_status to True") - time.sleep(1) - data_open_circuit_voltage = self.data_open_circuit_voltage - data_pole_weight = self.data_pole_weight - data_assembly_time = self.data_assembly_time - data_assembly_pressure = self.data_assembly_pressure - data_electrolyte_volume = self.data_electrolyte_volume - data_coin_num = self.data_coin_num - data_electrolyte_code = self.data_electrolyte_code - data_coin_cell_code = self.data_coin_cell_code - print("data_open_circuit_voltage", data_open_circuit_voltage) - print("data_pole_weight", data_pole_weight) - print("data_assembly_time", data_assembly_time) - print("data_assembly_pressure", data_assembly_pressure) - print("data_electrolyte_volume", data_electrolyte_volume) - print("data_coin_num", data_coin_num) - print("data_electrolyte_code", data_electrolyte_code) - print("data_coin_cell_code", data_coin_cell_code) - #接收完信息后,读取完毕标志位置True - liaopan3 = self.station_resource.get_resource("\u7535\u6c60\u6599\u76d8") - #把物料解绑后放到另一盘上 - battery = ElectrodeSheet(name=f"battery_{self.coin_num_N}", size_x=14, size_y=14, size_z=2) - battery._unilabos_state = { - "electrolyte_name": data_coin_cell_code, - "data_electrolyte_code": data_electrolyte_code, - "open_circuit_voltage": data_open_circuit_voltage, - "assembly_pressure": data_assembly_pressure, - "electrolyte_volume": data_electrolyte_volume - } - liaopan3.children[self.coin_num_N].assign_child_resource(battery, location=None) - #print(jipian2.parent) - ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ - "resources": [self.station_resource] - }) - - - self._unilab_rec_msg_succ_cmd(True) - time.sleep(1) - #等待允许读取标志位置False - while self.request_send_msg_status == True: - print("waiting for send_msg_status to False") - time.sleep(1) - self._unilab_rec_msg_succ_cmd(False) - time.sleep(1) - #将允许读取标志位置True - time_date = datetime.now().strftime("%Y%m%d") - #秒级时间戳用于标记每一行电池数据 - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - #生成输出文件的变量 - self.csv_export_file = os.path.join(file_path, f"date_{time_date}.csv") - #将数据存入csv文件 - if not os.path.exists(self.csv_export_file): - #创建一个表头 - with open(self.csv_export_file, 'w', newline='', encoding='utf-8') as csvfile: - writer = csv.writer(csvfile) - writer.writerow([ - 'Time', 'open_circuit_voltage', 'pole_weight', - 'assembly_time', 'assembly_pressure', 'electrolyte_volume', - 'coin_num', 'electrolyte_code', 'coin_cell_code' - ]) - #立刻写入磁盘 - csvfile.flush() - #开始追加电池信息 - with open(self.csv_export_file, 'a', newline='', encoding='utf-8') as csvfile: - writer = csv.writer(csvfile) - writer.writerow([ - timestamp, data_open_circuit_voltage, data_pole_weight, - data_assembly_time, data_assembly_pressure, data_electrolyte_volume, - data_coin_num, data_electrolyte_code, data_coin_cell_code - ]) - #立刻写入磁盘 - csvfile.flush() - self.success = True - return self.success - - - - def func_pack_send_finished_cmd(self) -> bool: - """UNILAB写参数""" - while (self._unilab_rece_finished_cmd()) == False: - print("wait for rece_finished_cmd to True") - time.sleep(1) - self.success = False - self._unilab_send_finished_cmd(True) - time.sleep(1) - while (self._unilab_rece_finished_cmd()) == True: - print("wait for rece_finished_cmd to False") - time.sleep(1) - self._unilab_send_finished_cmd(False) - #将允许读取标志位置True - self.success = True - return self.success - - - - def func_allpack_cmd(self, elec_num, elec_use_num, file_path: str="D:\\coin_cell_data") -> bool: - elec_num, elec_use_num = int(elec_num), int(elec_use_num) - summary_csv_file = os.path.join(file_path, "duandian.csv") - # 如果断点文件存在,先读取之前的进度 - if os.path.exists(summary_csv_file): - read_status_flag = True - with open(summary_csv_file, 'r', newline='', encoding='utf-8') as csvfile: - reader = csv.reader(csvfile) - header = next(reader) # 跳过标题行 - data_row = next(reader) # 读取数据行 - if len(data_row) >= 2: - elec_num_r = int(data_row[0]) - elec_use_num_r = int(data_row[1]) - elec_num_N = int(data_row[2]) - elec_use_num_N = int(data_row[3]) - coin_num_N = int(data_row[4]) - if elec_num_r == elec_num and elec_use_num_r == elec_use_num: - print("断点文件与当前任务匹配,继续") - else: - print("断点文件中elec_num、elec_use_num与当前任务不匹配,请检查任务下发参数或修改断点文件") - return False - print(f"从断点文件读取进度: elec_num_N={elec_num_N}, elec_use_num_N={elec_use_num_N}, coin_num_N={coin_num_N}") - - else: - read_status_flag = False - print("未找到断点文件,从头开始") - elec_num_N = 0 - elec_use_num_N = 0 - coin_num_N = 0 - for i in range(20): - print(f"剩余电解液瓶数: {elec_num}, 已组装电池数: {elec_use_num}") - print(f"剩余电解液瓶数: {type(elec_num)}, 已组装电池数: {type(elec_use_num)}") - print(f"剩余电解液瓶数: {type(int(elec_num))}, 已组装电池数: {type(int(elec_use_num))}") - - #如果是第一次运行,则进行初始化、切换自动、启动, 如果是断点重启则跳过。 - if read_status_flag == False: - pass - #初始化 - #self.func_pack_device_init() - #切换自动 - #self.func_pack_device_auto() - #启动,小车收回 - #self.func_pack_device_start() - #发送电解液瓶数量,启动搬运,多搬运没事 - #self.func_pack_send_bottle_num(elec_num) - last_i = elec_num_N - last_j = elec_use_num_N - for i in range(last_i, elec_num): - print(f"开始第{last_i+i+1}瓶电解液的组装") - #第一个循环从上次断点继续,后续循环从0开始 - j_start = last_j if i == last_i else 0 - self.func_pack_send_msg_cmd(elec_use_num-j_start) - - for j in range(j_start, elec_use_num): - print(f"开始第{last_i+i+1}瓶电解液的第{j+j_start+1}个电池组装") - #读取电池组装数据并存入csv - self.func_pack_get_msg_cmd(file_path) - time.sleep(1) - # TODO:读完再将电池数加一还是进入循环就将电池数加一需要考虑 - - - - # 生成断点文件 - # 生成包含elec_num_N、coin_num_N、timestamp的CSV文件 - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - with open(summary_csv_file, 'w', newline='', encoding='utf-8') as csvfile: - writer = csv.writer(csvfile) - writer.writerow(['elec_num','elec_use_num', 'elec_num_N', 'elec_use_num_N', 'coin_num_N', 'timestamp']) - writer.writerow([elec_num, elec_use_num, elec_num_N, elec_use_num_N, coin_num_N, timestamp]) - csvfile.flush() - coin_num_N += 1 - self.coin_num_N = coin_num_N - elec_use_num_N += 1 - elec_num_N += 1 - elec_use_num_N = 0 - - #循环正常结束,则删除断点文件 - os.remove(summary_csv_file) - #全部完成后等待依华发送完成信号 - self.func_pack_send_finished_cmd() - - - def func_pack_device_stop(self) -> bool: - """打包指令:设备停止""" - for i in range(3): - time.sleep(2) - print(f"输出{i}") - #print("_sys_hand_cmd", self._sys_hand_cmd()) - #time.sleep(1) - #print("_sys_hand_status", self._sys_hand_status()) - #time.sleep(1) - #print("_sys_init_cmd", self._sys_init_cmd()) - #time.sleep(1) - #print("_sys_init_status", self._sys_init_status()) - #time.sleep(1) - #print("_sys_auto_status", self._sys_auto_status()) - #time.sleep(1) - #print("data_axis_y_pos", self.data_axis_y_pos) - #time.sleep(1) - #self.success = False - #with open('action_device_stop.json', 'r', encoding='utf-8') as f: - # action_json = json.load(f) - #self.client.execute_procedure_from_json(action_json) - #self.success = True - #return self.success - - def fun_wuliao_test(self) -> bool: - #找到data_init中构建的2个物料盘 - liaopan3 = self.station_resource.get_resource("\u7535\u6c60\u6599\u76d8") - for i in range(16): - battery = ElectrodeSheet(name=f"battery_{i}", size_x=16, size_y=16, size_z=2) - battery._unilabos_state = { - "diameter": 20.0, - "height": 20.0, - "assembly_pressure": i, - "electrolyte_volume": 20.0, - "electrolyte_name": f"DP{i}" - } - liaopan3.children[i].assign_child_resource(battery, location=None) - - ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ - "resources": [self.station_resource] - }) - time.sleep(4) - # 数据读取与输出 - def func_read_data_and_output(self, file_path: str="D:\\coin_cell_data"): - # 检查CSV导出是否正在运行,已运行则跳出,防止同时启动两个while循环 - if self.csv_export_running: - return False, "读取已在运行中" - - #若不存在该目录则创建 - if not os.path.exists(file_path): - os.makedirs(file_path) - print(f"创建目录: {file_path}") - - # 只要允许读取标志位为true,就持续运行该函数,直到触发停止条件 - while self.allow_data_read: - - #函数运行标志位,确保只同时启动一个导出函数 - self.csv_export_running = True - - #等待接收结果标志位置True - while self.request_send_msg_status == False: - print("waiting for send_msg_status to True") - time.sleep(1) - #日期时间戳用于按天存放csv文件 - time_date = datetime.now().strftime("%Y%m%d") - #秒级时间戳用于标记每一行电池数据 - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - #生成输出文件的变量 - self.csv_export_file = os.path.join(file_path, f"date_{time_date}.csv") - - #接收信息 - data_open_circuit_voltage = self.data_open_circuit_voltage - data_pole_weight = self.data_pole_weight - data_assembly_time = self.data_assembly_time - data_assembly_pressure = self.data_assembly_pressure - data_electrolyte_volume = self.data_electrolyte_volume - data_coin_num = self.data_coin_num - data_electrolyte_code = self.data_electrolyte_code - data_coin_cell_code = self.data_coin_cell_code - # 电解液瓶位置 - elec_bottle_site = 2 - # 极片夹取位置(应当通过寄存器读光标) - Pos_elec_site = 0 - Al_elec_site = 0 - Gasket_site = 0 - - #接收完信息后,读取完毕标志位置True - self._unilab_rec_msg_succ_cmd()# = True - #等待允许读取标志位置False - while self.request_send_msg_status == True: - print("waiting for send_msg_status to False") - time.sleep(1) - self._unilab_rec_msg_succ_cmd()# = False - - #此处操作物料信息(如果中途报错停止,如何) - #报错怎么办(加个判断标志位,如果发生错误,则根据停止位置扣除物料) - #根据物料光标判断取哪个物料(人工摆盘,电解液瓶,移液枪头都有光标位置,寄存器读即可) - - #物料读取操作写在这里 - #在这里进行物料调取 - #转移物料瓶,elec_bottle_site对应第几瓶电解液(从依华寄存器读取) - # transfer_bottle(deck, elec_bottle_site) - # #找到电解液瓶的对象 - # electrolyte_rack = deck.get_resource("electrolyte_rack") - # pending_positions = electrolyte_rack.get_pending_positions()[elec_bottle_site] - # # TODO: 瓶子取液体操作需要加入 -# -# - # #找到压制工站对应的对象 - # battery_press_slot = deck.get_resource("battery_press_1") - # #创建一个新电池 - # test_battery = Battery( - # name=f"test_battery_{data_coin_num}", - # diameter=20.0, # 与压制槽直径匹配 - # height=3.0, # 电池高度 - # max_volume=100.0, # 100μL容量 - # barcode=data_coin_cell_code, # 电池条码 - # ) - # if battery_press_slot.has_battery(): - # return False, "压制工站已有电池,无法放置新电池" - # #在压制位放置电池 - # battery_press_slot.place_battery(test_battery) - # #从第一个子弹夹中取料 - # clip_magazine_1_hole = self.deck.get_resource("clip_magazine_1").get_item(Pos_elec_site) - # clip_magazine_2_hole = self.deck.get_resource("clip_magazine_2").get_item(Al_elec_site) - # clip_magazine_3_hole = self.deck.get_resource("clip_magazine_3").get_item(Gasket_site) - # - # if clip_magazine_1_hole.get_sheet_count() > 0: # 检查洞位是否有极片 - # electrode_sheet_1 = clip_magazine_1_hole.take_sheet() # 从洞位取出极片 - # test_battery.add_electrode_sheet(electrode_sheet_1) # 添加到电池中 - # print(f"已将极片 {electrode_sheet_1.name} 从子弹夹转移到电池") - # else: - # print("子弹夹洞位0没有极片") -# - # if clip_magazine_2_hole.get_sheet_count() > 0: # 检查洞位是否有极片 - # electrode_sheet_2 = clip_magazine_2_hole.take_sheet() # 从洞位取出极片 - # test_battery.add_electrode_sheet(electrode_sheet_2) # 添加到电池中 - # print(f"已将极片 {electrode_sheet_2.name} 从子弹夹转移到电池") - # else: - # print("子弹夹洞位0没有极片") -# - # if clip_magazine_3_hole.get_sheet_count() > 0: # 检查洞位是否有极片 - # electrode_sheet_3 = clip_magazine_3_hole.take_sheet() # 从洞位取出极片 - # test_battery.add_electrode_sheet(electrode_sheet_3) # 添加到电池中 - # print(f"已将极片 {electrode_sheet_3.name} 从子弹夹转移到电池") - # else: - # print("子弹夹洞位0没有极片") - # - # # TODO:#把电解液从瓶中取到电池夹子中 - # battery_site = deck.get_resource("battery_press_1") - # clip_magazine_battery = deck.get_resource("clip_magazine_battery") - # if battery_site.has_battery(): - # battery = battery_site.take_battery() #从压制槽取出电池 - # clip_magazine_battery.add_battery(battery) #从压制槽取出电池 -# -# -# -# - # # 保存配置到文件 - # self.deck.save("button_battery_station_layout.json", indent=2) - # print("\n台面配置已保存到: button_battery_station_layout.json") - # - # # 保存状态到文件 - # self.deck.save_state_to_file("button_battery_station_state.json", indent=2) - # print("台面状态已保存到: button_battery_station_state.json") - - - - - - - #将数据写入csv中 - #如当前目录下无同名文件则新建一个csv用于存放数据 - if not os.path.exists(self.csv_export_file): - #创建一个表头 - with open(self.csv_export_file, 'w', newline='', encoding='utf-8') as csvfile: - writer = csv.writer(csvfile) - writer.writerow([ - 'Time', 'open_circuit_voltage', 'pole_weight', - 'assembly_time', 'assembly_pressure', 'electrolyte_volume', - 'coin_num', 'electrolyte_code', 'coin_cell_code' - ]) - #立刻写入磁盘 - csvfile.flush() - #开始追加电池信息 - with open(self.csv_export_file, 'a', newline='', encoding='utf-8') as csvfile: - writer = csv.writer(csvfile) - writer.writerow([ - timestamp, data_open_circuit_voltage, data_pole_weight, - data_assembly_time, data_assembly_pressure, data_electrolyte_volume, - data_coin_num, data_electrolyte_code, data_coin_cell_code - ]) - #立刻写入磁盘 - csvfile.flush() - - # 只要不在自动模式运行中,就将允许标志位置False - if self.sys_auto_status == False or self.sys_start_status == False: - self.allow_data_read = False - self.csv_export_running = False - time.sleep(1) - - def func_stop_read_data(self): - """停止CSV导出""" - if not self.csv_export_running: - return False, "read data未在运行" - - self.csv_export_running = False - self.allow_data_read = False - - if self.csv_export_thread and self.csv_export_thread.is_alive(): - self.csv_export_thread.join(timeout=5) - - def func_get_csv_export_status(self): - """获取CSV导出状态""" - return { - 'allow_read': self.allow_data_read, - 'running': self.csv_export_running, - 'thread_alive': self.csv_export_thread.is_alive() if self.csv_export_thread else False - } - - - ''' - # ===================== 物料管理区 ====================== - @property - def data_material_inventory(self) -> int: - """主物料库存 (数量, INT16)""" - inventory, read_err = self.client.use_node('REG_DATA_MATERIAL_INVENTORY').read(1) - return inventory - - @property - def data_tips_inventory(self) -> int: - """移液枪头库存 (数量, INT16)""" - inventory, read_err = self.client.register_node_list(self.nodes).use_node('REG_DATA_TIPS_INVENTORY').read(1) - return inventory - - ''' - - -if __name__ == "__main__": - from pylabrobot.resources import Resource - Coin_Cell = CoinCellAssemblyWorkstation(Resource("1", 1, 1, 1), debug_mode=True) - #Coin_Cell.func_pack_device_init() - #Coin_Cell.func_pack_device_auto() - #Coin_Cell.func_pack_device_start() - #Coin_Cell.func_pack_send_bottle_num(2) - #Coin_Cell.func_pack_send_msg_cmd(2) - #Coin_Cell.func_pack_get_msg_cmd() - #Coin_Cell.func_pack_get_msg_cmd() - #Coin_Cell.func_pack_send_finished_cmd() -# - #Coin_Cell.func_allpack_cmd(3, 2) - #print(Coin_Cell.data_stack_vision_code) - #print("success") - #创建一个物料台面 - - deck = create_a_coin_cell_deck() - #deck = create_a_full_coin_cell_deck() - - - ##在台面上找到料盘和极片 - #liaopan1 = deck.get_resource("liaopan1") - #liaopan2 = deck.get_resource("liaopan2") - #jipian1 = liaopan1.children[1].children[0] -## - #print(jipian1) - ##把物料解绑后放到另一盘上 - #jipian1.parent.unassign_child_resource(jipian1) - #liaopan2.children[1].assign_child_resource(jipian1, location=None) - ##print(jipian2.parent) - - liaopan1 = deck.get_resource("liaopan1") - liaopan2 = deck.get_resource("liaopan2") - for i in range(16): - #找到liaopan1上每一个jipian - jipian_linshi = liaopan1.children[i].children[0] - #把物料解绑后放到另一盘上 - print("极片:", jipian_linshi) - jipian_linshi.parent.unassign_child_resource(jipian_linshi) - liaopan2.children[i].assign_child_resource(jipian_linshi, location=None) - - - from unilabos.resources.graphio import resource_ulab_to_plr, convert_resources_to_type - #with open("./button_battery_station_resources_unilab.json", "r", encoding="utf-8") as f: - # bioyond_resources_unilab = json.load(f) - #print(f"成功读取 JSON 文件,包含 {len(bioyond_resources_unilab)} 个资源") - #ulab_resources = convert_resources_to_type(bioyond_resources_unilab, List[PLRResource]) - #print(f"转换结果类型: {type(ulab_resources)}") - #print(ulab_resources) - - - - from unilabos.resources.graphio import convert_resources_from_type - from unilabos.config.config import BasicConfig - BasicConfig.ak = "beb0c15f-2279-46a1-aba5-00eaf89aef55" - BasicConfig.sk = "15d4f25e-3512-4f9c-9bfb-43ab85e7b561" - from unilabos.app.web.client import http_client - - resources = convert_resources_from_type([deck], [Resource]) - json.dump({"nodes": resources, "links": []}, open("button_battery_station_resources_unilab.json", "w"), indent=2) - - #print(resources) - http_client.remote_addr = "https://uni-lab.test.bohrium.com/api/v1" - - http_client.resource_add(resources) \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_0910.csv b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_0910.csv deleted file mode 100644 index e840715..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_0910.csv +++ /dev/null @@ -1,44 +0,0 @@ -Name,DataType,InitValue,Comment,Attribute,DeviceType,Address -COIL_SYS_START_CMD,BOOL,,豸,,coil,8010 -COIL_SYS_STOP_CMD,BOOL,,豸ֹͣ,,coil,8020 -COIL_SYS_RESET_CMD,BOOL,,豸λ,,coil,8030 -COIL_SYS_HAND_CMD,BOOL,,豸ֶģʽ,,coil,8040 -COIL_SYS_AUTO_CMD,BOOL,,豸Զģʽ,,coil,8050 -COIL_SYS_INIT_CMD,BOOL,,豸ʼģʽ,,coil,8060 -COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,,UNILAB䷽,,coil,8700 -COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,,UNILABܲ,,coil,8710 -COIL_SYS_START_STATUS,BOOL,,豸,,coil,8210 -COIL_SYS_STOP_STATUS,BOOL,,豸ֹͣ,,coil,8220 -COIL_SYS_RESET_STATUS,BOOL,,豸λ,,coil,8230 -COIL_SYS_HAND_STATUS,BOOL,,豸ֶģʽ,,coil,8240 -COIL_SYS_AUTO_STATUS,BOOL,,豸Զģʽ,,coil,8250 -COIL_SYS_INIT_STATUS,BOOL,,豸ʼ,,coil,8260 -COIL_REQUEST_REC_MSG_STATUS,BOOL,,豸䷽,,coil,8510 -COIL_REQUEST_SEND_MSG_STATUS,BOOL,,豸Ͳ,,coil,8500 -REG_MSG_ELECTROLYTE_USE_NUM,INT16,,ƿҺʹô,,hold_register,11000 -REG_MSG_ELECTROLYTE_NUM,INT16,,Һʹƿ,,hold_register,11002 -REG_MSG_ELECTROLYTE_VOLUME,INT16,,Һȡ,,hold_register,11004 -REG_MSG_ASSEMBLY_TYPE,INT16,,װƬѵʽ,,hold_register,11006 -REG_MSG_ASSEMBLY_PRESSURE,INT16,,װѹ,,hold_register,11008 -REG_DATA_ASSEMBLY_COIN_CELL_NUM,INT16,,ǰװ,,hold_register,10000 -REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,,ǰصѹ,,hold_register,10002 -REG_DATA_AXIS_X_POS,FLOAT32,,ҺXᵱǰλ,,hold_register,10004 -REG_DATA_AXIS_Y_POS,FLOAT32,,ҺZᵱǰλ,,hold_register,10006 -REG_DATA_AXIS_Z_POS,FLOAT32,,ҺYᵱǰλ,,hold_register,10008 -REG_DATA_POLE_WEIGHT,FLOAT32,,ǰƬ,,hold_register,10010 -REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,,ǰŵװʱ,,hold_register,10012 -REG_DATA_ASSEMBLY_PRESSURE,INT16,,ǰװѹ,,hold_register,10014 -REG_DATA_ELECTROLYTE_VOLUME,INT16,,ǰҺע,,hold_register,10016 -REG_DATA_COIN_NUM,INT16,,ǰ,,hold_register,10018 -REG_DATA_ELECTROLYTE_CODE,STRING,,Һάк,,hold_register,10020 -REG_DATA_COIN_CELL_CODE,STRING,,ضάк,,hold_register,10030 -REG_DATA_STACK_VISON_CODE,STRING,,϶ѵͼƬ,,hold_register,12004 -REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,,ѹ,,hold_register,10050 -REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,,ˮ,,hold_register,10052 -REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,,,,hold_register,10054 -UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM,BOOL,,Unilabȷѷ͵Һƿź,,coil,8720 -UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM,BOOL,,UnilabɽܵҺƿ,,coil,8520 -REG_MSG_ELECTROLYTE_NUM_USED,INT16,,Һװƽ,,hold_register,496 -REG_DATA_ELECTROLYTE_USE_NUM,INT16,,ǰװƽ,,hold_register,10000 -UNILAB_SEND_FINISHED_CMD,BOOL,,Unilabյź,,coil,8730 -UNILAB_RECE_FINISHED_CMD,BOOL,,֪unilabź,,coil,8530 diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv deleted file mode 100644 index 149a683..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv +++ /dev/null @@ -1,46 +0,0 @@ -Name,DataType,InitValue,Comment,Attribute,DeviceType,Address, -COIL_SYS_START_CMD,BOOL,,,,coil,8010, -COIL_SYS_STOP_CMD,BOOL,,,,coil,8020, -COIL_SYS_RESET_CMD,BOOL,,,,coil,8030, -COIL_SYS_HAND_CMD,BOOL,,,,coil,8040, -COIL_SYS_AUTO_CMD,BOOL,,,,coil,8050, -COIL_SYS_INIT_CMD,BOOL,,,,coil,8060, -COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,,,,coil,8700, -COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,,,,coil,8710,unilab_rec_msg_succ_cmd -COIL_SYS_START_STATUS,BOOL,,,,coil,8210, -COIL_SYS_STOP_STATUS,BOOL,,,,coil,8220, -COIL_SYS_RESET_STATUS,BOOL,,,,coil,8230, -COIL_SYS_HAND_STATUS,BOOL,,,,coil,8240, -COIL_SYS_AUTO_STATUS,BOOL,,,,coil,8250, -COIL_SYS_INIT_STATUS,BOOL,,,,coil,8260, -COIL_REQUEST_REC_MSG_STATUS,BOOL,,,,coil,8500, -COIL_REQUEST_SEND_MSG_STATUS,BOOL,,,,coil,8510,request_send_msg_status -REG_MSG_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,11000, -REG_MSG_ELECTROLYTE_NUM,INT16,,,,hold_register,11002,unilab_send_msg_electrolyte_num -REG_MSG_ELECTROLYTE_VOLUME,INT16,,,,hold_register,11004,unilab_send_msg_electrolyte_vol -REG_MSG_ASSEMBLY_TYPE,INT16,,,,hold_register,11006,unilab_send_msg_assembly_type -REG_MSG_ASSEMBLY_PRESSURE,INT16,,,,hold_register,11008,unilab_send_msg_assembly_pressure -REG_DATA_ASSEMBLY_COIN_CELL_NUM,INT16,,,,hold_register,10000,data_assembly_coin_cell_num -REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,,,,hold_register,10002,data_open_circuit_voltage -REG_DATA_AXIS_X_POS,FLOAT32,,,,hold_register,10004, -REG_DATA_AXIS_Y_POS,FLOAT32,,,,hold_register,10006, -REG_DATA_AXIS_Z_POS,FLOAT32,,,,hold_register,10008, -REG_DATA_POLE_WEIGHT,FLOAT32,,,,hold_register,10010,data_pole_weight -REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,,,,hold_register,10012,data_assembly_time -REG_DATA_ASSEMBLY_PRESSURE,INT16,,,,hold_register,10014,data_assembly_pressure -REG_DATA_ELECTROLYTE_VOLUME,INT16,,,,hold_register,10016,data_electrolyte_volume -REG_DATA_COIN_NUM,INT16,,,,hold_register,10018,data_coin_num -REG_DATA_ELECTROLYTE_CODE,STRING,,,,hold_register,10020,data_electrolyte_code() -REG_DATA_COIN_CELL_CODE,STRING,,,,hold_register,10030,data_coin_cell_code() -REG_DATA_STACK_VISON_CODE,STRING,,,,hold_register,12004,data_stack_vision_code() -REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,,,,hold_register,10050,data_glove_box_pressure -REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,,,,hold_register,10052,data_glove_box_water_content -REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,,,,hold_register,10054,data_glove_box_o2_content -UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8720, -UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8520, -REG_MSG_ELECTROLYTE_NUM_USED,INT16,,,,hold_register,496, -REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,10000, -UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,8730, -UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,8530, -REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,10018,ASSEMBLY_TYPE7or8 -COIL_ALUMINUM_FOIL,BOOL,,,,coil,8340, diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_old.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_old.py deleted file mode 100644 index ac61948..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_old.py +++ /dev/null @@ -1,1149 +0,0 @@ -import csv -import json -import os -import threading -import time -from datetime import datetime -from typing import Any, Dict, Optional -from pylabrobot.resources import Resource as PLRResource -from unilabos_msgs.msg import Resource -from unilabos.device_comms.modbus_plc.client import ModbusTcpClient -from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import MaterialHole, MaterialPlate -from unilabos.devices.workstation.workstation_base import WorkstationBase -from unilabos.device_comms.modbus_plc.client import TCPClient, ModbusNode, PLCWorkflow, ModbusWorkflow, WorkflowAction, BaseClient -from unilabos.device_comms.modbus_plc.modbus import DeviceType, Base as ModbusNodeBase, DataType, WorderOrder -from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import * -from unilabos.ros.nodes.base_device_node import ROS2DeviceNode, BaseROS2DeviceNode -from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode - -#构建物料系统 - -class CoinCellAssemblyWorkstation(WorkstationBase): - def __init__( - self, - station_resource: CoincellDeck, - address: str = "192.168.1.20", - port: str = "502", - debug_mode: bool = True, - *args, - **kwargs, - ): - super().__init__( - #桌子 - station_resource=station_resource, - *args, - **kwargs, - ) - self.debug_mode = debug_mode - self.station_resource = station_resource - """ 连接初始化 """ - modbus_client = TCPClient(addr=address, port=port) - print("modbus_client", modbus_client) - if not debug_mode: - modbus_client.client.connect() - count = 100 - while count >0: - count -=1 - if modbus_client.client.is_socket_open(): - break - time.sleep(2) - if not modbus_client.client.is_socket_open(): - raise ValueError('modbus tcp connection failed') - else: - print("测试模式,跳过连接") - - """ 工站的配置 """ - self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_a.csv')) - self.client = modbus_client.register_node_list(self.nodes) - self.success = False - self.allow_data_read = False #允许读取函数运行标志位 - self.csv_export_thread = None - self.csv_export_running = False - self.csv_export_file = None - #创建一个物料台面,包含两个极片板 - #self.deck = create_a_coin_cell_deck() - - #self._ros_node.update_resource(self.deck) - - #ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ - # "resources": [self.deck] - #}) - - - def post_init(self, ros_node: ROS2WorkstationNode): - self._ros_node = ros_node - #self.deck = create_a_coin_cell_deck() - ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ - "resources": [self.station_resource] - }) - - # 批量操作在这里写 - async def change_hole_sheet_to_2(self, hole: MaterialHole): - hole._unilabos_state["max_sheets"] = 2 - return await self._ros_node.update_resource(hole) - - - async def fill_plate(self): - plate_1: MaterialPlate = self.station_resource.children[0].children[0] - #plate_1 - return await self._ros_node.update_resource(plate_1) - - #def run_assembly(self, wf_name: str, resource: PLRResource, params: str = "\{\}"): - # """启动工作流""" - # self.current_workflow_status = WorkflowStatus.RUNNING - # logger.info(f"工作站 {self.device_id} 启动工作流: {wf_name}") -# - # # TODO: 实现工作流逻辑 -# - # anode_sheet = self.deck.get_resource("anode_sheet") - - """ Action逻辑代码 """ - def _sys_start_cmd(self, cmd=None): - """设备启动命令 (可读写)""" - if cmd is not None: # 写入模式 - self.success = False - node = self.client.use_node('COIL_SYS_START_CMD') - ret = node.write(cmd) - print(ret) - self.success = True - return self.success - else: # 读取模式 - cmd_feedback, read_err = self.client.use_node('COIL_SYS_START_CMD').read(1) - return cmd_feedback[0] - - def _sys_stop_cmd(self, cmd=None): - """设备停止命令 (可读写)""" - if cmd is not None: # 写入模式 - self.success = False - node = self.client.use_node('COIL_SYS_STOP_CMD') - node.write(cmd) - self.success = True - return self.success - else: # 读取模式 - cmd_feedback, read_err = self.client.use_node('COIL_SYS_STOP_CMD').read(1) - return cmd_feedback[0] - - def _sys_reset_cmd(self, cmd=None): - """设备复位命令 (可读写)""" - if cmd is not None: - self.success = False - self.client.use_node('COIL_SYS_RESET_CMD').write(cmd) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('COIL_SYS_RESET_CMD').read(1) - return cmd_feedback[0] - - def _sys_hand_cmd(self, cmd=None): - """手动模式命令 (可读写)""" - if cmd is not None: - self.success = False - self.client.use_node('COIL_SYS_HAND_CMD').write(cmd) - self.success = True - print("步骤0") - return self.success - else: - cmd_feedback, read_err = self.client.use_node('COIL_SYS_HAND_CMD').read(1) - return cmd_feedback[0] - - def _sys_auto_cmd(self, cmd=None): - """自动模式命令 (可读写)""" - if cmd is not None: - self.success = False - self.client.use_node('COIL_SYS_AUTO_CMD').write(cmd) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('COIL_SYS_AUTO_CMD').read(1) - return cmd_feedback[0] - - def _sys_init_cmd(self, cmd=None): - """初始化命令 (可读写)""" - if cmd is not None: - self.success = False - self.client.use_node('COIL_SYS_INIT_CMD').write(cmd) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('COIL_SYS_INIT_CMD').read(1) - return cmd_feedback[0] - - def _unilab_send_msg_succ_cmd(self, cmd=None): - """UNILAB发送配方完毕 (可读写)""" - if cmd is not None: - self.success = False - self.client.use_node('COIL_UNILAB_SEND_MSG_SUCC_CMD').write(cmd) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('COIL_UNILAB_SEND_MSG_SUCC_CMD').read(1) - return cmd_feedback[0] - - def _unilab_rec_msg_succ_cmd(self, cmd=None): - """UNILAB接收测试电池数据完毕 (可读写)""" - if cmd is not None: - self.success = False - self.client.use_node('COIL_UNILAB_REC_MSG_SUCC_CMD').write(cmd) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('COIL_UNILAB_REC_MSG_SUCC_CMD').read(1) - return cmd_feedback - - - # ====================== 命令类指令(REG_x_) ====================== - def _unilab_send_msg_electrolyte_num(self, num=None): - """UNILAB写电解液使用瓶数(可读写)""" - if num is not None: - self.success = False - ret = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').write(num) - print(ret) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').read(1) - return cmd_feedback[0] - - def _unilab_send_msg_electrolyte_use_num(self, use_num=None): - """UNILAB写单次电解液使用瓶数(可读写)""" - if use_num is not None: - self.success = False - self.client.use_node('REG_MSG_ELECTROLYTE_USE_NUM').write(use_num) - self.success = True - return self.success - else: - return False - - def _unilab_send_msg_assembly_type(self, num=None): - """UNILAB写组装参数""" - if num is not None: - self.success = False - self.client.use_node('REG_MSG_ASSEMBLY_TYPE').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('REG_MSG_ASSEMBLY_TYPE').read(1) - return cmd_feedback[0] - - def _unilab_send_msg_electrolyte_vol(self, vol=None): - """UNILAB写电解液吸取量参数""" - if vol is not None: - self.success = False - self.client.use_node('REG_MSG_ELECTROLYTE_VOLUME').write(vol, data_type=DataType.FLOAT32, word_order=WorderOrder.LITTLE) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_VOLUME').read(2, word_order=WorderOrder.LITTLE) - return cmd_feedback[0] - - def _unilab_send_msg_assembly_pressure(self, vol=None): - """UNILAB写电池压制力""" - if vol is not None: - self.success = False - self.client.use_node('REG_MSG_ASSEMBLY_PRESSURE').write(vol, data_type=DataType.FLOAT32, word_order=WorderOrder.LITTLE) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('REG_MSG_ASSEMBLY_PRESSURE').read(2, word_order=WorderOrder.LITTLE) - return cmd_feedback[0] - - # ==================== 0905新增内容(COIL_x_STATUS) ==================== - def _unilab_send_electrolyte_bottle_num(self, num=None): - """UNILAB发送电解液瓶数完毕""" - if num is not None: - self.success = False - self.client.use_node('UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM').read(1) - return cmd_feedback[0] - - def _unilab_rece_electrolyte_bottle_num(self, num=None): - """设备请求接受电解液瓶数""" - if num is not None: - self.success = False - self.client.use_node('UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM').read(1) - return cmd_feedback[0] - - def _reg_msg_electrolyte_num(self, num=None): - """电解液已使用瓶数""" - if num is not None: - self.success = False - self.client.use_node('REG_MSG_ELECTROLYTE_NUM').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').read(1) - return cmd_feedback[0] - - def _reg_data_electrolyte_use_num(self, num=None): - """单瓶电解液完成组装数""" - if num is not None: - self.success = False - self.client.use_node('REG_DATA_ELECTROLYTE_USE_NUM').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_USE_NUM').read(1) - return cmd_feedback[0] - - def _unilab_send_finished_cmd(self, num=None): - """Unilab发送已知一组组装完成信号""" - if num is not None: - self.success = False - self.client.use_node('UNILAB_SEND_FINISHED_CMD').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('UNILAB_SEND_FINISHED_CMD').read(1) - return cmd_feedback[0] - - def _unilab_rece_finished_cmd(self, num=None): - """Unilab接收已知一组组装完成信号""" - if num is not None: - self.success = False - self.client.use_node('UNILAB_RECE_FINISHED_CMD').write(num) - self.success = True - return self.success - else: - cmd_feedback, read_err = self.client.use_node('UNILAB_RECE_FINISHED_CMD').read(1) - return cmd_feedback[0] - - - - # ==================== 状态类属性(COIL_x_STATUS) ==================== - def _sys_start_status(self) -> bool: - """设备启动中( BOOL)""" - status, read_err = self.client.use_node('COIL_SYS_START_STATUS').read(1) - return status[0] - - def _sys_stop_status(self) -> bool: - """设备停止中( BOOL)""" - status, read_err = self.client.use_node('COIL_SYS_STOP_STATUS').read(1) - return status[0] - - def _sys_reset_status(self) -> bool: - """设备复位中( BOOL)""" - status, read_err = self.client.use_node('COIL_SYS_RESET_STATUS').read(1) - return status[0] - - def _sys_init_status(self) -> bool: - """设备初始化完成( BOOL)""" - status, read_err = self.client.use_node('COIL_SYS_INIT_STATUS').read(1) - return status[0] - - # 查找资源 - def modify_deck_name(self, resource_name: str): - # figure_res = self._ros_node.resource_tracker.figure_resource({"name": resource_name}) - # print(f"!!! figure_res: {type(figure_res)}") - self.station_resource.children[1] - return - - @property - def sys_status(self) -> str: - if self.debug_mode: - return "设备调试模式" - if self._sys_start_status(): - return "设备启动中" - elif self._sys_stop_status(): - return "设备停止中" - elif self._sys_reset_status(): - return "设备复位中" - elif self._sys_init_status(): - return "设备初始化中" - else: - return "未知状态" - - def _sys_hand_status(self) -> bool: - """设备手动模式( BOOL)""" - status, read_err = self.client.use_node('COIL_SYS_HAND_STATUS').read(1) - return status[0] - - def _sys_auto_status(self) -> bool: - """设备自动模式( BOOL)""" - status, read_err = self.client.use_node('COIL_SYS_AUTO_STATUS').read(1) - return status[0] - - @property - def sys_mode(self) -> str: - if self.debug_mode: - return "设备调试模式" - if self._sys_hand_status(): - return "设备手动模式" - elif self._sys_auto_status(): - return "设备自动模式" - else: - return "未知模式" - - @property - def request_rec_msg_status(self) -> bool: - """设备请求接受配方( BOOL)""" - if self.debug_mode: - return True - status, read_err = self.client.use_node('COIL_REQUEST_REC_MSG_STATUS').read(1) - return status[0] - - @property - def request_send_msg_status(self) -> bool: - """设备请求发送测试数据( BOOL)""" - if self.debug_mode: - return True - status, read_err = self.client.use_node('COIL_REQUEST_SEND_MSG_STATUS').read(1) - return status[0] - - # ======================= 其他属性(特殊功能) ======================== - ''' - @property - def warning_1(self) -> bool: - status, read_err = self.client.use_node('COIL_WARNING_1').read(1) - return status[0] - ''' - # ===================== 生产数据区 ====================== - - @property - def data_assembly_coin_cell_num(self) -> int: - """已完成电池数量 (INT16)""" - if self.debug_mode: - return 0 - num, read_err = self.client.use_node('REG_DATA_ASSEMBLY_COIN_CELL_NUM').read(1) - return num - - @property - def data_assembly_time(self) -> float: - """单颗电池组装时间 (秒, REAL/FLOAT32)""" - if self.debug_mode: - return 0 - time, read_err = self.client.use_node('REG_DATA_ASSEMBLY_PER_TIME').read(2, word_order=WorderOrder.LITTLE) - return time - - @property - def data_open_circuit_voltage(self) -> float: - """开路电压值 (FLOAT32)""" - if self.debug_mode: - return 0 - vol, read_err = self.client.use_node('REG_DATA_OPEN_CIRCUIT_VOLTAGE').read(2, word_order=WorderOrder.LITTLE) - return vol - - @property - def data_axis_x_pos(self) -> float: - """分液X轴当前位置 (FLOAT32)""" - if self.debug_mode: - return 0 - pos, read_err = self.client.use_node('REG_DATA_AXIS_X_POS').read(2, word_order=WorderOrder.LITTLE) - return pos - - @property - def data_axis_y_pos(self) -> float: - """分液Y轴当前位置 (FLOAT32)""" - if self.debug_mode: - return 0 - pos, read_err = self.client.use_node('REG_DATA_AXIS_Y_POS').read(2, word_order=WorderOrder.LITTLE) - return pos - - @property - def data_axis_z_pos(self) -> float: - """分液Z轴当前位置 (FLOAT32)""" - if self.debug_mode: - return 0 - pos, read_err = self.client.use_node('REG_DATA_AXIS_Z_POS').read(2, word_order=WorderOrder.LITTLE) - return pos - - @property - def data_pole_weight(self) -> float: - """当前电池正极片称重数据 (FLOAT32)""" - if self.debug_mode: - return 0 - weight, read_err = self.client.use_node('REG_DATA_POLE_WEIGHT').read(2, word_order=WorderOrder.LITTLE) - return weight - - @property - def data_assembly_pressure(self) -> int: - """当前电池压制力 (INT16)""" - if self.debug_mode: - return 0 - pressure, read_err = self.client.use_node('REG_DATA_ASSEMBLY_PRESSURE').read(1) - return pressure - - @property - def data_electrolyte_volume(self) -> int: - """当前电解液加注量 (INT16)""" - if self.debug_mode: - return 0 - vol, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_VOLUME').read(1) - return vol - - @property - def data_coin_num(self) -> int: - """当前电池数量 (INT16)""" - if self.debug_mode: - return 0 - num, read_err = self.client.use_node('REG_DATA_COIN_NUM').read(1) - return num - - @property - def data_coin_cell_code(self) -> str: - """电池二维码序列号 (STRING)""" - try: - # 尝试不同的字节序读取 - code_little, read_err = self.client.use_node('REG_DATA_COIN_CELL_CODE').read(10, word_order=WorderOrder.LITTLE) - print(code_little) - clean_code = code_little[-8:][::-1] - return clean_code - except Exception as e: - print(f"读取电池二维码失败: {e}") - return "N/A" - - - @property - def data_electrolyte_code(self) -> str: - try: - # 尝试不同的字节序读取 - code_little, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_CODE').read(10, word_order=WorderOrder.LITTLE) - print(code_little) - clean_code = code_little[-8:][::-1] - return clean_code - except Exception as e: - print(f"读取电解液二维码失败: {e}") - return "N/A" - - # ===================== 环境监控区 ====================== - @property - def data_glove_box_pressure(self) -> float: - """手套箱压力 (bar, FLOAT32)""" - if self.debug_mode: - return 0 - status, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_PRESSURE').read(2, word_order=WorderOrder.LITTLE) - return status - - @property - def data_glove_box_o2_content(self) -> float: - """手套箱氧含量 (ppm, FLOAT32)""" - if self.debug_mode: - return 0 - value, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_O2_CONTENT').read(2, word_order=WorderOrder.LITTLE) - return value - - @property - def data_glove_box_water_content(self) -> float: - """手套箱水含量 (ppm, FLOAT32)""" - if self.debug_mode: - return 0 - value, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_WATER_CONTENT').read(2, word_order=WorderOrder.LITTLE) - return value - -# @property -# def data_stack_vision_code(self) -> int: -# """物料堆叠复检图片编码 (INT16)""" -# if self.debug_mode: -# return 0 -# code, read_err = self.client.use_node('REG_DATA_STACK_VISON_CODE').read(1) -# #code, _ = self.client.use_node('REG_DATA_STACK_VISON_CODE').read(1).type -# print(f"读取物料堆叠复检图片编码", {code}, "error", type(code)) -# #print(code.type) -# # print(read_err) -# return int(code) - - def func_pack_device_init(self): - #切换手动模式 - print("切换手动模式") - self._sys_hand_cmd(True) - time.sleep(1) - while (self._sys_hand_status()) == False: - print("waiting for hand_cmd") - time.sleep(1) - #设备初始化 - self._sys_init_cmd(True) - time.sleep(1) - #sys_init_status为bool值,不加括号 - while (self._sys_init_status())== False: - print("waiting for init_cmd") - time.sleep(1) - #手动按钮置回False - self._sys_hand_cmd(False) - time.sleep(1) - while (self._sys_hand_cmd()) == True: - print("waiting for hand_cmd to False") - time.sleep(1) - #初始化命令置回False - self._sys_init_cmd(False) - time.sleep(1) - while (self._sys_init_cmd()) == True: - print("waiting for init_cmd to False") - time.sleep(1) - - def func_pack_device_auto(self): - #切换自动 - print("切换自动模式") - self._sys_auto_cmd(True) - time.sleep(1) - while (self._sys_auto_status()) == False: - print("waiting for auto_status") - time.sleep(1) - #自动按钮置False - self._sys_auto_cmd(False) - time.sleep(1) - while (self._sys_auto_cmd()) == True: - print("waiting for auto_cmd") - time.sleep(1) - - def func_pack_device_start(self): - #切换自动 - print("启动") - self._sys_start_cmd(True) - time.sleep(1) - while (self._sys_start_status()) == False: - print("waiting for start_status") - time.sleep(1) - #自动按钮置False - self._sys_start_cmd(False) - time.sleep(1) - while (self._sys_start_cmd()) == True: - print("waiting for start_cmd") - time.sleep(1) - - def func_pack_send_bottle_num(self, bottle_num: int): - #发送电解液平台数 - print("启动") - while (self._unilab_rece_electrolyte_bottle_num()) == False: - print("waiting for rece_electrolyte_bottle_num to True") - # self.client.use_node('8520').write(True) - time.sleep(1) - #发送电解液瓶数为2 - self._reg_msg_electrolyte_num(bottle_num) - time.sleep(1) - #完成信号置True - self._unilab_send_electrolyte_bottle_num(True) - time.sleep(1) - #检测到依华已接收 - while (self._unilab_rece_electrolyte_bottle_num()) == True: - print("waiting for rece_electrolyte_bottle_num to False") - time.sleep(1) - #完成信号置False - self._unilab_send_electrolyte_bottle_num(False) - time.sleep(1) - #自动按钮置False - - - # 下发参数 - #def func_pack_send_msg_cmd(self, elec_num: int, elec_use_num: int, elec_vol: float, assembly_type: int, assembly_pressure: int) -> bool: - # """UNILAB写参数""" - # while (self.request_rec_msg_status) == False: - # print("wait for res_msg") - # time.sleep(1) - # self.success = False - # self._unilab_send_msg_electrolyte_num(elec_num) - # time.sleep(1) - # self._unilab_send_msg_electrolyte_use_num(elec_use_num) - # time.sleep(1) - # self._unilab_send_msg_electrolyte_vol(elec_vol) - # time.sleep(1) - # self._unilab_send_msg_assembly_type(assembly_type) - # time.sleep(1) - # self._unilab_send_msg_assembly_pressure(assembly_pressure) - # time.sleep(1) - # self._unilab_send_msg_succ_cmd(True) - # time.sleep(1) - # self._unilab_send_msg_succ_cmd(False) - # #将允许读取标志位置True - # self.allow_data_read = True - # self.success = True - # return self.success - - def func_pack_send_msg_cmd(self, elec_use_num) -> bool: - """UNILAB写参数""" - while (self.request_rec_msg_status) == False: - print("wait for request_rec_msg_status to True") - time.sleep(1) - self.success = False - #self._unilab_send_msg_electrolyte_num(elec_num) - time.sleep(1) - self._unilab_send_msg_electrolyte_use_num(elec_use_num) - time.sleep(1) - self._unilab_send_msg_succ_cmd(True) - time.sleep(1) - while (self.request_rec_msg_status) == True: - print("wait for request_rec_msg_status to False") - time.sleep(1) - self._unilab_send_msg_succ_cmd(False) - #将允许读取标志位置True - self.allow_data_read = True - self.success = True - return self.success - - def func_pack_get_msg_cmd(self, file_path: str="D:\\coin_cell_data") -> bool: - """UNILAB读参数""" - while self.request_send_msg_status == False: - print("waiting for send_read_msg_status to True") - time.sleep(1) - data_open_circuit_voltage = self.data_open_circuit_voltage - data_pole_weight = self.data_pole_weight - data_assembly_time = self.data_assembly_time - data_assembly_pressure = self.data_assembly_pressure - data_electrolyte_volume = self.data_electrolyte_volume - data_coin_num = self.data_coin_num - data_electrolyte_code = self.data_electrolyte_code - data_coin_cell_code = self.data_coin_cell_code - print("data_open_circuit_voltage", data_open_circuit_voltage) - print("data_pole_weight", data_pole_weight) - print("data_assembly_time", data_assembly_time) - print("data_assembly_pressure", data_assembly_pressure) - print("data_electrolyte_volume", data_electrolyte_volume) - print("data_coin_num", data_coin_num) - print("data_electrolyte_code", data_electrolyte_code) - print("data_coin_cell_code", data_coin_cell_code) - #接收完信息后,读取完毕标志位置True - self._unilab_rec_msg_succ_cmd(True) - time.sleep(1) - #等待允许读取标志位置False - while self.request_send_msg_status == True: - print("waiting for send_msg_status to False") - time.sleep(1) - self._unilab_rec_msg_succ_cmd(False) - time.sleep(1) - #将允许读取标志位置True - time_date = datetime.now().strftime("%Y%m%d") - #秒级时间戳用于标记每一行电池数据 - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - #生成输出文件的变量 - self.csv_export_file = os.path.join(file_path, f"date_{time_date}.csv") - #将数据存入csv文件 - if not os.path.exists(self.csv_export_file): - #创建一个表头 - with open(self.csv_export_file, 'w', newline='', encoding='utf-8') as csvfile: - writer = csv.writer(csvfile) - writer.writerow([ - 'Time', 'open_circuit_voltage', 'pole_weight', - 'assembly_time', 'assembly_pressure', 'electrolyte_volume', - 'coin_num', 'electrolyte_code', 'coin_cell_code' - ]) - #立刻写入磁盘 - csvfile.flush() - #开始追加电池信息 - with open(self.csv_export_file, 'a', newline='', encoding='utf-8') as csvfile: - writer = csv.writer(csvfile) - writer.writerow([ - timestamp, data_open_circuit_voltage, data_pole_weight, - data_assembly_time, data_assembly_pressure, data_electrolyte_volume, - data_coin_num, data_electrolyte_code, data_coin_cell_code - ]) - #立刻写入磁盘 - csvfile.flush() - self.success = True - return self.success - - - - def func_pack_send_finished_cmd(self) -> bool: - """UNILAB写参数""" - while (self._unilab_rece_finished_cmd()) == False: - print("wait for rece_finished_cmd to True") - time.sleep(1) - self.success = False - self._unilab_send_finished_cmd(True) - time.sleep(1) - while (self._unilab_rece_finished_cmd()) == True: - print("wait for rece_finished_cmd to False") - time.sleep(1) - self._unilab_send_finished_cmd(False) - #将允许读取标志位置True - self.success = True - return self.success - - - - def func_allpack_cmd(self, elec_num, elec_use_num, file_path: str="D:\\coin_cell_data") -> bool: - elec_num, elec_use_num = int(elec_num), int(elec_use_num) - summary_csv_file = os.path.join(file_path, "duandian.csv") - # 如果断点文件存在,先读取之前的进度 - if os.path.exists(summary_csv_file): - read_status_flag = True - with open(summary_csv_file, 'r', newline='', encoding='utf-8') as csvfile: - reader = csv.reader(csvfile) - header = next(reader) # 跳过标题行 - data_row = next(reader) # 读取数据行 - if len(data_row) >= 2: - elec_num_r = int(data_row[0]) - elec_use_num_r = int(data_row[1]) - elec_num_N = int(data_row[2]) - elec_use_num_N = int(data_row[3]) - coin_num_N = int(data_row[4]) - if elec_num_r == elec_num and elec_use_num_r == elec_use_num: - print("断点文件与当前任务匹配,继续") - else: - print("断点文件中elec_num、elec_use_num与当前任务不匹配,请检查任务下发参数或修改断点文件") - return False - print(f"从断点文件读取进度: elec_num_N={elec_num_N}, elec_use_num_N={elec_use_num_N}, coin_num_N={coin_num_N}") - - else: - read_status_flag = False - print("未找到断点文件,从头开始") - elec_num_N = 0 - elec_use_num_N = 0 - coin_num_N = 0 - for i in range(20): - print(f"剩余电解液瓶数: {elec_num}, 已组装电池数: {elec_use_num}") - print(f"剩余电解液瓶数: {type(elec_num)}, 已组装电池数: {type(elec_use_num)}") - print(f"剩余电解液瓶数: {type(int(elec_num))}, 已组装电池数: {type(int(elec_use_num))}") - - #如果是第一次运行,则进行初始化、切换自动、启动, 如果是断点重启则跳过。 - if read_status_flag == False: - #初始化 - self.func_pack_device_init() - #切换自动 - self.func_pack_device_auto() - #启动,小车收回 - self.func_pack_device_start() - #发送电解液瓶数量,启动搬运,多搬运没事 - self.func_pack_send_bottle_num(elec_num) - last_i = elec_num_N - last_j = elec_use_num_N - for i in range(last_i, elec_num): - print(f"开始第{last_i+i+1}瓶电解液的组装") - #第一个循环从上次断点继续,后续循环从0开始 - j_start = last_j if i == last_i else 0 - self.func_pack_send_msg_cmd(elec_use_num-j_start) - - for j in range(j_start, elec_use_num): - print(f"开始第{last_i+i+1}瓶电解液的第{j+j_start+1}个电池组装") - #读取电池组装数据并存入csv - self.func_pack_get_msg_cmd(file_path) - time.sleep(1) - # TODO:读完再将电池数加一还是进入循环就将电池数加一需要考虑 - liaopan1 = self.station_resource.get_resource("liaopan1") - liaopan2 = self.station_resource.get_resource("liaopan2") - jipian1 = liaopan1.children[coin_num_N].children[0] - - #print(jipian1) - #把物料解绑后放到另一盘上 - jipian1.parent.unassign_child_resource(jipian1) - liaopan2.children[coin_num_N].assign_child_resource(jipian1, location=None) - #print(jipian2.parent) - ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ - "resources": [self.station_resource] - }) - - # 生成断点文件 - # 生成包含elec_num_N、coin_num_N、timestamp的CSV文件 - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - with open(summary_csv_file, 'w', newline='', encoding='utf-8') as csvfile: - writer = csv.writer(csvfile) - writer.writerow(['elec_num','elec_use_num', 'elec_num_N', 'elec_use_num_N', 'coin_num_N', 'timestamp']) - writer.writerow([elec_num, elec_use_num, elec_num_N, elec_use_num_N, coin_num_N, timestamp]) - csvfile.flush() - coin_num_N += 1 - elec_use_num_N += 1 - elec_num_N += 1 - elec_use_num_N = 0 - - #循环正常结束,则删除断点文件 - os.remove(summary_csv_file) - #全部完成后等待依华发送完成信号 - self.func_pack_send_finished_cmd() - - - def func_pack_device_stop(self) -> bool: - """打包指令:设备停止""" - for i in range(3): - time.sleep(2) - print(f"输出{i}") - #print("_sys_hand_cmd", self._sys_hand_cmd()) - #time.sleep(1) - #print("_sys_hand_status", self._sys_hand_status()) - #time.sleep(1) - #print("_sys_init_cmd", self._sys_init_cmd()) - #time.sleep(1) - #print("_sys_init_status", self._sys_init_status()) - #time.sleep(1) - #print("_sys_auto_status", self._sys_auto_status()) - #time.sleep(1) - #print("data_axis_y_pos", self.data_axis_y_pos) - #time.sleep(1) - #self.success = False - #with open('action_device_stop.json', 'r', encoding='utf-8') as f: - # action_json = json.load(f) - #self.client.execute_procedure_from_json(action_json) - #self.success = True - #return self.success - - def fun_wuliao_test(self) -> bool: - #找到data_init中构建的2个物料盘 - liaopan1 = self.station_resource.get_resource("liaopan1") - liaopan2 = self.station_resource.get_resource("liaopan2") - for i in range(16): - #找到liaopan1上每一个jipian - jipian_linshi = liaopan1.children[i].children[0] - #把物料解绑后放到另一盘上 - print("极片:", jipian_linshi) - jipian_linshi.parent.unassign_child_resource(jipian_linshi) - liaopan2.children[i].assign_child_resource(jipian_linshi, location=None) - ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ - "resources": [self.station_resource] - }) - time.sleep(10) - # 数据读取与输出 - def func_read_data_and_output(self, file_path: str="D:\\coin_cell_data"): - # 检查CSV导出是否正在运行,已运行则跳出,防止同时启动两个while循环 - if self.csv_export_running: - return False, "读取已在运行中" - - #若不存在该目录则创建 - if not os.path.exists(file_path): - os.makedirs(file_path) - print(f"创建目录: {file_path}") - - # 只要允许读取标志位为true,就持续运行该函数,直到触发停止条件 - while self.allow_data_read: - - #函数运行标志位,确保只同时启动一个导出函数 - self.csv_export_running = True - - #等待接收结果标志位置True - while self.request_send_msg_status == False: - print("waiting for send_msg_status to True") - time.sleep(1) - #日期时间戳用于按天存放csv文件 - time_date = datetime.now().strftime("%Y%m%d") - #秒级时间戳用于标记每一行电池数据 - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - #生成输出文件的变量 - self.csv_export_file = os.path.join(file_path, f"date_{time_date}.csv") - - #接收信息 - data_open_circuit_voltage = self.data_open_circuit_voltage - data_pole_weight = self.data_pole_weight - data_assembly_time = self.data_assembly_time - data_assembly_pressure = self.data_assembly_pressure - data_electrolyte_volume = self.data_electrolyte_volume - data_coin_num = self.data_coin_num - data_electrolyte_code = self.data_electrolyte_code - data_coin_cell_code = self.data_coin_cell_code - # 电解液瓶位置 - elec_bottle_site = 2 - # 极片夹取位置(应当通过寄存器读光标) - Pos_elec_site = 0 - Al_elec_site = 0 - Gasket_site = 0 - - #接收完信息后,读取完毕标志位置True - self._unilab_rec_msg_succ_cmd()# = True - #等待允许读取标志位置False - while self.request_send_msg_status == True: - print("waiting for send_msg_status to False") - time.sleep(1) - self._unilab_rec_msg_succ_cmd()# = False - - #此处操作物料信息(如果中途报错停止,如何) - #报错怎么办(加个判断标志位,如果发生错误,则根据停止位置扣除物料) - #根据物料光标判断取哪个物料(人工摆盘,电解液瓶,移液枪头都有光标位置,寄存器读即可) - - #物料读取操作写在这里 - #在这里进行物料调取 - #转移物料瓶,elec_bottle_site对应第几瓶电解液(从依华寄存器读取) - # transfer_bottle(deck, elec_bottle_site) - # #找到电解液瓶的对象 - # electrolyte_rack = deck.get_resource("electrolyte_rack") - # pending_positions = electrolyte_rack.get_pending_positions()[elec_bottle_site] - # # TODO: 瓶子取液体操作需要加入 -# -# - # #找到压制工站对应的对象 - # battery_press_slot = deck.get_resource("battery_press_1") - # #创建一个新电池 - # test_battery = Battery( - # name=f"test_battery_{data_coin_num}", - # diameter=20.0, # 与压制槽直径匹配 - # height=3.0, # 电池高度 - # max_volume=100.0, # 100μL容量 - # barcode=data_coin_cell_code, # 电池条码 - # ) - # if battery_press_slot.has_battery(): - # return False, "压制工站已有电池,无法放置新电池" - # #在压制位放置电池 - # battery_press_slot.place_battery(test_battery) - # #从第一个子弹夹中取料 - # clip_magazine_1_hole = self.deck.get_resource("clip_magazine_1").get_item(Pos_elec_site) - # clip_magazine_2_hole = self.deck.get_resource("clip_magazine_2").get_item(Al_elec_site) - # clip_magazine_3_hole = self.deck.get_resource("clip_magazine_3").get_item(Gasket_site) - # - # if clip_magazine_1_hole.get_sheet_count() > 0: # 检查洞位是否有极片 - # electrode_sheet_1 = clip_magazine_1_hole.take_sheet() # 从洞位取出极片 - # test_battery.add_electrode_sheet(electrode_sheet_1) # 添加到电池中 - # print(f"已将极片 {electrode_sheet_1.name} 从子弹夹转移到电池") - # else: - # print("子弹夹洞位0没有极片") -# - # if clip_magazine_2_hole.get_sheet_count() > 0: # 检查洞位是否有极片 - # electrode_sheet_2 = clip_magazine_2_hole.take_sheet() # 从洞位取出极片 - # test_battery.add_electrode_sheet(electrode_sheet_2) # 添加到电池中 - # print(f"已将极片 {electrode_sheet_2.name} 从子弹夹转移到电池") - # else: - # print("子弹夹洞位0没有极片") -# - # if clip_magazine_3_hole.get_sheet_count() > 0: # 检查洞位是否有极片 - # electrode_sheet_3 = clip_magazine_3_hole.take_sheet() # 从洞位取出极片 - # test_battery.add_electrode_sheet(electrode_sheet_3) # 添加到电池中 - # print(f"已将极片 {electrode_sheet_3.name} 从子弹夹转移到电池") - # else: - # print("子弹夹洞位0没有极片") - # - # # TODO:#把电解液从瓶中取到电池夹子中 - # battery_site = deck.get_resource("battery_press_1") - # clip_magazine_battery = deck.get_resource("clip_magazine_battery") - # if battery_site.has_battery(): - # battery = battery_site.take_battery() #从压制槽取出电池 - # clip_magazine_battery.add_battery(battery) #从压制槽取出电池 -# -# -# -# - # # 保存配置到文件 - # self.deck.save("button_battery_station_layout.json", indent=2) - # print("\n台面配置已保存到: button_battery_station_layout.json") - # - # # 保存状态到文件 - # self.deck.save_state_to_file("button_battery_station_state.json", indent=2) - # print("台面状态已保存到: button_battery_station_state.json") - - - - - - - #将数据写入csv中 - #如当前目录下无同名文件则新建一个csv用于存放数据 - if not os.path.exists(self.csv_export_file): - #创建一个表头 - with open(self.csv_export_file, 'w', newline='', encoding='utf-8') as csvfile: - writer = csv.writer(csvfile) - writer.writerow([ - 'Time', 'open_circuit_voltage', 'pole_weight', - 'assembly_time', 'assembly_pressure', 'electrolyte_volume', - 'coin_num', 'electrolyte_code', 'coin_cell_code' - ]) - #立刻写入磁盘 - csvfile.flush() - #开始追加电池信息 - with open(self.csv_export_file, 'a', newline='', encoding='utf-8') as csvfile: - writer = csv.writer(csvfile) - writer.writerow([ - timestamp, data_open_circuit_voltage, data_pole_weight, - data_assembly_time, data_assembly_pressure, data_electrolyte_volume, - data_coin_num, data_electrolyte_code, data_coin_cell_code - ]) - #立刻写入磁盘 - csvfile.flush() - - # 只要不在自动模式运行中,就将允许标志位置False - if self.sys_auto_status == False or self.sys_start_status == False: - self.allow_data_read = False - self.csv_export_running = False - time.sleep(1) - - def func_stop_read_data(self): - """停止CSV导出""" - if not self.csv_export_running: - return False, "read data未在运行" - - self.csv_export_running = False - self.allow_data_read = False - - if self.csv_export_thread and self.csv_export_thread.is_alive(): - self.csv_export_thread.join(timeout=5) - - def func_get_csv_export_status(self): - """获取CSV导出状态""" - return { - 'allow_read': self.allow_data_read, - 'running': self.csv_export_running, - 'thread_alive': self.csv_export_thread.is_alive() if self.csv_export_thread else False - } - - - ''' - # ===================== 物料管理区 ====================== - @property - def data_material_inventory(self) -> int: - """主物料库存 (数量, INT16)""" - inventory, read_err = self.client.use_node('REG_DATA_MATERIAL_INVENTORY').read(1) - return inventory - - @property - def data_tips_inventory(self) -> int: - """移液枪头库存 (数量, INT16)""" - inventory, read_err = self.client.register_node_list(self.nodes).use_node('REG_DATA_TIPS_INVENTORY').read(1) - return inventory - - ''' - - -if __name__ == "__main__": - from pylabrobot.resources import Resource - Coin_Cell = CoinCellAssemblyWorkstation(Resource("1", 1, 1, 1), debug_mode=True) - #Coin_Cell.func_pack_device_init() - #Coin_Cell.func_pack_device_auto() - #Coin_Cell.func_pack_device_start() - #Coin_Cell.func_pack_send_bottle_num(2) - #Coin_Cell.func_pack_send_msg_cmd(2) - #Coin_Cell.func_pack_get_msg_cmd() - #Coin_Cell.func_pack_get_msg_cmd() - #Coin_Cell.func_pack_send_finished_cmd() -# - #Coin_Cell.func_allpack_cmd(3, 2) - #print(Coin_Cell.data_stack_vision_code) - #print("success") - #创建一个物料台面 - - #deck = create_a_coin_cell_deck() - deck = create_a_full_coin_cell_deck() - - - ##在台面上找到料盘和极片 - #liaopan1 = deck.get_resource("liaopan1") - #liaopan2 = deck.get_resource("liaopan2") - #jipian1 = liaopan1.children[1].children[0] -## - #print(jipian1) - ##把物料解绑后放到另一盘上 - #jipian1.parent.unassign_child_resource(jipian1) - #liaopan2.children[1].assign_child_resource(jipian1, location=None) - ##print(jipian2.parent) - - liaopan1 = deck.get_resource("liaopan1") - liaopan2 = deck.get_resource("liaopan2") - for i in range(16): - #找到liaopan1上每一个jipian - jipian_linshi = liaopan1.children[i].children[0] - #把物料解绑后放到另一盘上 - print("极片:", jipian_linshi) - jipian_linshi.parent.unassign_child_resource(jipian_linshi) - liaopan2.children[i].assign_child_resource(jipian_linshi, location=None) - - - from unilabos.resources.graphio import resource_ulab_to_plr, convert_resources_to_type - #with open("./button_battery_station_resources_unilab.json", "r", encoding="utf-8") as f: - # bioyond_resources_unilab = json.load(f) - #print(f"成功读取 JSON 文件,包含 {len(bioyond_resources_unilab)} 个资源") - #ulab_resources = convert_resources_to_type(bioyond_resources_unilab, List[PLRResource]) - #print(f"转换结果类型: {type(ulab_resources)}") - #print(ulab_resources) - - - - from unilabos.resources.graphio import convert_resources_from_type - from unilabos.config.config import BasicConfig - BasicConfig.ak = "beb0c15f-2279-46a1-aba5-00eaf89aef55" - BasicConfig.sk = "15d4f25e-3512-4f9c-9bfb-43ab85e7b561" - from unilabos.app.web.client import http_client - - resources = convert_resources_from_type([deck], [Resource]) - json.dump({"nodes": resources, "links": []}, open("button_battery_station_resources_unilab.json", "w"), indent=2) - - #print(resources) - http_client.remote_addr = "https://uni-lab.test.bohrium.com/api/v1" - - http_client.resource_add(resources) \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_system_wuliao.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_system_wuliao.py deleted file mode 100644 index 6abdc38..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_system_wuliao.py +++ /dev/null @@ -1,135 +0,0 @@ -import json -import re -from pymodbus.client import ModbusTcpClient -from unilabos.device_comms.modbus_plc.modbus import WorderOrder, Coil, DiscreteInputs, HoldRegister, InputRegister, DataType -from pymodbus.constants import Endian -import time -import threading -import csv -import os -from datetime import datetime -from typing import Callable -from unilabos.device_comms.modbus_plc.client import TCPClient, ModbusNode, PLCWorkflow, ModbusWorkflow, WorkflowAction, BaseClient -from unilabos.device_comms.modbus_plc.modbus import DeviceType, Base as ModbusNodeBase, DataType, WorderOrder - -class Coin_Cell_Assembly: - - -if __name__ == '__main__': - coin_cell_assmbly = Coin_Cell_Assembly(address="192.168.1.20", port="502") - - #params = { - # "elec_num": 32 - #} - #str_data = json.dumps(params, ensure_ascii=False) - #print('param:', coin_cell_assmbly.func_pack_device_write_batch_elec_param(params)) - #time.sleep(1) - - print(coin_cell_assmbly.func_pack_device_write_per_elec_param( - elec_use_num=4, - elec_num=5, - elec_vol=55, - assembly_type=25, - assembly_pressure=550)) - time.sleep(1) - - -''' - print('start:', coin_cell_assmbly.func_pack_device_start()) - time.sleep(1) - - - - - - - - - print('start:', coin_cell_assmbly.func_pack_device_start()) - time.sleep(1) - - print('stop:', coin_cell_assmbly.func_pack_device_stop()) - time.sleep(1) - - while True: - # cmd coil - print('start cmd:', coin_cell_assmbly.sys_start_cmd(True)) - time.sleep(1) - print('stop cmd:', coin_cell_assmbly.sys_stop_cmd(False)) - time.sleep(1) - print('reset cmd:', coin_cell_assmbly.sys_reset_cmd(True)) - time.sleep(1) - print('hand cmd:', coin_cell_assmbly.sys_hand_cmd(False)) - time.sleep(1) - print('auto cmd:', coin_cell_assmbly.sys_auto_cmd(True)) - time.sleep(1) - print('init cmd:', coin_cell_assmbly.sys_init_cmd(False)) - time.sleep(1) - print('send msg succ cmd:', coin_cell_assmbly.unilab_send_msg_succ_cmd(False)) - time.sleep(1) - print('rec msg succ cmd:', coin_cell_assmbly.unilab_rec_msg_succ_cmd(True)) - time.sleep(1) - - # cmd reg - print('elec use num msg:', coin_cell_assmbly.unilab_send_msg_electrolyte_use_num(8)) - time.sleep(1) - print('elec num msg:', coin_cell_assmbly.unilab_send_msg_electrolyte_num(4)) - time.sleep(1) - print('elec vol msg:', coin_cell_assmbly.unilab_send_msg_electrolyte_vol(3.3)) - time.sleep(1) - print('assembly type msg:', coin_cell_assmbly.unilab_send_msg_assembly_type(1)) - time.sleep(1) - print('assembly pressure msg:', coin_cell_assmbly.unilab_send_msg_assembly_pressure(1)) - time.sleep(1) - - # status coil - print('start status:',coin_cell_assmbly.sys_start_status) - time.sleep(1) - print('stop status:',coin_cell_assmbly.sys_stop_status) - time.sleep(1) - print('reset status:',coin_cell_assmbly.sys_reset_status) - time.sleep(1) - print('hand status:',coin_cell_assmbly.sys_hand_status) - time.sleep(1) - print('auto status:', coin_cell_assmbly.sys_auto_status) - time.sleep(1) - print('init ok:', coin_cell_assmbly.sys_init_status) - time.sleep(1) - print('request rec msg:', coin_cell_assmbly.request_rec_msg_status) - time.sleep(1) - print('request send msg:', coin_cell_assmbly.request_send_msg_status) - time.sleep(1) - - # status reg - print('assembly coin cell num:', coin_cell_assmbly.data_assembly_coin_cell_num) - time.sleep(1) - print('assembly coin assembly per time:', coin_cell_assmbly.data_assembly_time) - time.sleep(1) - print('open circuit vol:', coin_cell_assmbly.data_open_circuit_voltage) - time.sleep(1) - print('axis x pos:', coin_cell_assmbly.data_axis_x_pos) - time.sleep(1) - print('axis y pos:', coin_cell_assmbly.data_axis_y_pos) - time.sleep(1) - print('axis z pos:', coin_cell_assmbly.data_axis_z_pos) - time.sleep(1) - print('pole weight:', coin_cell_assmbly.data_pole_weight) - time.sleep(1) - print('assembly pressure:', coin_cell_assmbly.data_assembly_coin_cell_num) - time.sleep(1) - print('assembly electrolyte vol:', coin_cell_assmbly.data_electrolyte_volume) - time.sleep(1) - print('assembly coin num:', coin_cell_assmbly.data_coin_num) - time.sleep(1) - print('coin cell code:', coin_cell_assmbly.data_coin_cell_code) - time.sleep(1) - print('elec code:', coin_cell_assmbly.data_electrolyte_code) - time.sleep(1) - print('glove box pressure:', coin_cell_assmbly.data_glove_box_pressure) - time.sleep(1) - print('glove box o2:', coin_cell_assmbly.data_glove_box_o2_content) - time.sleep(1) - print('glove box water:', coin_cell_assmbly.data_glove_box_water_content) - time.sleep(1) - -''' \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig.json b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig.json deleted file mode 100644 index 31fb003..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig.json +++ /dev/null @@ -1,1925 +0,0 @@ -{ - "nodes": [ - { - "id": "BatteryStation", - "name": "扣电工作站", - "children": [ - "deck" - ], - "parent": null, - "type": "device", - "class": "bettery_station_registry", - "position": { - "x": 600, - "y": 400, - "z": 0 - }, - "config": { - "debug_mode": false, - "_comment": "protocol_type接外部工站固定写法字段,一般为空,station_resource写法也固定", - "protocol_type": [], - "station_resource": { - "data": { - "_resource_child_name": "deck", - "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" - } - }, - - "address": "192.168.1.20", - "port": 502 - }, - "data": {} - }, - { - "id": "deck", - "name": "deck", - "sample_id": null, - "children": [ - "liaopan1", - "liaopan2" - ], - "parent": null, - "type": "deck", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "Deck", - "size_x": 1200, - "size_y": 800, - "size_z": 900, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "deck", - "barcode": null - }, - "data": {} - }, - { - "id": "liaopan1", - "name": "liaopan1", - "sample_id": null, - "children": [ - "liaopan1_materialhole_0_0", - "liaopan1_materialhole_0_1", - "liaopan1_materialhole_0_2", - "liaopan1_materialhole_0_3", - "liaopan1_materialhole_1_0", - "liaopan1_materialhole_1_1", - "liaopan1_materialhole_1_2", - "liaopan1_materialhole_1_3", - "liaopan1_materialhole_2_0", - "liaopan1_materialhole_2_1", - "liaopan1_materialhole_2_2", - "liaopan1_materialhole_2_3", - "liaopan1_materialhole_3_0", - "liaopan1_materialhole_3_1", - "liaopan1_materialhole_3_2", - "liaopan1_materialhole_3_3" - ], - "parent": "deck", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120.8, - "size_y": 120.5, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "liaopan1_materialhole_0_0", - "B1": "liaopan1_materialhole_0_1", - "C1": "liaopan1_materialhole_0_2", - "D1": "liaopan1_materialhole_0_3", - "A2": "liaopan1_materialhole_1_0", - "B2": "liaopan1_materialhole_1_1", - "C2": "liaopan1_materialhole_1_2", - "D2": "liaopan1_materialhole_1_3", - "A3": "liaopan1_materialhole_2_0", - "B3": "liaopan1_materialhole_2_1", - "C3": "liaopan1_materialhole_2_2", - "D3": "liaopan1_materialhole_2_3", - "A4": "liaopan1_materialhole_3_0", - "B4": "liaopan1_materialhole_3_1", - "C4": "liaopan1_materialhole_3_2", - "D4": "liaopan1_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "liaopan1_materialhole_0_0", - "name": "liaopan1_materialhole_0_0", - "sample_id": null, - "children": [ - "jipian_0" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_0", - "name": "jipian_0", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_1", - "name": "liaopan1_materialhole_0_1", - "sample_id": null, - "children": [ - "jipian_1" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_1", - "name": "jipian_1", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_2", - "name": "liaopan1_materialhole_0_2", - "sample_id": null, - "children": [], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_3", - "name": "liaopan1_materialhole_0_3", - "sample_id": null, - "children": [ - "jipian_3" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_3", - "name": "jipian_3", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_0", - "name": "liaopan1_materialhole_1_0", - "sample_id": null, - "children": [ - "jipian_4" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_4", - "name": "jipian_4", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_1", - "name": "liaopan1_materialhole_1_1", - "sample_id": null, - "children": [ - "jipian_5" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_5", - "name": "jipian_5", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_2", - "name": "liaopan1_materialhole_1_2", - "sample_id": null, - "children": [ - "jipian_6" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_6", - "name": "jipian_6", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_3", - "name": "liaopan1_materialhole_1_3", - "sample_id": null, - "children": [ - "jipian_7" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_7", - "name": "jipian_7", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_0", - "name": "liaopan1_materialhole_2_0", - "sample_id": null, - "children": [ - "jipian_8" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_8", - "name": "jipian_8", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_1", - "name": "liaopan1_materialhole_2_1", - "sample_id": null, - "children": [ - "jipian_9" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_9", - "name": "jipian_9", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_2", - "name": "liaopan1_materialhole_2_2", - "sample_id": null, - "children": [ - "jipian_10" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_10", - "name": "jipian_10", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_3", - "name": "liaopan1_materialhole_2_3", - "sample_id": null, - "children": [ - "jipian_11" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_11", - "name": "jipian_11", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_0", - "name": "liaopan1_materialhole_3_0", - "sample_id": null, - "children": [ - "jipian_12" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_12", - "name": "jipian_12", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_1", - "name": "liaopan1_materialhole_3_1", - "sample_id": null, - "children": [ - "jipian_13" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_13", - "name": "jipian_13", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_2", - "name": "liaopan1_materialhole_3_2", - "sample_id": null, - "children": [ - "jipian_14" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_14", - "name": "jipian_14", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_3", - "name": "liaopan1_materialhole_3_3", - "sample_id": null, - "children": [ - "jipian_15" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_15", - "name": "jipian_15", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan2", - "name": "liaopan2", - "sample_id": null, - "children": [ - "liaopan2_materialhole_0_0", - "liaopan2_materialhole_0_1", - "liaopan2_materialhole_0_2", - "liaopan2_materialhole_0_3", - "liaopan2_materialhole_1_0", - "liaopan2_materialhole_1_1", - "liaopan2_materialhole_1_2", - "liaopan2_materialhole_1_3", - "liaopan2_materialhole_2_0", - "liaopan2_materialhole_2_1", - "liaopan2_materialhole_2_2", - "liaopan2_materialhole_2_3", - "liaopan2_materialhole_3_0", - "liaopan2_materialhole_3_1", - "liaopan2_materialhole_3_2", - "liaopan2_materialhole_3_3" - ], - "parent": "deck", - "type": "container", - "class": "", - "position": { - "x": 500, - "y": 0, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120.8, - "size_y": 120.5, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "liaopan2_materialhole_0_0", - "B1": "liaopan2_materialhole_0_1", - "C1": "liaopan2_materialhole_0_2", - "D1": "liaopan2_materialhole_0_3", - "A2": "liaopan2_materialhole_1_0", - "B2": "liaopan2_materialhole_1_1", - "C2": "liaopan2_materialhole_1_2", - "D2": "liaopan2_materialhole_1_3", - "A3": "liaopan2_materialhole_2_0", - "B3": "liaopan2_materialhole_2_1", - "C3": "liaopan2_materialhole_2_2", - "D3": "liaopan2_materialhole_2_3", - "A4": "liaopan2_materialhole_3_0", - "B4": "liaopan2_materialhole_3_1", - "C4": "liaopan2_materialhole_3_2", - "D4": "liaopan2_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "liaopan2_materialhole_0_0", - "name": "liaopan2_materialhole_0_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_1", - "name": "liaopan2_materialhole_0_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_2", - "name": "liaopan2_materialhole_0_2", - "sample_id": null, - "children": [ - "jipian_2" - ], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_2", - "name": "jipian_2", - "sample_id": null, - "children": [], - "parent": "liaopan2_materialhole_0_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_3", - "name": "liaopan2_materialhole_0_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_0", - "name": "liaopan2_materialhole_1_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_1", - "name": "liaopan2_materialhole_1_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_2", - "name": "liaopan2_materialhole_1_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_3", - "name": "liaopan2_materialhole_1_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_0", - "name": "liaopan2_materialhole_2_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_1", - "name": "liaopan2_materialhole_2_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_2", - "name": "liaopan2_materialhole_2_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_3", - "name": "liaopan2_materialhole_2_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_0", - "name": "liaopan2_materialhole_3_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_1", - "name": "liaopan2_materialhole_3_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_2", - "name": "liaopan2_materialhole_3_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_3", - "name": "liaopan2_materialhole_3_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - } - ], - "links": [] -} \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig2.json b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig2.json deleted file mode 100644 index 79baec0..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig2.json +++ /dev/null @@ -1,1925 +0,0 @@ -{ - "nodes": [ - { - "id": "BatteryStation", - "name": "扣电工作站", - "children": [ - "deck" - ], - "parent": null, - "type": "device", - "class": "bettery_station_registry", - "position": { - "x": 600, - "y": 400, - "z": 0 - }, - "config": { - "debug_mode": false, - "_comment": "protocol_type接外部工站固定写法字段,一般为空,station_resource写法也固定", - "protocol_type": [], - "station_resource": { - "data": { - "_resource_child_name": "deck", - "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" - } - }, - - "address": "192.168.1.20", - "port": 502 - }, - "data": {} - }, - { - "id": "deck", - "name": "deck", - "sample_id": null, - "children": [ - "liaopan1", - "liaopan2" - ], - "parent": null, - "type": "deck", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "Deck", - "size_x": 1200, - "size_y": 800, - "size_z": 900, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "deck", - "barcode": null - }, - "data": {} - }, - { - "id": "liaopan1", - "name": "liaopan1", - "sample_id": null, - "children": [ - "liaopan1_materialhole_0_0", - "liaopan1_materialhole_0_1", - "liaopan1_materialhole_0_2", - "liaopan1_materialhole_0_3", - "liaopan1_materialhole_1_0", - "liaopan1_materialhole_1_1", - "liaopan1_materialhole_1_2", - "liaopan1_materialhole_1_3", - "liaopan1_materialhole_2_0", - "liaopan1_materialhole_2_1", - "liaopan1_materialhole_2_2", - "liaopan1_materialhole_2_3", - "liaopan1_materialhole_3_0", - "liaopan1_materialhole_3_1", - "liaopan1_materialhole_3_2", - "liaopan1_materialhole_3_3" - ], - "parent": "deck", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120.8, - "size_y": 120.5, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "liaopan1_materialhole_0_0", - "B1": "liaopan1_materialhole_0_1", - "C1": "liaopan1_materialhole_0_2", - "D1": "liaopan1_materialhole_0_3", - "A2": "liaopan1_materialhole_1_0", - "B2": "liaopan1_materialhole_1_1", - "C2": "liaopan1_materialhole_1_2", - "D2": "liaopan1_materialhole_1_3", - "A3": "liaopan1_materialhole_2_0", - "B3": "liaopan1_materialhole_2_1", - "C3": "liaopan1_materialhole_2_2", - "D3": "liaopan1_materialhole_2_3", - "A4": "liaopan1_materialhole_3_0", - "B4": "liaopan1_materialhole_3_1", - "C4": "liaopan1_materialhole_3_2", - "D4": "liaopan1_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "liaopan1_materialhole_0_0", - "name": "liaopan1_materialhole_0_0", - "sample_id": null, - "children": [ - "jipian_0" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_0", - "name": "jipian_0", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_1", - "name": "liaopan1_materialhole_0_1", - "sample_id": null, - "children": [ - "jipian_1" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_1", - "name": "jipian_1", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_2", - "name": "liaopan1_materialhole_0_2", - "sample_id": null, - "children": [ - "jipian_2" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_2", - "name": "jipian_2", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_3", - "name": "liaopan1_materialhole_0_3", - "sample_id": null, - "children": [ - "jipian_3" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_3", - "name": "jipian_3", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_0", - "name": "liaopan1_materialhole_1_0", - "sample_id": null, - "children": [ - "jipian_4" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_4", - "name": "jipian_4", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_1", - "name": "liaopan1_materialhole_1_1", - "sample_id": null, - "children": [ - "jipian_5" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_5", - "name": "jipian_5", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_2", - "name": "liaopan1_materialhole_1_2", - "sample_id": null, - "children": [ - "jipian_6" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_6", - "name": "jipian_6", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_3", - "name": "liaopan1_materialhole_1_3", - "sample_id": null, - "children": [ - "jipian_7" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_7", - "name": "jipian_7", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_0", - "name": "liaopan1_materialhole_2_0", - "sample_id": null, - "children": [ - "jipian_8" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_8", - "name": "jipian_8", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_1", - "name": "liaopan1_materialhole_2_1", - "sample_id": null, - "children": [ - "jipian_9" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_9", - "name": "jipian_9", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_2", - "name": "liaopan1_materialhole_2_2", - "sample_id": null, - "children": [ - "jipian_10" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_10", - "name": "jipian_10", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_3", - "name": "liaopan1_materialhole_2_3", - "sample_id": null, - "children": [ - "jipian_11" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_11", - "name": "jipian_11", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_0", - "name": "liaopan1_materialhole_3_0", - "sample_id": null, - "children": [ - "jipian_12" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_12", - "name": "jipian_12", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_1", - "name": "liaopan1_materialhole_3_1", - "sample_id": null, - "children": [ - "jipian_13" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_13", - "name": "jipian_13", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_2", - "name": "liaopan1_materialhole_3_2", - "sample_id": null, - "children": [ - "jipian_14" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_14", - "name": "jipian_14", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_3", - "name": "liaopan1_materialhole_3_3", - "sample_id": null, - "children": [ - "jipian_15" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_15", - "name": "jipian_15", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan2", - "name": "liaopan2", - "sample_id": null, - "children": [ - "liaopan2_materialhole_0_0", - "liaopan2_materialhole_0_1", - "liaopan2_materialhole_0_2", - "liaopan2_materialhole_0_3", - "liaopan2_materialhole_1_0", - "liaopan2_materialhole_1_1", - "liaopan2_materialhole_1_2", - "liaopan2_materialhole_1_3", - "liaopan2_materialhole_2_0", - "liaopan2_materialhole_2_1", - "liaopan2_materialhole_2_2", - "liaopan2_materialhole_2_3", - "liaopan2_materialhole_3_0", - "liaopan2_materialhole_3_1", - "liaopan2_materialhole_3_2", - "liaopan2_materialhole_3_3" - ], - "parent": "deck", - "type": "container", - "class": "", - "position": { - "x": 500, - "y": 0, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120.8, - "size_y": 120.5, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "liaopan2_materialhole_0_0", - "B1": "liaopan2_materialhole_0_1", - "C1": "liaopan2_materialhole_0_2", - "D1": "liaopan2_materialhole_0_3", - "A2": "liaopan2_materialhole_1_0", - "B2": "liaopan2_materialhole_1_1", - "C2": "liaopan2_materialhole_1_2", - "D2": "liaopan2_materialhole_1_3", - "A3": "liaopan2_materialhole_2_0", - "B3": "liaopan2_materialhole_2_1", - "C3": "liaopan2_materialhole_2_2", - "D3": "liaopan2_materialhole_2_3", - "A4": "liaopan2_materialhole_3_0", - "B4": "liaopan2_materialhole_3_1", - "C4": "liaopan2_materialhole_3_2", - "D4": "liaopan2_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "liaopan2_materialhole_0_0", - "name": "liaopan2_materialhole_0_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_1", - "name": "liaopan2_materialhole_0_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_2", - "name": "liaopan2_materialhole_0_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_3", - "name": "liaopan2_materialhole_0_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_0", - "name": "liaopan2_materialhole_1_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_1", - "name": "liaopan2_materialhole_1_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_2", - "name": "liaopan2_materialhole_1_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_3", - "name": "liaopan2_materialhole_1_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_0", - "name": "liaopan2_materialhole_2_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_1", - "name": "liaopan2_materialhole_2_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_2", - "name": "liaopan2_materialhole_2_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_3", - "name": "liaopan2_materialhole_2_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_0", - "name": "liaopan2_materialhole_3_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_1", - "name": "liaopan2_materialhole_3_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_2", - "name": "liaopan2_materialhole_3_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_3", - "name": "liaopan2_materialhole_3_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - } - ], - "links": [] -} \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3.json b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3.json deleted file mode 100644 index 39e72a7..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3.json +++ /dev/null @@ -1,1925 +0,0 @@ -{ - "nodes": [ - { - "id": "BatteryStation", - "name": "扣电工作站", - "children": [ - "coin_cell_deck" - ], - "parent": null, - "type": "device", - "class": "bettery_station_registry", - "position": { - "x": 600, - "y": 400, - "z": 0 - }, - "config": { - "debug_mode": false, - "_comment": "protocol_type接外部工站固定写法字段,一般为空,station_resource写法也固定", - "protocol_type": [], - "station_resource": { - "data": { - "_resource_child_name": "coin_cell_deck", - "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" - } - }, - - "address": "192.168.1.20", - "port": 502 - }, - "data": {} - }, - { - "id": "coin_cell_deck", - "name": "coin_cell_deck", - "sample_id": null, - "children": [ - "liaopan1", - "liaopan2" - ], - "parent": null, - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "CoincellDeck", - "size_x": 1620.0, - "size_y": 1270.0, - "size_z": 500.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "coin_cell_deck", - "barcode": null - }, - "data": {} - }, - { - "id": "liaopan1", - "name": "liaopan1", - "sample_id": null, - "children": [ - "liaopan1_materialhole_0_0", - "liaopan1_materialhole_0_1", - "liaopan1_materialhole_0_2", - "liaopan1_materialhole_0_3", - "liaopan1_materialhole_1_0", - "liaopan1_materialhole_1_1", - "liaopan1_materialhole_1_2", - "liaopan1_materialhole_1_3", - "liaopan1_materialhole_2_0", - "liaopan1_materialhole_2_1", - "liaopan1_materialhole_2_2", - "liaopan1_materialhole_2_3", - "liaopan1_materialhole_3_0", - "liaopan1_materialhole_3_1", - "liaopan1_materialhole_3_2", - "liaopan1_materialhole_3_3" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120.8, - "size_y": 120.5, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "liaopan1_materialhole_0_0", - "B1": "liaopan1_materialhole_0_1", - "C1": "liaopan1_materialhole_0_2", - "D1": "liaopan1_materialhole_0_3", - "A2": "liaopan1_materialhole_1_0", - "B2": "liaopan1_materialhole_1_1", - "C2": "liaopan1_materialhole_1_2", - "D2": "liaopan1_materialhole_1_3", - "A3": "liaopan1_materialhole_2_0", - "B3": "liaopan1_materialhole_2_1", - "C3": "liaopan1_materialhole_2_2", - "D3": "liaopan1_materialhole_2_3", - "A4": "liaopan1_materialhole_3_0", - "B4": "liaopan1_materialhole_3_1", - "C4": "liaopan1_materialhole_3_2", - "D4": "liaopan1_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "liaopan1_materialhole_0_0", - "name": "liaopan1_materialhole_0_0", - "sample_id": null, - "children": [ - "jipian_0" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_0", - "name": "jipian_0", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_1", - "name": "liaopan1_materialhole_0_1", - "sample_id": null, - "children": [ - "jipian_1" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_1", - "name": "jipian_1", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_2", - "name": "liaopan1_materialhole_0_2", - "sample_id": null, - "children": [ - "jipian_2" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_2", - "name": "jipian_2", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_3", - "name": "liaopan1_materialhole_0_3", - "sample_id": null, - "children": [ - "jipian_3" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_3", - "name": "jipian_3", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_0", - "name": "liaopan1_materialhole_1_0", - "sample_id": null, - "children": [ - "jipian_4" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_4", - "name": "jipian_4", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_1", - "name": "liaopan1_materialhole_1_1", - "sample_id": null, - "children": [ - "jipian_5" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_5", - "name": "jipian_5", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_2", - "name": "liaopan1_materialhole_1_2", - "sample_id": null, - "children": [ - "jipian_6" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_6", - "name": "jipian_6", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_3", - "name": "liaopan1_materialhole_1_3", - "sample_id": null, - "children": [ - "jipian_7" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_7", - "name": "jipian_7", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_0", - "name": "liaopan1_materialhole_2_0", - "sample_id": null, - "children": [ - "jipian_8" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_8", - "name": "jipian_8", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_1", - "name": "liaopan1_materialhole_2_1", - "sample_id": null, - "children": [ - "jipian_9" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_9", - "name": "jipian_9", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_2", - "name": "liaopan1_materialhole_2_2", - "sample_id": null, - "children": [ - "jipian_10" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_10", - "name": "jipian_10", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_3", - "name": "liaopan1_materialhole_2_3", - "sample_id": null, - "children": [ - "jipian_11" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_11", - "name": "jipian_11", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_0", - "name": "liaopan1_materialhole_3_0", - "sample_id": null, - "children": [ - "jipian_12" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_12", - "name": "jipian_12", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_1", - "name": "liaopan1_materialhole_3_1", - "sample_id": null, - "children": [ - "jipian_13" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_13", - "name": "jipian_13", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_2", - "name": "liaopan1_materialhole_3_2", - "sample_id": null, - "children": [ - "jipian_14" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_14", - "name": "jipian_14", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_3", - "name": "liaopan1_materialhole_3_3", - "sample_id": null, - "children": [ - "jipian_15" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_15", - "name": "jipian_15", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan2", - "name": "liaopan2", - "sample_id": null, - "children": [ - "liaopan2_materialhole_0_0", - "liaopan2_materialhole_0_1", - "liaopan2_materialhole_0_2", - "liaopan2_materialhole_0_3", - "liaopan2_materialhole_1_0", - "liaopan2_materialhole_1_1", - "liaopan2_materialhole_1_2", - "liaopan2_materialhole_1_3", - "liaopan2_materialhole_2_0", - "liaopan2_materialhole_2_1", - "liaopan2_materialhole_2_2", - "liaopan2_materialhole_2_3", - "liaopan2_materialhole_3_0", - "liaopan2_materialhole_3_1", - "liaopan2_materialhole_3_2", - "liaopan2_materialhole_3_3" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 500, - "y": 0, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120.8, - "size_y": 120.5, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "liaopan2_materialhole_0_0", - "B1": "liaopan2_materialhole_0_1", - "C1": "liaopan2_materialhole_0_2", - "D1": "liaopan2_materialhole_0_3", - "A2": "liaopan2_materialhole_1_0", - "B2": "liaopan2_materialhole_1_1", - "C2": "liaopan2_materialhole_1_2", - "D2": "liaopan2_materialhole_1_3", - "A3": "liaopan2_materialhole_2_0", - "B3": "liaopan2_materialhole_2_1", - "C3": "liaopan2_materialhole_2_2", - "D3": "liaopan2_materialhole_2_3", - "A4": "liaopan2_materialhole_3_0", - "B4": "liaopan2_materialhole_3_1", - "C4": "liaopan2_materialhole_3_2", - "D4": "liaopan2_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "liaopan2_materialhole_0_0", - "name": "liaopan2_materialhole_0_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_1", - "name": "liaopan2_materialhole_0_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_2", - "name": "liaopan2_materialhole_0_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_3", - "name": "liaopan2_materialhole_0_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_0", - "name": "liaopan2_materialhole_1_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_1", - "name": "liaopan2_materialhole_1_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_2", - "name": "liaopan2_materialhole_1_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_3", - "name": "liaopan2_materialhole_1_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_0", - "name": "liaopan2_materialhole_2_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_1", - "name": "liaopan2_materialhole_2_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_2", - "name": "liaopan2_materialhole_2_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_3", - "name": "liaopan2_materialhole_2_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_0", - "name": "liaopan2_materialhole_3_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_1", - "name": "liaopan2_materialhole_3_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_2", - "name": "liaopan2_materialhole_3_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_3", - "name": "liaopan2_materialhole_3_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - } - ], - "links": [] -} \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3a.json b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3a.json deleted file mode 100644 index 3a42dda..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3a.json +++ /dev/null @@ -1,1956 +0,0 @@ -{ - "nodes": [ - { - "id": "BatteryStation", - "name": "扣电工作站", - "children": [ - "coin_cell_deck" - ], - "parent": null, - "type": "device", - "class": "bettery_station_registry", - "position": { - "x": 600, - "y": 400, - "z": 0 - }, - "config": { - "debug_mode": false, - "_comment": "protocol_type接外部工站固定写法字段,一般为空,station_resource写法也固定", - "protocol_type": [], - "station_resource": { - "data": { - "_resource_child_name": "coin_cell_deck", - "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" - } - }, - - "address": "192.168.1.20", - "port": 502 - }, - "data": {} - }, - { - "id": "coin_cell_deck", - "name": "coin_cell_deck", - "sample_id": null, - "children": [ - "liaopan1", - "liaopan2", - "battery1" - ], - "parent": null, - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "CoincellDeck", - "size_x": 1200, - "size_y": 800, - "size_z": 900, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "coin_cell_deck", - "barcode": null - }, - "data": {} - }, - { - "id": "liaopan1", - "name": "liaopan1", - "sample_id": null, - "children": [ - "liaopan1_materialhole_0_0", - "liaopan1_materialhole_0_1", - "liaopan1_materialhole_0_2", - "liaopan1_materialhole_0_3", - "liaopan1_materialhole_1_0", - "liaopan1_materialhole_1_1", - "liaopan1_materialhole_1_2", - "liaopan1_materialhole_1_3", - "liaopan1_materialhole_2_0", - "liaopan1_materialhole_2_1", - "liaopan1_materialhole_2_2", - "liaopan1_materialhole_2_3", - "liaopan1_materialhole_3_0", - "liaopan1_materialhole_3_1", - "liaopan1_materialhole_3_2", - "liaopan1_materialhole_3_3" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120.8, - "size_y": 120.5, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "liaopan1_materialhole_0_0", - "B1": "liaopan1_materialhole_0_1", - "C1": "liaopan1_materialhole_0_2", - "D1": "liaopan1_materialhole_0_3", - "A2": "liaopan1_materialhole_1_0", - "B2": "liaopan1_materialhole_1_1", - "C2": "liaopan1_materialhole_1_2", - "D2": "liaopan1_materialhole_1_3", - "A3": "liaopan1_materialhole_2_0", - "B3": "liaopan1_materialhole_2_1", - "C3": "liaopan1_materialhole_2_2", - "D3": "liaopan1_materialhole_2_3", - "A4": "liaopan1_materialhole_3_0", - "B4": "liaopan1_materialhole_3_1", - "C4": "liaopan1_materialhole_3_2", - "D4": "liaopan1_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "liaopan1_materialhole_0_0", - "name": "liaopan1_materialhole_0_0", - "sample_id": null, - "children": [ - "jipian_0" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_0", - "name": "jipian_0", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_1", - "name": "liaopan1_materialhole_0_1", - "sample_id": null, - "children": [ - "jipian_1" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_1", - "name": "jipian_1", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_2", - "name": "liaopan1_materialhole_0_2", - "sample_id": null, - "children": [ - "jipian_2" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_2", - "name": "jipian_2", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_3", - "name": "liaopan1_materialhole_0_3", - "sample_id": null, - "children": [ - "jipian_3" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_3", - "name": "jipian_3", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_0", - "name": "liaopan1_materialhole_1_0", - "sample_id": null, - "children": [ - "jipian_4" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_4", - "name": "jipian_4", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_1", - "name": "liaopan1_materialhole_1_1", - "sample_id": null, - "children": [ - "jipian_5" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_5", - "name": "jipian_5", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_2", - "name": "liaopan1_materialhole_1_2", - "sample_id": null, - "children": [ - "jipian_6" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_6", - "name": "jipian_6", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_3", - "name": "liaopan1_materialhole_1_3", - "sample_id": null, - "children": [ - "jipian_7" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_7", - "name": "jipian_7", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_0", - "name": "liaopan1_materialhole_2_0", - "sample_id": null, - "children": [ - "jipian_8" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_8", - "name": "jipian_8", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_1", - "name": "liaopan1_materialhole_2_1", - "sample_id": null, - "children": [ - "jipian_9" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_9", - "name": "jipian_9", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_2", - "name": "liaopan1_materialhole_2_2", - "sample_id": null, - "children": [ - "jipian_10" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_10", - "name": "jipian_10", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_3", - "name": "liaopan1_materialhole_2_3", - "sample_id": null, - "children": [ - "jipian_11" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_11", - "name": "jipian_11", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_0", - "name": "liaopan1_materialhole_3_0", - "sample_id": null, - "children": [ - "jipian_12" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_12", - "name": "jipian_12", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_1", - "name": "liaopan1_materialhole_3_1", - "sample_id": null, - "children": [ - "jipian_13" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_13", - "name": "jipian_13", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_2", - "name": "liaopan1_materialhole_3_2", - "sample_id": null, - "children": [ - "jipian_14" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_14", - "name": "jipian_14", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_3", - "name": "liaopan1_materialhole_3_3", - "sample_id": null, - "children": [ - "jipian_15" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian_15", - "name": "jipian_15", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan2", - "name": "liaopan2", - "sample_id": null, - "children": [ - "liaopan2_materialhole_0_0", - "liaopan2_materialhole_0_1", - "liaopan2_materialhole_0_2", - "liaopan2_materialhole_0_3", - "liaopan2_materialhole_1_0", - "liaopan2_materialhole_1_1", - "liaopan2_materialhole_1_2", - "liaopan2_materialhole_1_3", - "liaopan2_materialhole_2_0", - "liaopan2_materialhole_2_1", - "liaopan2_materialhole_2_2", - "liaopan2_materialhole_2_3", - "liaopan2_materialhole_3_0", - "liaopan2_materialhole_3_1", - "liaopan2_materialhole_3_2", - "liaopan2_materialhole_3_3" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 500, - "y": 0, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120.8, - "size_y": 120.5, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "liaopan2_materialhole_0_0", - "B1": "liaopan2_materialhole_0_1", - "C1": "liaopan2_materialhole_0_2", - "D1": "liaopan2_materialhole_0_3", - "A2": "liaopan2_materialhole_1_0", - "B2": "liaopan2_materialhole_1_1", - "C2": "liaopan2_materialhole_1_2", - "D2": "liaopan2_materialhole_1_3", - "A3": "liaopan2_materialhole_2_0", - "B3": "liaopan2_materialhole_2_1", - "C3": "liaopan2_materialhole_2_2", - "D3": "liaopan2_materialhole_2_3", - "A4": "liaopan2_materialhole_3_0", - "B4": "liaopan2_materialhole_3_1", - "C4": "liaopan2_materialhole_3_2", - "D4": "liaopan2_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "liaopan2_materialhole_0_0", - "name": "liaopan2_materialhole_0_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_1", - "name": "liaopan2_materialhole_0_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_2", - "name": "liaopan2_materialhole_0_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_3", - "name": "liaopan2_materialhole_0_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_0", - "name": "liaopan2_materialhole_1_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_1", - "name": "liaopan2_materialhole_1_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_2", - "name": "liaopan2_materialhole_1_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_3", - "name": "liaopan2_materialhole_1_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_0", - "name": "liaopan2_materialhole_2_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_1", - "name": "liaopan2_materialhole_2_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_2", - "name": "liaopan2_materialhole_2_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_3", - "name": "liaopan2_materialhole_2_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_0", - "name": "liaopan2_materialhole_3_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_1", - "name": "liaopan2_materialhole_3_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_2", - "name": "liaopan2_materialhole_3_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_3", - "name": "liaopan2_materialhole_3_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "battery1", - "name": "battery1", - "sample_id": null, - "children": [], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 600, - "y": 600, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 30, - "size_y": 30, - "size_z": 30, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "battery", - "model": null, - "barcode": null - }, - "data": {} - } - ], - "links": [] -} \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3b.json b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3b.json deleted file mode 100644 index 78a6081..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3b.json +++ /dev/null @@ -1,2589 +0,0 @@ -{ - "nodes": [ - { - "id": "BatteryStation", - "name": "扣电工作站", - "children": [ - "coin_cell_deck" - ], - "parent": null, - "type": "device", - "class": "bettery_station_registry", - "position": { - "x": 600, - "y": 400, - "z": 0 - }, - "config": { - "debug_mode": false, - "_comment": "protocol_type接外部工站固定写法字段,一般为空,station_resource写法也固定", - "protocol_type": [], - "station_resource": { - "data": { - "_resource_child_name": "coin_cell_deck", - "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" - } - }, - - "address": "192.168.1.20", - "port": 502 - }, - "data": {} - }, - { - "id": "coin_cell_deck", - "name": "coin_cell_deck", - "sample_id": null, - "children": [ - "liaopan1", - "liaopan2", - "\u7535\u6c60\u6599\u76d8" - ], - "parent": null, - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "CoincellDeck", - "size_x": 1200, - "size_y": 800, - "size_z": 900, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "coin_cell_deck", - "barcode": null - }, - "data": {} - }, - { - "id": "liaopan1", - "name": "liaopan1", - "sample_id": null, - "children": [ - "liaopan1_materialhole_0_0", - "liaopan1_materialhole_0_1", - "liaopan1_materialhole_0_2", - "liaopan1_materialhole_0_3", - "liaopan1_materialhole_1_0", - "liaopan1_materialhole_1_1", - "liaopan1_materialhole_1_2", - "liaopan1_materialhole_1_3", - "liaopan1_materialhole_2_0", - "liaopan1_materialhole_2_1", - "liaopan1_materialhole_2_2", - "liaopan1_materialhole_2_3", - "liaopan1_materialhole_3_0", - "liaopan1_materialhole_3_1", - "liaopan1_materialhole_3_2", - "liaopan1_materialhole_3_3" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120.8, - "size_y": 120.5, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "liaopan1_materialhole_0_0", - "B1": "liaopan1_materialhole_0_1", - "C1": "liaopan1_materialhole_0_2", - "D1": "liaopan1_materialhole_0_3", - "A2": "liaopan1_materialhole_1_0", - "B2": "liaopan1_materialhole_1_1", - "C2": "liaopan1_materialhole_1_2", - "D2": "liaopan1_materialhole_1_3", - "A3": "liaopan1_materialhole_2_0", - "B3": "liaopan1_materialhole_2_1", - "C3": "liaopan1_materialhole_2_2", - "D3": "liaopan1_materialhole_2_3", - "A4": "liaopan1_materialhole_3_0", - "B4": "liaopan1_materialhole_3_1", - "C4": "liaopan1_materialhole_3_2", - "D4": "liaopan1_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "liaopan1_materialhole_0_0", - "name": "liaopan1_materialhole_0_0", - "sample_id": null, - "children": [ - "jipian1_0" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian1_0", - "name": "jipian1_0", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_1", - "name": "liaopan1_materialhole_0_1", - "sample_id": null, - "children": [ - "jipian1_1" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian1_1", - "name": "jipian1_1", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_2", - "name": "liaopan1_materialhole_0_2", - "sample_id": null, - "children": [ - "jipian1_2" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian1_2", - "name": "jipian1_2", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_3", - "name": "liaopan1_materialhole_0_3", - "sample_id": null, - "children": [ - "jipian1_3" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian1_3", - "name": "jipian1_3", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_0", - "name": "liaopan1_materialhole_1_0", - "sample_id": null, - "children": [ - "jipian1_4" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian1_4", - "name": "jipian1_4", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_1", - "name": "liaopan1_materialhole_1_1", - "sample_id": null, - "children": [ - "jipian1_5" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian1_5", - "name": "jipian1_5", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_2", - "name": "liaopan1_materialhole_1_2", - "sample_id": null, - "children": [ - "jipian1_6" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian1_6", - "name": "jipian1_6", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_3", - "name": "liaopan1_materialhole_1_3", - "sample_id": null, - "children": [ - "jipian1_7" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian1_7", - "name": "jipian1_7", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_0", - "name": "liaopan1_materialhole_2_0", - "sample_id": null, - "children": [ - "jipian1_8" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian1_8", - "name": "jipian1_8", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_1", - "name": "liaopan1_materialhole_2_1", - "sample_id": null, - "children": [ - "jipian1_9" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian1_9", - "name": "jipian1_9", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_2", - "name": "liaopan1_materialhole_2_2", - "sample_id": null, - "children": [ - "jipian1_10" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian1_10", - "name": "jipian1_10", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_3", - "name": "liaopan1_materialhole_2_3", - "sample_id": null, - "children": [ - "jipian1_11" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian1_11", - "name": "jipian1_11", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_0", - "name": "liaopan1_materialhole_3_0", - "sample_id": null, - "children": [ - "jipian1_12" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian1_12", - "name": "jipian1_12", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_1", - "name": "liaopan1_materialhole_3_1", - "sample_id": null, - "children": [ - "jipian1_13" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian1_13", - "name": "jipian1_13", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_2", - "name": "liaopan1_materialhole_3_2", - "sample_id": null, - "children": [ - "jipian1_14" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian1_14", - "name": "jipian1_14", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_3", - "name": "liaopan1_materialhole_3_3", - "sample_id": null, - "children": [ - "jipian1_15" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "jipian1_15", - "name": "jipian1_15", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan2", - "name": "liaopan2", - "sample_id": null, - "children": [ - "liaopan2_materialhole_0_0", - "liaopan2_materialhole_0_1", - "liaopan2_materialhole_0_2", - "liaopan2_materialhole_0_3", - "liaopan2_materialhole_1_0", - "liaopan2_materialhole_1_1", - "liaopan2_materialhole_1_2", - "liaopan2_materialhole_1_3", - "liaopan2_materialhole_2_0", - "liaopan2_materialhole_2_1", - "liaopan2_materialhole_2_2", - "liaopan2_materialhole_2_3", - "liaopan2_materialhole_3_0", - "liaopan2_materialhole_3_1", - "liaopan2_materialhole_3_2", - "liaopan2_materialhole_3_3" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 500, - "y": 0, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120.8, - "size_y": 120.5, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "liaopan2_materialhole_0_0", - "B1": "liaopan2_materialhole_0_1", - "C1": "liaopan2_materialhole_0_2", - "D1": "liaopan2_materialhole_0_3", - "A2": "liaopan2_materialhole_1_0", - "B2": "liaopan2_materialhole_1_1", - "C2": "liaopan2_materialhole_1_2", - "D2": "liaopan2_materialhole_1_3", - "A3": "liaopan2_materialhole_2_0", - "B3": "liaopan2_materialhole_2_1", - "C3": "liaopan2_materialhole_2_2", - "D3": "liaopan2_materialhole_2_3", - "A4": "liaopan2_materialhole_3_0", - "B4": "liaopan2_materialhole_3_1", - "C4": "liaopan2_materialhole_3_2", - "D4": "liaopan2_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "liaopan2_materialhole_0_0", - "name": "liaopan2_materialhole_0_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_1", - "name": "liaopan2_materialhole_0_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_2", - "name": "liaopan2_materialhole_0_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_3", - "name": "liaopan2_materialhole_0_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_0", - "name": "liaopan2_materialhole_1_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_1", - "name": "liaopan2_materialhole_1_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_2", - "name": "liaopan2_materialhole_1_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_3", - "name": "liaopan2_materialhole_1_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_0", - "name": "liaopan2_materialhole_2_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_1", - "name": "liaopan2_materialhole_2_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_2", - "name": "liaopan2_materialhole_2_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_3", - "name": "liaopan2_materialhole_2_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_0", - "name": "liaopan2_materialhole_3_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 84.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_1", - "name": "liaopan2_materialhole_3_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 60.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_2", - "name": "liaopan2_materialhole_3_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 36.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_3", - "name": "liaopan2_materialhole_3_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 12.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8", - "name": "\u7535\u6c60\u6599\u76d8", - "sample_id": null, - "children": [ - "\u7535\u6c60\u6599\u76d8_materialhole_0_0", - "\u7535\u6c60\u6599\u76d8_materialhole_0_1", - "\u7535\u6c60\u6599\u76d8_materialhole_0_2", - "\u7535\u6c60\u6599\u76d8_materialhole_0_3", - "\u7535\u6c60\u6599\u76d8_materialhole_1_0", - "\u7535\u6c60\u6599\u76d8_materialhole_1_1", - "\u7535\u6c60\u6599\u76d8_materialhole_1_2", - "\u7535\u6c60\u6599\u76d8_materialhole_1_3", - "\u7535\u6c60\u6599\u76d8_materialhole_2_0", - "\u7535\u6c60\u6599\u76d8_materialhole_2_1", - "\u7535\u6c60\u6599\u76d8_materialhole_2_2", - "\u7535\u6c60\u6599\u76d8_materialhole_2_3", - "\u7535\u6c60\u6599\u76d8_materialhole_3_0", - "\u7535\u6c60\u6599\u76d8_materialhole_3_1", - "\u7535\u6c60\u6599\u76d8_materialhole_3_2", - "\u7535\u6c60\u6599\u76d8_materialhole_3_3" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 300, - "y": 300, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120.8, - "size_y": 150.5, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", - "B1": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", - "C1": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", - "D1": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", - "A2": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", - "B2": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", - "C2": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", - "D2": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", - "A3": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", - "B3": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", - "C3": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", - "D3": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", - "A4": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", - "B4": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", - "C4": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", - "D4": "\u7535\u6c60\u6599\u76d8_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", - "sample_id": null, - "children": [ - "battery1" - ], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 99.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "battery1", - "name": "battery1", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 16, - "size_y": 16, - "size_z": 2, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20.0, - "height": 20.0, - "assembly_pressure": 20.0, - "electrolyte_volume": 20.0, - "electrolyte_name": "DP001" - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 75.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 51.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 27.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 99.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 75.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 51.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 27.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 99.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 75.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 51.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 27.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 99.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 75.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 51.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_3", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_3", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 27.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - } - ], - "links": [] -} \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json deleted file mode 100644 index 630faa5..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json +++ /dev/null @@ -1,691 +0,0 @@ -{ - "nodes": [ - { - "id": "BatteryStation", - "name": "扣电工作站", - "children": [ - "coin_cell_deck" - ], - "parent": null, - "type": "device", - "class": "bettery_station_registry", - "position": { - "x": 600, - "y": 400, - "z": 0 - }, - "config": { - "debug_mode": false, - "_comment": "protocol_type接外部工站固定写法字段,一般为空,station_resource写法也固定", - "protocol_type": [], - "station_resource": { - "data": { - "_resource_child_name": "coin_cell_deck", - "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" - } - }, - - "address": "192.168.1.20", - "port": 502 - }, - "data": {} - }, - { - "id": "coin_cell_deck", - "name": "coin_cell_deck", - "sample_id": null, - "children": [ - "\u7535\u6c60\u6599\u76d8" - ], - "parent": null, - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "CoincellDeck", - "size_x": 1000, - "size_y": 1000, - "size_z": 900, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "coin_cell_deck", - "barcode": null - }, - "data": {} - }, - { - "id": "\u7535\u6c60\u6599\u76d8", - "name": "\u7535\u6c60\u6599\u76d8", - "sample_id": null, - "children": [ - "\u7535\u6c60\u6599\u76d8_materialhole_0_0", - "\u7535\u6c60\u6599\u76d8_materialhole_0_1", - "\u7535\u6c60\u6599\u76d8_materialhole_0_2", - "\u7535\u6c60\u6599\u76d8_materialhole_0_3", - "\u7535\u6c60\u6599\u76d8_materialhole_1_0", - "\u7535\u6c60\u6599\u76d8_materialhole_1_1", - "\u7535\u6c60\u6599\u76d8_materialhole_1_2", - "\u7535\u6c60\u6599\u76d8_materialhole_1_3", - "\u7535\u6c60\u6599\u76d8_materialhole_2_0", - "\u7535\u6c60\u6599\u76d8_materialhole_2_1", - "\u7535\u6c60\u6599\u76d8_materialhole_2_2", - "\u7535\u6c60\u6599\u76d8_materialhole_2_3", - "\u7535\u6c60\u6599\u76d8_materialhole_3_0", - "\u7535\u6c60\u6599\u76d8_materialhole_3_1", - "\u7535\u6c60\u6599\u76d8_materialhole_3_2", - "\u7535\u6c60\u6599\u76d8_materialhole_3_3" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 100, - "y": 100, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120.8, - "size_y": 160.5, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", - "B1": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", - "C1": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", - "D1": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", - "A2": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", - "B2": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", - "C2": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", - "D2": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", - "A3": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", - "B3": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", - "C3": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", - "D3": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", - "A4": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", - "B4": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", - "C4": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", - "D4": "\u7535\u6c60\u6599\u76d8_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 104.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 80.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 56.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 32.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 104.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 80.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 56.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 32.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 104.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 80.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 56.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 32.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 104.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 80.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 56.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_3", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_3", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 32.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - } - ], - "links": [] -} \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig4.json b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig4.json deleted file mode 100644 index 0ba79b7..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig4.json +++ /dev/null @@ -1,14472 +0,0 @@ -{ - "nodes": [ - { - "id": "BatteryStation", - "name": "扣电工作站", - "children": [ - "coin_cell_deck" - ], - "parent": null, - "type": "device", - "class": "bettery_station_registry", - "position": { - "x": 600, - "y": 400, - "z": 0 - }, - "config": { - "debug_mode": false, - "_comment": "protocol_type接外部工站固定写法字段,一般为空,station_resource写法也固定", - "protocol_type": [], - "station_resource": { - "data": { - "_resource_child_name": "coin_cell_deck", - "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" - } - }, - - "address": "192.168.1.20", - "port": 502 - }, - "data": {} - }, - { - "id": "coin_cell_deck", - "name": "coin_cell_deck", - "sample_id": null, - "children": [ - "zi_dan_jia", - "zi_dan_jia2", - "zi_dan_jia3", - "zi_dan_jia4", - "zi_dan_jia5", - "zi_dan_jia6", - "zi_dan_jia7", - "zi_dan_jia8", - "liaopan1", - "liaopan2", - "liaopan3", - "liaopan4", - "liaopan5", - "liaopan6", - "bottle_rack_3x4", - "bottle_rack_6x2", - "bottle_rack_6x2_2", - "tip_box_64", - "waste_tip_box" - ], - "parent": null, - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "CoincellDeck", - "size_x": 1620.0, - "size_y": 1270.0, - "size_z": 500.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "coin_cell_deck", - "barcode": null - }, - "data": {} - }, - { - "id": "zi_dan_jia", - "name": "zi_dan_jia", - "sample_id": null, - "children": [ - "zi_dan_jia_clipmagazinehole_0_0", - "zi_dan_jia_clipmagazinehole_0_1", - "zi_dan_jia_clipmagazinehole_1_0", - "zi_dan_jia_clipmagazinehole_1_1" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 1400, - "y": 50, - "z": 0 - }, - "config": { - "type": "ClipMagazine_four", - "size_x": 80, - "size_y": 80, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_four", - "model": null, - "barcode": null, - "ordering": { - "A1": "zi_dan_jia_clipmagazinehole_0_0", - "B1": "zi_dan_jia_clipmagazinehole_0_1", - "A2": "zi_dan_jia_clipmagazinehole_1_0", - "B2": "zi_dan_jia_clipmagazinehole_1_1" - }, - "hole_diameter": 14.0, - "hole_depth": 10.0, - "max_sheets_per_hole": 100 - }, - "data": {} - }, - { - "id": "zi_dan_jia_clipmagazinehole_0_0", - "name": "zi_dan_jia_clipmagazinehole_0_0", - "sample_id": null, - "children": [ - "zi_dan_jia2_jipian_0" - ], - "parent": "zi_dan_jia", - "type": "container", - "class": "", - "position": { - "x": 15.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia2_jipian_0", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia_clipmagazinehole_0_0" - } - ] - } - }, - { - "id": "zi_dan_jia2_jipian_0", - "name": "zi_dan_jia2_jipian_0", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia_clipmagazinehole_0_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia_clipmagazinehole_0_1", - "name": "zi_dan_jia_clipmagazinehole_0_1", - "sample_id": null, - "children": [ - "zi_dan_jia2_jipian_1" - ], - "parent": "zi_dan_jia", - "type": "container", - "class": "", - "position": { - "x": 15.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia2_jipian_1", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia_clipmagazinehole_0_1" - } - ] - } - }, - { - "id": "zi_dan_jia2_jipian_1", - "name": "zi_dan_jia2_jipian_1", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia_clipmagazinehole_0_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia_clipmagazinehole_1_0", - "name": "zi_dan_jia_clipmagazinehole_1_0", - "sample_id": null, - "children": [ - "zi_dan_jia2_jipian_2" - ], - "parent": "zi_dan_jia", - "type": "container", - "class": "", - "position": { - "x": 40.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia2_jipian_2", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia_clipmagazinehole_1_0" - } - ] - } - }, - { - "id": "zi_dan_jia2_jipian_2", - "name": "zi_dan_jia2_jipian_2", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia_clipmagazinehole_1_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia_clipmagazinehole_1_1", - "name": "zi_dan_jia_clipmagazinehole_1_1", - "sample_id": null, - "children": [ - "zi_dan_jia2_jipian_3" - ], - "parent": "zi_dan_jia", - "type": "container", - "class": "", - "position": { - "x": 40.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia2_jipian_3", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia_clipmagazinehole_1_1" - } - ] - } - }, - { - "id": "zi_dan_jia2_jipian_3", - "name": "zi_dan_jia2_jipian_3", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia_clipmagazinehole_1_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia2", - "name": "zi_dan_jia2", - "sample_id": null, - "children": [ - "zi_dan_jia2_clipmagazinehole_0_0", - "zi_dan_jia2_clipmagazinehole_0_1", - "zi_dan_jia2_clipmagazinehole_1_0", - "zi_dan_jia2_clipmagazinehole_1_1" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 1600, - "y": 200, - "z": 0 - }, - "config": { - "type": "ClipMagazine_four", - "size_x": 80, - "size_y": 80, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_four", - "model": null, - "barcode": null, - "ordering": { - "A1": "zi_dan_jia2_clipmagazinehole_0_0", - "B1": "zi_dan_jia2_clipmagazinehole_0_1", - "A2": "zi_dan_jia2_clipmagazinehole_1_0", - "B2": "zi_dan_jia2_clipmagazinehole_1_1" - }, - "hole_diameter": 14.0, - "hole_depth": 10.0, - "max_sheets_per_hole": 100 - }, - "data": {} - }, - { - "id": "zi_dan_jia2_clipmagazinehole_0_0", - "name": "zi_dan_jia2_clipmagazinehole_0_0", - "sample_id": null, - "children": [ - "zi_dan_jia_jipian_0" - ], - "parent": "zi_dan_jia2", - "type": "container", - "class": "", - "position": { - "x": 15.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia_jipian_0", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia2_clipmagazinehole_0_0" - } - ] - } - }, - { - "id": "zi_dan_jia_jipian_0", - "name": "zi_dan_jia_jipian_0", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia2_clipmagazinehole_0_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia2_clipmagazinehole_0_1", - "name": "zi_dan_jia2_clipmagazinehole_0_1", - "sample_id": null, - "children": [ - "zi_dan_jia_jipian_1" - ], - "parent": "zi_dan_jia2", - "type": "container", - "class": "", - "position": { - "x": 15.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia_jipian_1", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia2_clipmagazinehole_0_1" - } - ] - } - }, - { - "id": "zi_dan_jia_jipian_1", - "name": "zi_dan_jia_jipian_1", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia2_clipmagazinehole_0_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia2_clipmagazinehole_1_0", - "name": "zi_dan_jia2_clipmagazinehole_1_0", - "sample_id": null, - "children": [ - "zi_dan_jia_jipian_2" - ], - "parent": "zi_dan_jia2", - "type": "container", - "class": "", - "position": { - "x": 40.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia_jipian_2", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia2_clipmagazinehole_1_0" - } - ] - } - }, - { - "id": "zi_dan_jia_jipian_2", - "name": "zi_dan_jia_jipian_2", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia2_clipmagazinehole_1_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia2_clipmagazinehole_1_1", - "name": "zi_dan_jia2_clipmagazinehole_1_1", - "sample_id": null, - "children": [ - "zi_dan_jia_jipian_3" - ], - "parent": "zi_dan_jia2", - "type": "container", - "class": "", - "position": { - "x": 40.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia_jipian_3", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia2_clipmagazinehole_1_1" - } - ] - } - }, - { - "id": "zi_dan_jia_jipian_3", - "name": "zi_dan_jia_jipian_3", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia2_clipmagazinehole_1_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia3", - "name": "zi_dan_jia3", - "sample_id": null, - "children": [ - "zi_dan_jia3_clipmagazinehole_0_0", - "zi_dan_jia3_clipmagazinehole_0_1", - "zi_dan_jia3_clipmagazinehole_1_0", - "zi_dan_jia3_clipmagazinehole_1_1", - "zi_dan_jia3_clipmagazinehole_2_0", - "zi_dan_jia3_clipmagazinehole_2_1" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 1500, - "y": 200, - "z": 0 - }, - "config": { - "type": "ClipMagazine", - "size_x": 80, - "size_y": 80, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine", - "model": null, - "barcode": null, - "ordering": { - "A1": "zi_dan_jia3_clipmagazinehole_0_0", - "B1": "zi_dan_jia3_clipmagazinehole_0_1", - "A2": "zi_dan_jia3_clipmagazinehole_1_0", - "B2": "zi_dan_jia3_clipmagazinehole_1_1", - "A3": "zi_dan_jia3_clipmagazinehole_2_0", - "B3": "zi_dan_jia3_clipmagazinehole_2_1" - }, - "hole_diameter": 14.0, - "hole_depth": 10.0, - "max_sheets_per_hole": 100 - }, - "data": {} - }, - { - "id": "zi_dan_jia3_clipmagazinehole_0_0", - "name": "zi_dan_jia3_clipmagazinehole_0_0", - "sample_id": null, - "children": [ - "zi_dan_jia3_jipian_0" - ], - "parent": "zi_dan_jia3", - "type": "container", - "class": "", - "position": { - "x": 15.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia3_jipian_0", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia3_clipmagazinehole_0_0" - } - ] - } - }, - { - "id": "zi_dan_jia3_jipian_0", - "name": "zi_dan_jia3_jipian_0", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia3_clipmagazinehole_0_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia3_clipmagazinehole_0_1", - "name": "zi_dan_jia3_clipmagazinehole_0_1", - "sample_id": null, - "children": [ - "zi_dan_jia3_jipian_1" - ], - "parent": "zi_dan_jia3", - "type": "container", - "class": "", - "position": { - "x": 15.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia3_jipian_1", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia3_clipmagazinehole_0_1" - } - ] - } - }, - { - "id": "zi_dan_jia3_jipian_1", - "name": "zi_dan_jia3_jipian_1", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia3_clipmagazinehole_0_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia3_clipmagazinehole_1_0", - "name": "zi_dan_jia3_clipmagazinehole_1_0", - "sample_id": null, - "children": [ - "zi_dan_jia3_jipian_2" - ], - "parent": "zi_dan_jia3", - "type": "container", - "class": "", - "position": { - "x": 40.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia3_jipian_2", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia3_clipmagazinehole_1_0" - } - ] - } - }, - { - "id": "zi_dan_jia3_jipian_2", - "name": "zi_dan_jia3_jipian_2", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia3_clipmagazinehole_1_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia3_clipmagazinehole_1_1", - "name": "zi_dan_jia3_clipmagazinehole_1_1", - "sample_id": null, - "children": [ - "zi_dan_jia3_jipian_3" - ], - "parent": "zi_dan_jia3", - "type": "container", - "class": "", - "position": { - "x": 40.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia3_jipian_3", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia3_clipmagazinehole_1_1" - } - ] - } - }, - { - "id": "zi_dan_jia3_jipian_3", - "name": "zi_dan_jia3_jipian_3", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia3_clipmagazinehole_1_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia3_clipmagazinehole_2_0", - "name": "zi_dan_jia3_clipmagazinehole_2_0", - "sample_id": null, - "children": [ - "zi_dan_jia3_jipian_4" - ], - "parent": "zi_dan_jia3", - "type": "container", - "class": "", - "position": { - "x": 65.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia3_jipian_4", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia3_clipmagazinehole_2_0" - } - ] - } - }, - { - "id": "zi_dan_jia3_jipian_4", - "name": "zi_dan_jia3_jipian_4", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia3_clipmagazinehole_2_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia3_clipmagazinehole_2_1", - "name": "zi_dan_jia3_clipmagazinehole_2_1", - "sample_id": null, - "children": [ - "zi_dan_jia3_jipian_5" - ], - "parent": "zi_dan_jia3", - "type": "container", - "class": "", - "position": { - "x": 65.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia3_jipian_5", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia3_clipmagazinehole_2_1" - } - ] - } - }, - { - "id": "zi_dan_jia3_jipian_5", - "name": "zi_dan_jia3_jipian_5", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia3_clipmagazinehole_2_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia4", - "name": "zi_dan_jia4", - "sample_id": null, - "children": [ - "zi_dan_jia4_clipmagazinehole_0_0", - "zi_dan_jia4_clipmagazinehole_0_1", - "zi_dan_jia4_clipmagazinehole_1_0", - "zi_dan_jia4_clipmagazinehole_1_1", - "zi_dan_jia4_clipmagazinehole_2_0", - "zi_dan_jia4_clipmagazinehole_2_1" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 1500, - "y": 300, - "z": 0 - }, - "config": { - "type": "ClipMagazine", - "size_x": 80, - "size_y": 80, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine", - "model": null, - "barcode": null, - "ordering": { - "A1": "zi_dan_jia4_clipmagazinehole_0_0", - "B1": "zi_dan_jia4_clipmagazinehole_0_1", - "A2": "zi_dan_jia4_clipmagazinehole_1_0", - "B2": "zi_dan_jia4_clipmagazinehole_1_1", - "A3": "zi_dan_jia4_clipmagazinehole_2_0", - "B3": "zi_dan_jia4_clipmagazinehole_2_1" - }, - "hole_diameter": 14.0, - "hole_depth": 10.0, - "max_sheets_per_hole": 100 - }, - "data": {} - }, - { - "id": "zi_dan_jia4_clipmagazinehole_0_0", - "name": "zi_dan_jia4_clipmagazinehole_0_0", - "sample_id": null, - "children": [ - "zi_dan_jia4_jipian_0" - ], - "parent": "zi_dan_jia4", - "type": "container", - "class": "", - "position": { - "x": 15.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia4_jipian_0", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia4_clipmagazinehole_0_0" - } - ] - } - }, - { - "id": "zi_dan_jia4_jipian_0", - "name": "zi_dan_jia4_jipian_0", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia4_clipmagazinehole_0_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia4_clipmagazinehole_0_1", - "name": "zi_dan_jia4_clipmagazinehole_0_1", - "sample_id": null, - "children": [ - "zi_dan_jia4_jipian_1" - ], - "parent": "zi_dan_jia4", - "type": "container", - "class": "", - "position": { - "x": 15.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia4_jipian_1", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia4_clipmagazinehole_0_1" - } - ] - } - }, - { - "id": "zi_dan_jia4_jipian_1", - "name": "zi_dan_jia4_jipian_1", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia4_clipmagazinehole_0_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia4_clipmagazinehole_1_0", - "name": "zi_dan_jia4_clipmagazinehole_1_0", - "sample_id": null, - "children": [ - "zi_dan_jia4_jipian_2" - ], - "parent": "zi_dan_jia4", - "type": "container", - "class": "", - "position": { - "x": 40.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia4_jipian_2", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia4_clipmagazinehole_1_0" - } - ] - } - }, - { - "id": "zi_dan_jia4_jipian_2", - "name": "zi_dan_jia4_jipian_2", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia4_clipmagazinehole_1_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia4_clipmagazinehole_1_1", - "name": "zi_dan_jia4_clipmagazinehole_1_1", - "sample_id": null, - "children": [ - "zi_dan_jia4_jipian_3" - ], - "parent": "zi_dan_jia4", - "type": "container", - "class": "", - "position": { - "x": 40.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia4_jipian_3", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia4_clipmagazinehole_1_1" - } - ] - } - }, - { - "id": "zi_dan_jia4_jipian_3", - "name": "zi_dan_jia4_jipian_3", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia4_clipmagazinehole_1_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia4_clipmagazinehole_2_0", - "name": "zi_dan_jia4_clipmagazinehole_2_0", - "sample_id": null, - "children": [ - "zi_dan_jia4_jipian_4" - ], - "parent": "zi_dan_jia4", - "type": "container", - "class": "", - "position": { - "x": 65.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia4_jipian_4", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia4_clipmagazinehole_2_0" - } - ] - } - }, - { - "id": "zi_dan_jia4_jipian_4", - "name": "zi_dan_jia4_jipian_4", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia4_clipmagazinehole_2_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia4_clipmagazinehole_2_1", - "name": "zi_dan_jia4_clipmagazinehole_2_1", - "sample_id": null, - "children": [ - "zi_dan_jia4_jipian_5" - ], - "parent": "zi_dan_jia4", - "type": "container", - "class": "", - "position": { - "x": 65.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia4_jipian_5", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia4_clipmagazinehole_2_1" - } - ] - } - }, - { - "id": "zi_dan_jia4_jipian_5", - "name": "zi_dan_jia4_jipian_5", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia4_clipmagazinehole_2_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia5", - "name": "zi_dan_jia5", - "sample_id": null, - "children": [ - "zi_dan_jia5_clipmagazinehole_0_0", - "zi_dan_jia5_clipmagazinehole_0_1", - "zi_dan_jia5_clipmagazinehole_1_0", - "zi_dan_jia5_clipmagazinehole_1_1", - "zi_dan_jia5_clipmagazinehole_2_0", - "zi_dan_jia5_clipmagazinehole_2_1" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 1600, - "y": 300, - "z": 0 - }, - "config": { - "type": "ClipMagazine", - "size_x": 80, - "size_y": 80, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine", - "model": null, - "barcode": null, - "ordering": { - "A1": "zi_dan_jia5_clipmagazinehole_0_0", - "B1": "zi_dan_jia5_clipmagazinehole_0_1", - "A2": "zi_dan_jia5_clipmagazinehole_1_0", - "B2": "zi_dan_jia5_clipmagazinehole_1_1", - "A3": "zi_dan_jia5_clipmagazinehole_2_0", - "B3": "zi_dan_jia5_clipmagazinehole_2_1" - }, - "hole_diameter": 14.0, - "hole_depth": 10.0, - "max_sheets_per_hole": 100 - }, - "data": {} - }, - { - "id": "zi_dan_jia5_clipmagazinehole_0_0", - "name": "zi_dan_jia5_clipmagazinehole_0_0", - "sample_id": null, - "children": [ - "zi_dan_jia5_jipian_0" - ], - "parent": "zi_dan_jia5", - "type": "container", - "class": "", - "position": { - "x": 15.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia5_jipian_0", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia5_clipmagazinehole_0_0" - } - ] - } - }, - { - "id": "zi_dan_jia5_jipian_0", - "name": "zi_dan_jia5_jipian_0", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia5_clipmagazinehole_0_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia5_clipmagazinehole_0_1", - "name": "zi_dan_jia5_clipmagazinehole_0_1", - "sample_id": null, - "children": [ - "zi_dan_jia5_jipian_1" - ], - "parent": "zi_dan_jia5", - "type": "container", - "class": "", - "position": { - "x": 15.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia5_jipian_1", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia5_clipmagazinehole_0_1" - } - ] - } - }, - { - "id": "zi_dan_jia5_jipian_1", - "name": "zi_dan_jia5_jipian_1", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia5_clipmagazinehole_0_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia5_clipmagazinehole_1_0", - "name": "zi_dan_jia5_clipmagazinehole_1_0", - "sample_id": null, - "children": [ - "zi_dan_jia5_jipian_2" - ], - "parent": "zi_dan_jia5", - "type": "container", - "class": "", - "position": { - "x": 40.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia5_jipian_2", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia5_clipmagazinehole_1_0" - } - ] - } - }, - { - "id": "zi_dan_jia5_jipian_2", - "name": "zi_dan_jia5_jipian_2", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia5_clipmagazinehole_1_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia5_clipmagazinehole_1_1", - "name": "zi_dan_jia5_clipmagazinehole_1_1", - "sample_id": null, - "children": [ - "zi_dan_jia5_jipian_3" - ], - "parent": "zi_dan_jia5", - "type": "container", - "class": "", - "position": { - "x": 40.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia5_jipian_3", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia5_clipmagazinehole_1_1" - } - ] - } - }, - { - "id": "zi_dan_jia5_jipian_3", - "name": "zi_dan_jia5_jipian_3", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia5_clipmagazinehole_1_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia5_clipmagazinehole_2_0", - "name": "zi_dan_jia5_clipmagazinehole_2_0", - "sample_id": null, - "children": [ - "zi_dan_jia5_jipian_4" - ], - "parent": "zi_dan_jia5", - "type": "container", - "class": "", - "position": { - "x": 65.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia5_jipian_4", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia5_clipmagazinehole_2_0" - } - ] - } - }, - { - "id": "zi_dan_jia5_jipian_4", - "name": "zi_dan_jia5_jipian_4", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia5_clipmagazinehole_2_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia5_clipmagazinehole_2_1", - "name": "zi_dan_jia5_clipmagazinehole_2_1", - "sample_id": null, - "children": [ - "zi_dan_jia5_jipian_5" - ], - "parent": "zi_dan_jia5", - "type": "container", - "class": "", - "position": { - "x": 65.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia5_jipian_5", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia5_clipmagazinehole_2_1" - } - ] - } - }, - { - "id": "zi_dan_jia5_jipian_5", - "name": "zi_dan_jia5_jipian_5", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia5_clipmagazinehole_2_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia6", - "name": "zi_dan_jia6", - "sample_id": null, - "children": [ - "zi_dan_jia6_clipmagazinehole_0_0", - "zi_dan_jia6_clipmagazinehole_0_1", - "zi_dan_jia6_clipmagazinehole_1_0", - "zi_dan_jia6_clipmagazinehole_1_1", - "zi_dan_jia6_clipmagazinehole_2_0", - "zi_dan_jia6_clipmagazinehole_2_1" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 1530, - "y": 500, - "z": 0 - }, - "config": { - "type": "ClipMagazine", - "size_x": 80, - "size_y": 80, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine", - "model": null, - "barcode": null, - "ordering": { - "A1": "zi_dan_jia6_clipmagazinehole_0_0", - "B1": "zi_dan_jia6_clipmagazinehole_0_1", - "A2": "zi_dan_jia6_clipmagazinehole_1_0", - "B2": "zi_dan_jia6_clipmagazinehole_1_1", - "A3": "zi_dan_jia6_clipmagazinehole_2_0", - "B3": "zi_dan_jia6_clipmagazinehole_2_1" - }, - "hole_diameter": 14.0, - "hole_depth": 10.0, - "max_sheets_per_hole": 100 - }, - "data": {} - }, - { - "id": "zi_dan_jia6_clipmagazinehole_0_0", - "name": "zi_dan_jia6_clipmagazinehole_0_0", - "sample_id": null, - "children": [ - "zi_dan_jia6_jipian_0" - ], - "parent": "zi_dan_jia6", - "type": "container", - "class": "", - "position": { - "x": 15.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia6_jipian_0", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia6_clipmagazinehole_0_0" - } - ] - } - }, - { - "id": "zi_dan_jia6_jipian_0", - "name": "zi_dan_jia6_jipian_0", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia6_clipmagazinehole_0_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia6_clipmagazinehole_0_1", - "name": "zi_dan_jia6_clipmagazinehole_0_1", - "sample_id": null, - "children": [ - "zi_dan_jia6_jipian_1" - ], - "parent": "zi_dan_jia6", - "type": "container", - "class": "", - "position": { - "x": 15.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia6_jipian_1", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia6_clipmagazinehole_0_1" - } - ] - } - }, - { - "id": "zi_dan_jia6_jipian_1", - "name": "zi_dan_jia6_jipian_1", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia6_clipmagazinehole_0_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia6_clipmagazinehole_1_0", - "name": "zi_dan_jia6_clipmagazinehole_1_0", - "sample_id": null, - "children": [ - "zi_dan_jia6_jipian_2" - ], - "parent": "zi_dan_jia6", - "type": "container", - "class": "", - "position": { - "x": 40.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia6_jipian_2", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia6_clipmagazinehole_1_0" - } - ] - } - }, - { - "id": "zi_dan_jia6_jipian_2", - "name": "zi_dan_jia6_jipian_2", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia6_clipmagazinehole_1_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia6_clipmagazinehole_1_1", - "name": "zi_dan_jia6_clipmagazinehole_1_1", - "sample_id": null, - "children": [ - "zi_dan_jia6_jipian_3" - ], - "parent": "zi_dan_jia6", - "type": "container", - "class": "", - "position": { - "x": 40.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia6_jipian_3", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia6_clipmagazinehole_1_1" - } - ] - } - }, - { - "id": "zi_dan_jia6_jipian_3", - "name": "zi_dan_jia6_jipian_3", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia6_clipmagazinehole_1_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia6_clipmagazinehole_2_0", - "name": "zi_dan_jia6_clipmagazinehole_2_0", - "sample_id": null, - "children": [ - "zi_dan_jia6_jipian_4" - ], - "parent": "zi_dan_jia6", - "type": "container", - "class": "", - "position": { - "x": 65.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia6_jipian_4", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia6_clipmagazinehole_2_0" - } - ] - } - }, - { - "id": "zi_dan_jia6_jipian_4", - "name": "zi_dan_jia6_jipian_4", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia6_clipmagazinehole_2_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia6_clipmagazinehole_2_1", - "name": "zi_dan_jia6_clipmagazinehole_2_1", - "sample_id": null, - "children": [ - "zi_dan_jia6_jipian_5" - ], - "parent": "zi_dan_jia6", - "type": "container", - "class": "", - "position": { - "x": 65.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia6_jipian_5", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia6_clipmagazinehole_2_1" - } - ] - } - }, - { - "id": "zi_dan_jia6_jipian_5", - "name": "zi_dan_jia6_jipian_5", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia6_clipmagazinehole_2_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia7", - "name": "zi_dan_jia7", - "sample_id": null, - "children": [ - "zi_dan_jia7_clipmagazinehole_0_0", - "zi_dan_jia7_clipmagazinehole_0_1", - "zi_dan_jia7_clipmagazinehole_1_0", - "zi_dan_jia7_clipmagazinehole_1_1", - "zi_dan_jia7_clipmagazinehole_2_0", - "zi_dan_jia7_clipmagazinehole_2_1" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 1180, - "y": 400, - "z": 0 - }, - "config": { - "type": "ClipMagazine", - "size_x": 80, - "size_y": 80, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine", - "model": null, - "barcode": null, - "ordering": { - "A1": "zi_dan_jia7_clipmagazinehole_0_0", - "B1": "zi_dan_jia7_clipmagazinehole_0_1", - "A2": "zi_dan_jia7_clipmagazinehole_1_0", - "B2": "zi_dan_jia7_clipmagazinehole_1_1", - "A3": "zi_dan_jia7_clipmagazinehole_2_0", - "B3": "zi_dan_jia7_clipmagazinehole_2_1" - }, - "hole_diameter": 14.0, - "hole_depth": 10.0, - "max_sheets_per_hole": 100 - }, - "data": {} - }, - { - "id": "zi_dan_jia7_clipmagazinehole_0_0", - "name": "zi_dan_jia7_clipmagazinehole_0_0", - "sample_id": null, - "children": [ - "zi_dan_jia7_jipian_0" - ], - "parent": "zi_dan_jia7", - "type": "container", - "class": "", - "position": { - "x": 15.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia7_jipian_0", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia7_clipmagazinehole_0_0" - } - ] - } - }, - { - "id": "zi_dan_jia7_jipian_0", - "name": "zi_dan_jia7_jipian_0", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia7_clipmagazinehole_0_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia7_clipmagazinehole_0_1", - "name": "zi_dan_jia7_clipmagazinehole_0_1", - "sample_id": null, - "children": [ - "zi_dan_jia7_jipian_1" - ], - "parent": "zi_dan_jia7", - "type": "container", - "class": "", - "position": { - "x": 15.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia7_jipian_1", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia7_clipmagazinehole_0_1" - } - ] - } - }, - { - "id": "zi_dan_jia7_jipian_1", - "name": "zi_dan_jia7_jipian_1", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia7_clipmagazinehole_0_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia7_clipmagazinehole_1_0", - "name": "zi_dan_jia7_clipmagazinehole_1_0", - "sample_id": null, - "children": [ - "zi_dan_jia7_jipian_2" - ], - "parent": "zi_dan_jia7", - "type": "container", - "class": "", - "position": { - "x": 40.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia7_jipian_2", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia7_clipmagazinehole_1_0" - } - ] - } - }, - { - "id": "zi_dan_jia7_jipian_2", - "name": "zi_dan_jia7_jipian_2", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia7_clipmagazinehole_1_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia7_clipmagazinehole_1_1", - "name": "zi_dan_jia7_clipmagazinehole_1_1", - "sample_id": null, - "children": [ - "zi_dan_jia7_jipian_3" - ], - "parent": "zi_dan_jia7", - "type": "container", - "class": "", - "position": { - "x": 40.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia7_jipian_3", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia7_clipmagazinehole_1_1" - } - ] - } - }, - { - "id": "zi_dan_jia7_jipian_3", - "name": "zi_dan_jia7_jipian_3", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia7_clipmagazinehole_1_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia7_clipmagazinehole_2_0", - "name": "zi_dan_jia7_clipmagazinehole_2_0", - "sample_id": null, - "children": [ - "zi_dan_jia7_jipian_4" - ], - "parent": "zi_dan_jia7", - "type": "container", - "class": "", - "position": { - "x": 65.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia7_jipian_4", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia7_clipmagazinehole_2_0" - } - ] - } - }, - { - "id": "zi_dan_jia7_jipian_4", - "name": "zi_dan_jia7_jipian_4", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia7_clipmagazinehole_2_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia7_clipmagazinehole_2_1", - "name": "zi_dan_jia7_clipmagazinehole_2_1", - "sample_id": null, - "children": [ - "zi_dan_jia7_jipian_5" - ], - "parent": "zi_dan_jia7", - "type": "container", - "class": "", - "position": { - "x": 65.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia7_jipian_5", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia7_clipmagazinehole_2_1" - } - ] - } - }, - { - "id": "zi_dan_jia7_jipian_5", - "name": "zi_dan_jia7_jipian_5", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia7_clipmagazinehole_2_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia8", - "name": "zi_dan_jia8", - "sample_id": null, - "children": [ - "zi_dan_jia8_clipmagazinehole_0_0", - "zi_dan_jia8_clipmagazinehole_0_1", - "zi_dan_jia8_clipmagazinehole_1_0", - "zi_dan_jia8_clipmagazinehole_1_1", - "zi_dan_jia8_clipmagazinehole_2_0", - "zi_dan_jia8_clipmagazinehole_2_1" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 1280, - "y": 400, - "z": 0 - }, - "config": { - "type": "ClipMagazine", - "size_x": 80, - "size_y": 80, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine", - "model": null, - "barcode": null, - "ordering": { - "A1": "zi_dan_jia8_clipmagazinehole_0_0", - "B1": "zi_dan_jia8_clipmagazinehole_0_1", - "A2": "zi_dan_jia8_clipmagazinehole_1_0", - "B2": "zi_dan_jia8_clipmagazinehole_1_1", - "A3": "zi_dan_jia8_clipmagazinehole_2_0", - "B3": "zi_dan_jia8_clipmagazinehole_2_1" - }, - "hole_diameter": 14.0, - "hole_depth": 10.0, - "max_sheets_per_hole": 100 - }, - "data": {} - }, - { - "id": "zi_dan_jia8_clipmagazinehole_0_0", - "name": "zi_dan_jia8_clipmagazinehole_0_0", - "sample_id": null, - "children": [ - "zi_dan_jia8_jipian_0" - ], - "parent": "zi_dan_jia8", - "type": "container", - "class": "", - "position": { - "x": 15.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia8_jipian_0", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia8_clipmagazinehole_0_0" - } - ] - } - }, - { - "id": "zi_dan_jia8_jipian_0", - "name": "zi_dan_jia8_jipian_0", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia8_clipmagazinehole_0_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia8_clipmagazinehole_0_1", - "name": "zi_dan_jia8_clipmagazinehole_0_1", - "sample_id": null, - "children": [ - "zi_dan_jia8_jipian_1" - ], - "parent": "zi_dan_jia8", - "type": "container", - "class": "", - "position": { - "x": 15.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia8_jipian_1", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia8_clipmagazinehole_0_1" - } - ] - } - }, - { - "id": "zi_dan_jia8_jipian_1", - "name": "zi_dan_jia8_jipian_1", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia8_clipmagazinehole_0_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia8_clipmagazinehole_1_0", - "name": "zi_dan_jia8_clipmagazinehole_1_0", - "sample_id": null, - "children": [ - "zi_dan_jia8_jipian_2" - ], - "parent": "zi_dan_jia8", - "type": "container", - "class": "", - "position": { - "x": 40.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia8_jipian_2", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia8_clipmagazinehole_1_0" - } - ] - } - }, - { - "id": "zi_dan_jia8_jipian_2", - "name": "zi_dan_jia8_jipian_2", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia8_clipmagazinehole_1_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia8_clipmagazinehole_1_1", - "name": "zi_dan_jia8_clipmagazinehole_1_1", - "sample_id": null, - "children": [ - "zi_dan_jia8_jipian_3" - ], - "parent": "zi_dan_jia8", - "type": "container", - "class": "", - "position": { - "x": 40.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia8_jipian_3", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia8_clipmagazinehole_1_1" - } - ] - } - }, - { - "id": "zi_dan_jia8_jipian_3", - "name": "zi_dan_jia8_jipian_3", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia8_clipmagazinehole_1_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia8_clipmagazinehole_2_0", - "name": "zi_dan_jia8_clipmagazinehole_2_0", - "sample_id": null, - "children": [ - "zi_dan_jia8_jipian_4" - ], - "parent": "zi_dan_jia8", - "type": "container", - "class": "", - "position": { - "x": 65.0, - "y": 52.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia8_jipian_4", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia8_clipmagazinehole_2_0" - } - ] - } - }, - { - "id": "zi_dan_jia8_jipian_4", - "name": "zi_dan_jia8_jipian_4", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia8_clipmagazinehole_2_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "zi_dan_jia8_clipmagazinehole_2_1", - "name": "zi_dan_jia8_clipmagazinehole_2_1", - "sample_id": null, - "children": [ - "zi_dan_jia8_jipian_5" - ], - "parent": "zi_dan_jia8", - "type": "container", - "class": "", - "position": { - "x": 65.0, - "y": 27.5, - "z": 10 - }, - "config": { - "type": "ClipMagazineHole", - "size_x": 14.0, - "size_y": 14.0, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "clip_magazine_hole", - "model": null, - "barcode": null, - "max_volume": 1960.0, - "material_z_thickness": null, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "sheet_count": 1, - "sheets": [ - { - "name": "zi_dan_jia8_jipian_5", - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "location": null, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null, - "children": [], - "parent_name": "zi_dan_jia8_clipmagazinehole_2_1" - } - ] - } - }, - { - "id": "zi_dan_jia8_jipian_5", - "name": "zi_dan_jia8_jipian_5", - "sample_id": null, - "children": [], - "parent": "zi_dan_jia8_clipmagazinehole_2_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1", - "name": "liaopan1", - "sample_id": null, - "children": [ - "liaopan1_materialhole_0_0", - "liaopan1_materialhole_0_1", - "liaopan1_materialhole_0_2", - "liaopan1_materialhole_0_3", - "liaopan1_materialhole_1_0", - "liaopan1_materialhole_1_1", - "liaopan1_materialhole_1_2", - "liaopan1_materialhole_1_3", - "liaopan1_materialhole_2_0", - "liaopan1_materialhole_2_1", - "liaopan1_materialhole_2_2", - "liaopan1_materialhole_2_3", - "liaopan1_materialhole_3_0", - "liaopan1_materialhole_3_1", - "liaopan1_materialhole_3_2", - "liaopan1_materialhole_3_3" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 1010, - "y": 50, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120, - "size_y": 100, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "liaopan1_materialhole_0_0", - "B1": "liaopan1_materialhole_0_1", - "C1": "liaopan1_materialhole_0_2", - "D1": "liaopan1_materialhole_0_3", - "A2": "liaopan1_materialhole_1_0", - "B2": "liaopan1_materialhole_1_1", - "C2": "liaopan1_materialhole_1_2", - "D2": "liaopan1_materialhole_1_3", - "A3": "liaopan1_materialhole_2_0", - "B3": "liaopan1_materialhole_2_1", - "C3": "liaopan1_materialhole_2_2", - "D3": "liaopan1_materialhole_2_3", - "A4": "liaopan1_materialhole_3_0", - "B4": "liaopan1_materialhole_3_1", - "C4": "liaopan1_materialhole_3_2", - "D4": "liaopan1_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "liaopan1_materialhole_0_0", - "name": "liaopan1_materialhole_0_0", - "sample_id": null, - "children": [ - "liaopan1_jipian_0" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan1_jipian_0", - "name": "liaopan1_jipian_0", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_1", - "name": "liaopan1_materialhole_0_1", - "sample_id": null, - "children": [ - "liaopan1_jipian_1" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan1_jipian_1", - "name": "liaopan1_jipian_1", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_2", - "name": "liaopan1_materialhole_0_2", - "sample_id": null, - "children": [ - "liaopan1_jipian_2" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan1_jipian_2", - "name": "liaopan1_jipian_2", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_0_3", - "name": "liaopan1_materialhole_0_3", - "sample_id": null, - "children": [ - "liaopan1_jipian_3" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan1_jipian_3", - "name": "liaopan1_jipian_3", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_0", - "name": "liaopan1_materialhole_1_0", - "sample_id": null, - "children": [ - "liaopan1_jipian_4" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan1_jipian_4", - "name": "liaopan1_jipian_4", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_1", - "name": "liaopan1_materialhole_1_1", - "sample_id": null, - "children": [ - "liaopan1_jipian_5" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan1_jipian_5", - "name": "liaopan1_jipian_5", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_2", - "name": "liaopan1_materialhole_1_2", - "sample_id": null, - "children": [ - "liaopan1_jipian_6" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan1_jipian_6", - "name": "liaopan1_jipian_6", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_1_3", - "name": "liaopan1_materialhole_1_3", - "sample_id": null, - "children": [ - "liaopan1_jipian_7" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan1_jipian_7", - "name": "liaopan1_jipian_7", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_0", - "name": "liaopan1_materialhole_2_0", - "sample_id": null, - "children": [ - "liaopan1_jipian_8" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan1_jipian_8", - "name": "liaopan1_jipian_8", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_1", - "name": "liaopan1_materialhole_2_1", - "sample_id": null, - "children": [ - "liaopan1_jipian_9" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan1_jipian_9", - "name": "liaopan1_jipian_9", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_2", - "name": "liaopan1_materialhole_2_2", - "sample_id": null, - "children": [ - "liaopan1_jipian_10" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan1_jipian_10", - "name": "liaopan1_jipian_10", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_2_3", - "name": "liaopan1_materialhole_2_3", - "sample_id": null, - "children": [ - "liaopan1_jipian_11" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan1_jipian_11", - "name": "liaopan1_jipian_11", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_0", - "name": "liaopan1_materialhole_3_0", - "sample_id": null, - "children": [ - "liaopan1_jipian_12" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan1_jipian_12", - "name": "liaopan1_jipian_12", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_1", - "name": "liaopan1_materialhole_3_1", - "sample_id": null, - "children": [ - "liaopan1_jipian_13" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan1_jipian_13", - "name": "liaopan1_jipian_13", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_2", - "name": "liaopan1_materialhole_3_2", - "sample_id": null, - "children": [ - "liaopan1_jipian_14" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan1_jipian_14", - "name": "liaopan1_jipian_14", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan1_materialhole_3_3", - "name": "liaopan1_materialhole_3_3", - "sample_id": null, - "children": [ - "liaopan1_jipian_15" - ], - "parent": "liaopan1", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan1_jipian_15", - "name": "liaopan1_jipian_15", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan2", - "name": "liaopan2", - "sample_id": null, - "children": [ - "liaopan2_materialhole_0_0", - "liaopan2_materialhole_0_1", - "liaopan2_materialhole_0_2", - "liaopan2_materialhole_0_3", - "liaopan2_materialhole_1_0", - "liaopan2_materialhole_1_1", - "liaopan2_materialhole_1_2", - "liaopan2_materialhole_1_3", - "liaopan2_materialhole_2_0", - "liaopan2_materialhole_2_1", - "liaopan2_materialhole_2_2", - "liaopan2_materialhole_2_3", - "liaopan2_materialhole_3_0", - "liaopan2_materialhole_3_1", - "liaopan2_materialhole_3_2", - "liaopan2_materialhole_3_3" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 1130, - "y": 50, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120, - "size_y": 100, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "liaopan2_materialhole_0_0", - "B1": "liaopan2_materialhole_0_1", - "C1": "liaopan2_materialhole_0_2", - "D1": "liaopan2_materialhole_0_3", - "A2": "liaopan2_materialhole_1_0", - "B2": "liaopan2_materialhole_1_1", - "C2": "liaopan2_materialhole_1_2", - "D2": "liaopan2_materialhole_1_3", - "A3": "liaopan2_materialhole_2_0", - "B3": "liaopan2_materialhole_2_1", - "C3": "liaopan2_materialhole_2_2", - "D3": "liaopan2_materialhole_2_3", - "A4": "liaopan2_materialhole_3_0", - "B4": "liaopan2_materialhole_3_1", - "C4": "liaopan2_materialhole_3_2", - "D4": "liaopan2_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "liaopan2_materialhole_0_0", - "name": "liaopan2_materialhole_0_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_1", - "name": "liaopan2_materialhole_0_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_2", - "name": "liaopan2_materialhole_0_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_0_3", - "name": "liaopan2_materialhole_0_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_0", - "name": "liaopan2_materialhole_1_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_1", - "name": "liaopan2_materialhole_1_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_2", - "name": "liaopan2_materialhole_1_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_1_3", - "name": "liaopan2_materialhole_1_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_0", - "name": "liaopan2_materialhole_2_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_1", - "name": "liaopan2_materialhole_2_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_2", - "name": "liaopan2_materialhole_2_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_2_3", - "name": "liaopan2_materialhole_2_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_0", - "name": "liaopan2_materialhole_3_0", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_1", - "name": "liaopan2_materialhole_3_1", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_2", - "name": "liaopan2_materialhole_3_2", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan2_materialhole_3_3", - "name": "liaopan2_materialhole_3_3", - "sample_id": null, - "children": [], - "parent": "liaopan2", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan3", - "name": "liaopan3", - "sample_id": null, - "children": [ - "liaopan3_materialhole_0_0", - "liaopan3_materialhole_0_1", - "liaopan3_materialhole_0_2", - "liaopan3_materialhole_0_3", - "liaopan3_materialhole_1_0", - "liaopan3_materialhole_1_1", - "liaopan3_materialhole_1_2", - "liaopan3_materialhole_1_3", - "liaopan3_materialhole_2_0", - "liaopan3_materialhole_2_1", - "liaopan3_materialhole_2_2", - "liaopan3_materialhole_2_3", - "liaopan3_materialhole_3_0", - "liaopan3_materialhole_3_1", - "liaopan3_materialhole_3_2", - "liaopan3_materialhole_3_3" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 1250, - "y": 50, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120, - "size_y": 100, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "liaopan3_materialhole_0_0", - "B1": "liaopan3_materialhole_0_1", - "C1": "liaopan3_materialhole_0_2", - "D1": "liaopan3_materialhole_0_3", - "A2": "liaopan3_materialhole_1_0", - "B2": "liaopan3_materialhole_1_1", - "C2": "liaopan3_materialhole_1_2", - "D2": "liaopan3_materialhole_1_3", - "A3": "liaopan3_materialhole_2_0", - "B3": "liaopan3_materialhole_2_1", - "C3": "liaopan3_materialhole_2_2", - "D3": "liaopan3_materialhole_2_3", - "A4": "liaopan3_materialhole_3_0", - "B4": "liaopan3_materialhole_3_1", - "C4": "liaopan3_materialhole_3_2", - "D4": "liaopan3_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "liaopan3_materialhole_0_0", - "name": "liaopan3_materialhole_0_0", - "sample_id": null, - "children": [], - "parent": "liaopan3", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan3_materialhole_0_1", - "name": "liaopan3_materialhole_0_1", - "sample_id": null, - "children": [], - "parent": "liaopan3", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan3_materialhole_0_2", - "name": "liaopan3_materialhole_0_2", - "sample_id": null, - "children": [], - "parent": "liaopan3", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan3_materialhole_0_3", - "name": "liaopan3_materialhole_0_3", - "sample_id": null, - "children": [], - "parent": "liaopan3", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan3_materialhole_1_0", - "name": "liaopan3_materialhole_1_0", - "sample_id": null, - "children": [], - "parent": "liaopan3", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan3_materialhole_1_1", - "name": "liaopan3_materialhole_1_1", - "sample_id": null, - "children": [], - "parent": "liaopan3", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan3_materialhole_1_2", - "name": "liaopan3_materialhole_1_2", - "sample_id": null, - "children": [], - "parent": "liaopan3", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan3_materialhole_1_3", - "name": "liaopan3_materialhole_1_3", - "sample_id": null, - "children": [], - "parent": "liaopan3", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan3_materialhole_2_0", - "name": "liaopan3_materialhole_2_0", - "sample_id": null, - "children": [], - "parent": "liaopan3", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan3_materialhole_2_1", - "name": "liaopan3_materialhole_2_1", - "sample_id": null, - "children": [], - "parent": "liaopan3", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan3_materialhole_2_2", - "name": "liaopan3_materialhole_2_2", - "sample_id": null, - "children": [], - "parent": "liaopan3", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan3_materialhole_2_3", - "name": "liaopan3_materialhole_2_3", - "sample_id": null, - "children": [], - "parent": "liaopan3", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan3_materialhole_3_0", - "name": "liaopan3_materialhole_3_0", - "sample_id": null, - "children": [], - "parent": "liaopan3", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan3_materialhole_3_1", - "name": "liaopan3_materialhole_3_1", - "sample_id": null, - "children": [], - "parent": "liaopan3", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan3_materialhole_3_2", - "name": "liaopan3_materialhole_3_2", - "sample_id": null, - "children": [], - "parent": "liaopan3", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan3_materialhole_3_3", - "name": "liaopan3_materialhole_3_3", - "sample_id": null, - "children": [], - "parent": "liaopan3", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan4", - "name": "liaopan4", - "sample_id": null, - "children": [ - "liaopan4_materialhole_0_0", - "liaopan4_materialhole_0_1", - "liaopan4_materialhole_0_2", - "liaopan4_materialhole_0_3", - "liaopan4_materialhole_1_0", - "liaopan4_materialhole_1_1", - "liaopan4_materialhole_1_2", - "liaopan4_materialhole_1_3", - "liaopan4_materialhole_2_0", - "liaopan4_materialhole_2_1", - "liaopan4_materialhole_2_2", - "liaopan4_materialhole_2_3", - "liaopan4_materialhole_3_0", - "liaopan4_materialhole_3_1", - "liaopan4_materialhole_3_2", - "liaopan4_materialhole_3_3" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 1010, - "y": 150, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120, - "size_y": 100, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "liaopan4_materialhole_0_0", - "B1": "liaopan4_materialhole_0_1", - "C1": "liaopan4_materialhole_0_2", - "D1": "liaopan4_materialhole_0_3", - "A2": "liaopan4_materialhole_1_0", - "B2": "liaopan4_materialhole_1_1", - "C2": "liaopan4_materialhole_1_2", - "D2": "liaopan4_materialhole_1_3", - "A3": "liaopan4_materialhole_2_0", - "B3": "liaopan4_materialhole_2_1", - "C3": "liaopan4_materialhole_2_2", - "D3": "liaopan4_materialhole_2_3", - "A4": "liaopan4_materialhole_3_0", - "B4": "liaopan4_materialhole_3_1", - "C4": "liaopan4_materialhole_3_2", - "D4": "liaopan4_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "liaopan4_materialhole_0_0", - "name": "liaopan4_materialhole_0_0", - "sample_id": null, - "children": [ - "liaopan4_jipian_0" - ], - "parent": "liaopan4", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan4_jipian_0", - "name": "liaopan4_jipian_0", - "sample_id": null, - "children": [], - "parent": "liaopan4_materialhole_0_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan4_materialhole_0_1", - "name": "liaopan4_materialhole_0_1", - "sample_id": null, - "children": [ - "liaopan4_jipian_1" - ], - "parent": "liaopan4", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan4_jipian_1", - "name": "liaopan4_jipian_1", - "sample_id": null, - "children": [], - "parent": "liaopan4_materialhole_0_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan4_materialhole_0_2", - "name": "liaopan4_materialhole_0_2", - "sample_id": null, - "children": [ - "liaopan4_jipian_2" - ], - "parent": "liaopan4", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan4_jipian_2", - "name": "liaopan4_jipian_2", - "sample_id": null, - "children": [], - "parent": "liaopan4_materialhole_0_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan4_materialhole_0_3", - "name": "liaopan4_materialhole_0_3", - "sample_id": null, - "children": [ - "liaopan4_jipian_3" - ], - "parent": "liaopan4", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan4_jipian_3", - "name": "liaopan4_jipian_3", - "sample_id": null, - "children": [], - "parent": "liaopan4_materialhole_0_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan4_materialhole_1_0", - "name": "liaopan4_materialhole_1_0", - "sample_id": null, - "children": [ - "liaopan4_jipian_4" - ], - "parent": "liaopan4", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan4_jipian_4", - "name": "liaopan4_jipian_4", - "sample_id": null, - "children": [], - "parent": "liaopan4_materialhole_1_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan4_materialhole_1_1", - "name": "liaopan4_materialhole_1_1", - "sample_id": null, - "children": [ - "liaopan4_jipian_5" - ], - "parent": "liaopan4", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan4_jipian_5", - "name": "liaopan4_jipian_5", - "sample_id": null, - "children": [], - "parent": "liaopan4_materialhole_1_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan4_materialhole_1_2", - "name": "liaopan4_materialhole_1_2", - "sample_id": null, - "children": [ - "liaopan4_jipian_6" - ], - "parent": "liaopan4", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan4_jipian_6", - "name": "liaopan4_jipian_6", - "sample_id": null, - "children": [], - "parent": "liaopan4_materialhole_1_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan4_materialhole_1_3", - "name": "liaopan4_materialhole_1_3", - "sample_id": null, - "children": [ - "liaopan4_jipian_7" - ], - "parent": "liaopan4", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan4_jipian_7", - "name": "liaopan4_jipian_7", - "sample_id": null, - "children": [], - "parent": "liaopan4_materialhole_1_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan4_materialhole_2_0", - "name": "liaopan4_materialhole_2_0", - "sample_id": null, - "children": [ - "liaopan4_jipian_8" - ], - "parent": "liaopan4", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan4_jipian_8", - "name": "liaopan4_jipian_8", - "sample_id": null, - "children": [], - "parent": "liaopan4_materialhole_2_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan4_materialhole_2_1", - "name": "liaopan4_materialhole_2_1", - "sample_id": null, - "children": [ - "liaopan4_jipian_9" - ], - "parent": "liaopan4", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan4_jipian_9", - "name": "liaopan4_jipian_9", - "sample_id": null, - "children": [], - "parent": "liaopan4_materialhole_2_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan4_materialhole_2_2", - "name": "liaopan4_materialhole_2_2", - "sample_id": null, - "children": [ - "liaopan4_jipian_10" - ], - "parent": "liaopan4", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan4_jipian_10", - "name": "liaopan4_jipian_10", - "sample_id": null, - "children": [], - "parent": "liaopan4_materialhole_2_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan4_materialhole_2_3", - "name": "liaopan4_materialhole_2_3", - "sample_id": null, - "children": [ - "liaopan4_jipian_11" - ], - "parent": "liaopan4", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan4_jipian_11", - "name": "liaopan4_jipian_11", - "sample_id": null, - "children": [], - "parent": "liaopan4_materialhole_2_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan4_materialhole_3_0", - "name": "liaopan4_materialhole_3_0", - "sample_id": null, - "children": [ - "liaopan4_jipian_12" - ], - "parent": "liaopan4", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan4_jipian_12", - "name": "liaopan4_jipian_12", - "sample_id": null, - "children": [], - "parent": "liaopan4_materialhole_3_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan4_materialhole_3_1", - "name": "liaopan4_materialhole_3_1", - "sample_id": null, - "children": [ - "liaopan4_jipian_13" - ], - "parent": "liaopan4", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan4_jipian_13", - "name": "liaopan4_jipian_13", - "sample_id": null, - "children": [], - "parent": "liaopan4_materialhole_3_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan4_materialhole_3_2", - "name": "liaopan4_materialhole_3_2", - "sample_id": null, - "children": [ - "liaopan4_jipian_14" - ], - "parent": "liaopan4", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan4_jipian_14", - "name": "liaopan4_jipian_14", - "sample_id": null, - "children": [], - "parent": "liaopan4_materialhole_3_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan4_materialhole_3_3", - "name": "liaopan4_materialhole_3_3", - "sample_id": null, - "children": [ - "liaopan4_jipian_15" - ], - "parent": "liaopan4", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan4_jipian_15", - "name": "liaopan4_jipian_15", - "sample_id": null, - "children": [], - "parent": "liaopan4_materialhole_3_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "liaopan5", - "name": "liaopan5", - "sample_id": null, - "children": [ - "liaopan5_materialhole_0_0", - "liaopan5_materialhole_0_1", - "liaopan5_materialhole_0_2", - "liaopan5_materialhole_0_3", - "liaopan5_materialhole_1_0", - "liaopan5_materialhole_1_1", - "liaopan5_materialhole_1_2", - "liaopan5_materialhole_1_3", - "liaopan5_materialhole_2_0", - "liaopan5_materialhole_2_1", - "liaopan5_materialhole_2_2", - "liaopan5_materialhole_2_3", - "liaopan5_materialhole_3_0", - "liaopan5_materialhole_3_1", - "liaopan5_materialhole_3_2", - "liaopan5_materialhole_3_3" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 1130, - "y": 150, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120, - "size_y": 100, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "liaopan5_materialhole_0_0", - "B1": "liaopan5_materialhole_0_1", - "C1": "liaopan5_materialhole_0_2", - "D1": "liaopan5_materialhole_0_3", - "A2": "liaopan5_materialhole_1_0", - "B2": "liaopan5_materialhole_1_1", - "C2": "liaopan5_materialhole_1_2", - "D2": "liaopan5_materialhole_1_3", - "A3": "liaopan5_materialhole_2_0", - "B3": "liaopan5_materialhole_2_1", - "C3": "liaopan5_materialhole_2_2", - "D3": "liaopan5_materialhole_2_3", - "A4": "liaopan5_materialhole_3_0", - "B4": "liaopan5_materialhole_3_1", - "C4": "liaopan5_materialhole_3_2", - "D4": "liaopan5_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "liaopan5_materialhole_0_0", - "name": "liaopan5_materialhole_0_0", - "sample_id": null, - "children": [], - "parent": "liaopan5", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan5_materialhole_0_1", - "name": "liaopan5_materialhole_0_1", - "sample_id": null, - "children": [], - "parent": "liaopan5", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan5_materialhole_0_2", - "name": "liaopan5_materialhole_0_2", - "sample_id": null, - "children": [], - "parent": "liaopan5", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan5_materialhole_0_3", - "name": "liaopan5_materialhole_0_3", - "sample_id": null, - "children": [], - "parent": "liaopan5", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan5_materialhole_1_0", - "name": "liaopan5_materialhole_1_0", - "sample_id": null, - "children": [], - "parent": "liaopan5", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan5_materialhole_1_1", - "name": "liaopan5_materialhole_1_1", - "sample_id": null, - "children": [], - "parent": "liaopan5", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan5_materialhole_1_2", - "name": "liaopan5_materialhole_1_2", - "sample_id": null, - "children": [], - "parent": "liaopan5", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan5_materialhole_1_3", - "name": "liaopan5_materialhole_1_3", - "sample_id": null, - "children": [], - "parent": "liaopan5", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan5_materialhole_2_0", - "name": "liaopan5_materialhole_2_0", - "sample_id": null, - "children": [], - "parent": "liaopan5", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan5_materialhole_2_1", - "name": "liaopan5_materialhole_2_1", - "sample_id": null, - "children": [], - "parent": "liaopan5", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan5_materialhole_2_2", - "name": "liaopan5_materialhole_2_2", - "sample_id": null, - "children": [], - "parent": "liaopan5", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan5_materialhole_2_3", - "name": "liaopan5_materialhole_2_3", - "sample_id": null, - "children": [], - "parent": "liaopan5", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan5_materialhole_3_0", - "name": "liaopan5_materialhole_3_0", - "sample_id": null, - "children": [], - "parent": "liaopan5", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan5_materialhole_3_1", - "name": "liaopan5_materialhole_3_1", - "sample_id": null, - "children": [], - "parent": "liaopan5", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan5_materialhole_3_2", - "name": "liaopan5_materialhole_3_2", - "sample_id": null, - "children": [], - "parent": "liaopan5", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan5_materialhole_3_3", - "name": "liaopan5_materialhole_3_3", - "sample_id": null, - "children": [], - "parent": "liaopan5", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan6", - "name": "liaopan6", - "sample_id": null, - "children": [ - "liaopan6_materialhole_0_0", - "liaopan6_materialhole_0_1", - "liaopan6_materialhole_0_2", - "liaopan6_materialhole_0_3", - "liaopan6_materialhole_1_0", - "liaopan6_materialhole_1_1", - "liaopan6_materialhole_1_2", - "liaopan6_materialhole_1_3", - "liaopan6_materialhole_2_0", - "liaopan6_materialhole_2_1", - "liaopan6_materialhole_2_2", - "liaopan6_materialhole_2_3", - "liaopan6_materialhole_3_0", - "liaopan6_materialhole_3_1", - "liaopan6_materialhole_3_2", - "liaopan6_materialhole_3_3" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 1250, - "y": 150, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120, - "size_y": 100, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "liaopan6_materialhole_0_0", - "B1": "liaopan6_materialhole_0_1", - "C1": "liaopan6_materialhole_0_2", - "D1": "liaopan6_materialhole_0_3", - "A2": "liaopan6_materialhole_1_0", - "B2": "liaopan6_materialhole_1_1", - "C2": "liaopan6_materialhole_1_2", - "D2": "liaopan6_materialhole_1_3", - "A3": "liaopan6_materialhole_2_0", - "B3": "liaopan6_materialhole_2_1", - "C3": "liaopan6_materialhole_2_2", - "D3": "liaopan6_materialhole_2_3", - "A4": "liaopan6_materialhole_3_0", - "B4": "liaopan6_materialhole_3_1", - "C4": "liaopan6_materialhole_3_2", - "D4": "liaopan6_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "liaopan6_materialhole_0_0", - "name": "liaopan6_materialhole_0_0", - "sample_id": null, - "children": [], - "parent": "liaopan6", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan6_materialhole_0_1", - "name": "liaopan6_materialhole_0_1", - "sample_id": null, - "children": [], - "parent": "liaopan6", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan6_materialhole_0_2", - "name": "liaopan6_materialhole_0_2", - "sample_id": null, - "children": [], - "parent": "liaopan6", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan6_materialhole_0_3", - "name": "liaopan6_materialhole_0_3", - "sample_id": null, - "children": [], - "parent": "liaopan6", - "type": "container", - "class": "", - "position": { - "x": 12.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan6_materialhole_1_0", - "name": "liaopan6_materialhole_1_0", - "sample_id": null, - "children": [], - "parent": "liaopan6", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan6_materialhole_1_1", - "name": "liaopan6_materialhole_1_1", - "sample_id": null, - "children": [], - "parent": "liaopan6", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan6_materialhole_1_2", - "name": "liaopan6_materialhole_1_2", - "sample_id": null, - "children": [], - "parent": "liaopan6", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan6_materialhole_1_3", - "name": "liaopan6_materialhole_1_3", - "sample_id": null, - "children": [], - "parent": "liaopan6", - "type": "container", - "class": "", - "position": { - "x": 36.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan6_materialhole_2_0", - "name": "liaopan6_materialhole_2_0", - "sample_id": null, - "children": [], - "parent": "liaopan6", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan6_materialhole_2_1", - "name": "liaopan6_materialhole_2_1", - "sample_id": null, - "children": [], - "parent": "liaopan6", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan6_materialhole_2_2", - "name": "liaopan6_materialhole_2_2", - "sample_id": null, - "children": [], - "parent": "liaopan6", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan6_materialhole_2_3", - "name": "liaopan6_materialhole_2_3", - "sample_id": null, - "children": [], - "parent": "liaopan6", - "type": "container", - "class": "", - "position": { - "x": 60.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan6_materialhole_3_0", - "name": "liaopan6_materialhole_3_0", - "sample_id": null, - "children": [], - "parent": "liaopan6", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 74.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan6_materialhole_3_1", - "name": "liaopan6_materialhole_3_1", - "sample_id": null, - "children": [], - "parent": "liaopan6", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 50.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan6_materialhole_3_2", - "name": "liaopan6_materialhole_3_2", - "sample_id": null, - "children": [], - "parent": "liaopan6", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 26.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "liaopan6_materialhole_3_3", - "name": "liaopan6_materialhole_3_3", - "sample_id": null, - "children": [], - "parent": "liaopan6", - "type": "container", - "class": "", - "position": { - "x": 84.0, - "y": 2.0, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "bottle_rack_3x4", - "name": "bottle_rack_3x4", - "sample_id": null, - "children": [ - "sheet_3x4_0", - "sheet_3x4_1", - "sheet_3x4_2", - "sheet_3x4_3", - "sheet_3x4_4", - "sheet_3x4_5", - "sheet_3x4_6", - "sheet_3x4_7", - "sheet_3x4_8", - "sheet_3x4_9", - "sheet_3x4_10", - "sheet_3x4_11" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 100, - "y": 200, - "z": 0 - }, - "config": { - "type": "BottleRack", - "size_x": 210.0, - "size_y": 140.0, - "size_z": 100.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "bottle_rack", - "model": null, - "barcode": null, - "num_items_x": 3, - "num_items_y": 4, - "position_spacing": 35.0, - "orientation": "vertical", - "padding_x": 20.0, - "padding_y": 20.0 - }, - "data": { - "bottle_diameter": 30.0, - "bottle_height": 100.0, - "position_spacing": 35.0, - "name_to_index": {} - } - }, - { - "id": "sheet_3x4_0", - "name": "sheet_3x4_0", - "sample_id": null, - "children": [], - "parent": "bottle_rack_3x4", - "type": "container", - "class": "", - "position": { - "x": 20.0, - "y": 20.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_3x4_1", - "name": "sheet_3x4_1", - "sample_id": null, - "children": [], - "parent": "bottle_rack_3x4", - "type": "container", - "class": "", - "position": { - "x": 20.0, - "y": 55.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_3x4_2", - "name": "sheet_3x4_2", - "sample_id": null, - "children": [], - "parent": "bottle_rack_3x4", - "type": "container", - "class": "", - "position": { - "x": 20.0, - "y": 90.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_3x4_3", - "name": "sheet_3x4_3", - "sample_id": null, - "children": [], - "parent": "bottle_rack_3x4", - "type": "container", - "class": "", - "position": { - "x": 55.0, - "y": 20.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_3x4_4", - "name": "sheet_3x4_4", - "sample_id": null, - "children": [], - "parent": "bottle_rack_3x4", - "type": "container", - "class": "", - "position": { - "x": 55.0, - "y": 55.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_3x4_5", - "name": "sheet_3x4_5", - "sample_id": null, - "children": [], - "parent": "bottle_rack_3x4", - "type": "container", - "class": "", - "position": { - "x": 55.0, - "y": 90.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_3x4_6", - "name": "sheet_3x4_6", - "sample_id": null, - "children": [], - "parent": "bottle_rack_3x4", - "type": "container", - "class": "", - "position": { - "x": 90.0, - "y": 20.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_3x4_7", - "name": "sheet_3x4_7", - "sample_id": null, - "children": [], - "parent": "bottle_rack_3x4", - "type": "container", - "class": "", - "position": { - "x": 90.0, - "y": 55.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_3x4_8", - "name": "sheet_3x4_8", - "sample_id": null, - "children": [], - "parent": "bottle_rack_3x4", - "type": "container", - "class": "", - "position": { - "x": 90.0, - "y": 90.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_3x4_9", - "name": "sheet_3x4_9", - "sample_id": null, - "children": [], - "parent": "bottle_rack_3x4", - "type": "container", - "class": "", - "position": { - "x": 125.0, - "y": 20.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_3x4_10", - "name": "sheet_3x4_10", - "sample_id": null, - "children": [], - "parent": "bottle_rack_3x4", - "type": "container", - "class": "", - "position": { - "x": 125.0, - "y": 55.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_3x4_11", - "name": "sheet_3x4_11", - "sample_id": null, - "children": [], - "parent": "bottle_rack_3x4", - "type": "container", - "class": "", - "position": { - "x": 125.0, - "y": 90.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "bottle_rack_6x2", - "name": "bottle_rack_6x2", - "sample_id": null, - "children": [ - "sheet_6x2_0", - "sheet_6x2_1", - "sheet_6x2_2", - "sheet_6x2_3", - "sheet_6x2_4", - "sheet_6x2_5", - "sheet_6x2_6", - "sheet_6x2_7", - "sheet_6x2_8", - "sheet_6x2_9", - "sheet_6x2_10", - "sheet_6x2_11" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 300, - "y": 300, - "z": 0 - }, - "config": { - "type": "BottleRack", - "size_x": 120.0, - "size_y": 250.0, - "size_z": 100.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "bottle_rack", - "model": null, - "barcode": null, - "num_items_x": 6, - "num_items_y": 2, - "position_spacing": 35.0, - "orientation": "vertical", - "padding_x": 20.0, - "padding_y": 20.0 - }, - "data": { - "bottle_diameter": 30.0, - "bottle_height": 100.0, - "position_spacing": 35.0, - "name_to_index": {} - } - }, - { - "id": "sheet_6x2_0", - "name": "sheet_6x2_0", - "sample_id": null, - "children": [], - "parent": "bottle_rack_6x2", - "type": "container", - "class": "", - "position": { - "x": 20.0, - "y": 20.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_6x2_1", - "name": "sheet_6x2_1", - "sample_id": null, - "children": [], - "parent": "bottle_rack_6x2", - "type": "container", - "class": "", - "position": { - "x": 20.0, - "y": 55.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_6x2_2", - "name": "sheet_6x2_2", - "sample_id": null, - "children": [], - "parent": "bottle_rack_6x2", - "type": "container", - "class": "", - "position": { - "x": 20.0, - "y": 90.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_6x2_3", - "name": "sheet_6x2_3", - "sample_id": null, - "children": [], - "parent": "bottle_rack_6x2", - "type": "container", - "class": "", - "position": { - "x": 20.0, - "y": 125.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_6x2_4", - "name": "sheet_6x2_4", - "sample_id": null, - "children": [], - "parent": "bottle_rack_6x2", - "type": "container", - "class": "", - "position": { - "x": 20.0, - "y": 160.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_6x2_5", - "name": "sheet_6x2_5", - "sample_id": null, - "children": [], - "parent": "bottle_rack_6x2", - "type": "container", - "class": "", - "position": { - "x": 20.0, - "y": 195.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_6x2_6", - "name": "sheet_6x2_6", - "sample_id": null, - "children": [], - "parent": "bottle_rack_6x2", - "type": "container", - "class": "", - "position": { - "x": 55.0, - "y": 20.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_6x2_7", - "name": "sheet_6x2_7", - "sample_id": null, - "children": [], - "parent": "bottle_rack_6x2", - "type": "container", - "class": "", - "position": { - "x": 55.0, - "y": 55.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_6x2_8", - "name": "sheet_6x2_8", - "sample_id": null, - "children": [], - "parent": "bottle_rack_6x2", - "type": "container", - "class": "", - "position": { - "x": 55.0, - "y": 90.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_6x2_9", - "name": "sheet_6x2_9", - "sample_id": null, - "children": [], - "parent": "bottle_rack_6x2", - "type": "container", - "class": "", - "position": { - "x": 55.0, - "y": 125.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_6x2_10", - "name": "sheet_6x2_10", - "sample_id": null, - "children": [], - "parent": "bottle_rack_6x2", - "type": "container", - "class": "", - "position": { - "x": 55.0, - "y": 160.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "sheet_6x2_11", - "name": "sheet_6x2_11", - "sample_id": null, - "children": [], - "parent": "bottle_rack_6x2", - "type": "container", - "class": "", - "position": { - "x": 55.0, - "y": 195.0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, - { - "id": "bottle_rack_6x2_2", - "name": "bottle_rack_6x2_2", - "sample_id": null, - "children": [], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 430, - "y": 300, - "z": 0 - }, - "config": { - "type": "BottleRack", - "size_x": 120.0, - "size_y": 250.0, - "size_z": 100.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "bottle_rack", - "model": null, - "barcode": null, - "num_items_x": 6, - "num_items_y": 2, - "position_spacing": 35.0, - "orientation": "vertical", - "padding_x": 20.0, - "padding_y": 20.0 - }, - "data": { - "bottle_diameter": 30.0, - "bottle_height": 100.0, - "position_spacing": 35.0, - "name_to_index": {} - } - }, - { - "id": "tip_box_64", - "name": "tip_box_64", - "sample_id": null, - "children": [ - "tip_box_64_tipspot_0_0", - "tip_box_64_tipspot_0_1", - "tip_box_64_tipspot_0_2", - "tip_box_64_tipspot_0_3", - "tip_box_64_tipspot_0_4", - "tip_box_64_tipspot_0_5", - "tip_box_64_tipspot_0_6", - "tip_box_64_tipspot_0_7", - "tip_box_64_tipspot_1_0", - "tip_box_64_tipspot_1_1", - "tip_box_64_tipspot_1_2", - "tip_box_64_tipspot_1_3", - "tip_box_64_tipspot_1_4", - "tip_box_64_tipspot_1_5", - "tip_box_64_tipspot_1_6", - "tip_box_64_tipspot_1_7", - "tip_box_64_tipspot_2_0", - "tip_box_64_tipspot_2_1", - "tip_box_64_tipspot_2_2", - "tip_box_64_tipspot_2_3", - "tip_box_64_tipspot_2_4", - "tip_box_64_tipspot_2_5", - "tip_box_64_tipspot_2_6", - "tip_box_64_tipspot_2_7", - "tip_box_64_tipspot_3_0", - "tip_box_64_tipspot_3_1", - "tip_box_64_tipspot_3_2", - "tip_box_64_tipspot_3_3", - "tip_box_64_tipspot_3_4", - "tip_box_64_tipspot_3_5", - "tip_box_64_tipspot_3_6", - "tip_box_64_tipspot_3_7", - "tip_box_64_tipspot_4_0", - "tip_box_64_tipspot_4_1", - "tip_box_64_tipspot_4_2", - "tip_box_64_tipspot_4_3", - "tip_box_64_tipspot_4_4", - "tip_box_64_tipspot_4_5", - "tip_box_64_tipspot_4_6", - "tip_box_64_tipspot_4_7", - "tip_box_64_tipspot_5_0", - "tip_box_64_tipspot_5_1", - "tip_box_64_tipspot_5_2", - "tip_box_64_tipspot_5_3", - "tip_box_64_tipspot_5_4", - "tip_box_64_tipspot_5_5", - "tip_box_64_tipspot_5_6", - "tip_box_64_tipspot_5_7", - "tip_box_64_tipspot_6_0", - "tip_box_64_tipspot_6_1", - "tip_box_64_tipspot_6_2", - "tip_box_64_tipspot_6_3", - "tip_box_64_tipspot_6_4", - "tip_box_64_tipspot_6_5", - "tip_box_64_tipspot_6_6", - "tip_box_64_tipspot_6_7", - "tip_box_64_tipspot_7_0", - "tip_box_64_tipspot_7_1", - "tip_box_64_tipspot_7_2", - "tip_box_64_tipspot_7_3", - "tip_box_64_tipspot_7_4", - "tip_box_64_tipspot_7_5", - "tip_box_64_tipspot_7_6", - "tip_box_64_tipspot_7_7" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 300, - "y": 100, - "z": 0 - }, - "config": { - "type": "TipBox64", - "size_x": 127.8, - "size_y": 85.5, - "size_z": 60.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_box_64", - "model": null, - "barcode": null, - "ordering": { - "A1": "tip_box_64_tipspot_0_0", - "B1": "tip_box_64_tipspot_0_1", - "C1": "tip_box_64_tipspot_0_2", - "D1": "tip_box_64_tipspot_0_3", - "E1": "tip_box_64_tipspot_0_4", - "F1": "tip_box_64_tipspot_0_5", - "G1": "tip_box_64_tipspot_0_6", - "H1": "tip_box_64_tipspot_0_7", - "A2": "tip_box_64_tipspot_1_0", - "B2": "tip_box_64_tipspot_1_1", - "C2": "tip_box_64_tipspot_1_2", - "D2": "tip_box_64_tipspot_1_3", - "E2": "tip_box_64_tipspot_1_4", - "F2": "tip_box_64_tipspot_1_5", - "G2": "tip_box_64_tipspot_1_6", - "H2": "tip_box_64_tipspot_1_7", - "A3": "tip_box_64_tipspot_2_0", - "B3": "tip_box_64_tipspot_2_1", - "C3": "tip_box_64_tipspot_2_2", - "D3": "tip_box_64_tipspot_2_3", - "E3": "tip_box_64_tipspot_2_4", - "F3": "tip_box_64_tipspot_2_5", - "G3": "tip_box_64_tipspot_2_6", - "H3": "tip_box_64_tipspot_2_7", - "A4": "tip_box_64_tipspot_3_0", - "B4": "tip_box_64_tipspot_3_1", - "C4": "tip_box_64_tipspot_3_2", - "D4": "tip_box_64_tipspot_3_3", - "E4": "tip_box_64_tipspot_3_4", - "F4": "tip_box_64_tipspot_3_5", - "G4": "tip_box_64_tipspot_3_6", - "H4": "tip_box_64_tipspot_3_7", - "A5": "tip_box_64_tipspot_4_0", - "B5": "tip_box_64_tipspot_4_1", - "C5": "tip_box_64_tipspot_4_2", - "D5": "tip_box_64_tipspot_4_3", - "E5": "tip_box_64_tipspot_4_4", - "F5": "tip_box_64_tipspot_4_5", - "G5": "tip_box_64_tipspot_4_6", - "H5": "tip_box_64_tipspot_4_7", - "A6": "tip_box_64_tipspot_5_0", - "B6": "tip_box_64_tipspot_5_1", - "C6": "tip_box_64_tipspot_5_2", - "D6": "tip_box_64_tipspot_5_3", - "E6": "tip_box_64_tipspot_5_4", - "F6": "tip_box_64_tipspot_5_5", - "G6": "tip_box_64_tipspot_5_6", - "H6": "tip_box_64_tipspot_5_7", - "A7": "tip_box_64_tipspot_6_0", - "B7": "tip_box_64_tipspot_6_1", - "C7": "tip_box_64_tipspot_6_2", - "D7": "tip_box_64_tipspot_6_3", - "E7": "tip_box_64_tipspot_6_4", - "F7": "tip_box_64_tipspot_6_5", - "G7": "tip_box_64_tipspot_6_6", - "H7": "tip_box_64_tipspot_6_7", - "A8": "tip_box_64_tipspot_7_0", - "B8": "tip_box_64_tipspot_7_1", - "C8": "tip_box_64_tipspot_7_2", - "D8": "tip_box_64_tipspot_7_3", - "E8": "tip_box_64_tipspot_7_4", - "F8": "tip_box_64_tipspot_7_5", - "G8": "tip_box_64_tipspot_7_6", - "H8": "tip_box_64_tipspot_7_7" - }, - "num_items_x": 8, - "num_items_y": 8, - "dx": 8.0, - "dy": 8.0, - "item_dx": 9.0, - "item_dy": 9.0 - }, - "data": {} - }, - { - "id": "tip_box_64_tipspot_0_0", - "name": "tip_box_64_tipspot_0_0", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 8.0, - "y": 71.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_0_1", - "name": "tip_box_64_tipspot_0_1", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 8.0, - "y": 62.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_0_2", - "name": "tip_box_64_tipspot_0_2", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 8.0, - "y": 53.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_0_3", - "name": "tip_box_64_tipspot_0_3", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 8.0, - "y": 44.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_0_4", - "name": "tip_box_64_tipspot_0_4", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 8.0, - "y": 35.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_0_5", - "name": "tip_box_64_tipspot_0_5", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 8.0, - "y": 26.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_0_6", - "name": "tip_box_64_tipspot_0_6", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 8.0, - "y": 17.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_0_7", - "name": "tip_box_64_tipspot_0_7", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 8.0, - "y": 8.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_1_0", - "name": "tip_box_64_tipspot_1_0", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 17.0, - "y": 71.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_1_1", - "name": "tip_box_64_tipspot_1_1", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 17.0, - "y": 62.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_1_2", - "name": "tip_box_64_tipspot_1_2", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 17.0, - "y": 53.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_1_3", - "name": "tip_box_64_tipspot_1_3", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 17.0, - "y": 44.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_1_4", - "name": "tip_box_64_tipspot_1_4", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 17.0, - "y": 35.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_1_5", - "name": "tip_box_64_tipspot_1_5", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 17.0, - "y": 26.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_1_6", - "name": "tip_box_64_tipspot_1_6", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 17.0, - "y": 17.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_1_7", - "name": "tip_box_64_tipspot_1_7", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 17.0, - "y": 8.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_2_0", - "name": "tip_box_64_tipspot_2_0", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 26.0, - "y": 71.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_2_1", - "name": "tip_box_64_tipspot_2_1", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 26.0, - "y": 62.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_2_2", - "name": "tip_box_64_tipspot_2_2", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 26.0, - "y": 53.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_2_3", - "name": "tip_box_64_tipspot_2_3", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 26.0, - "y": 44.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_2_4", - "name": "tip_box_64_tipspot_2_4", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 26.0, - "y": 35.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_2_5", - "name": "tip_box_64_tipspot_2_5", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 26.0, - "y": 26.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_2_6", - "name": "tip_box_64_tipspot_2_6", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 26.0, - "y": 17.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_2_7", - "name": "tip_box_64_tipspot_2_7", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 26.0, - "y": 8.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_3_0", - "name": "tip_box_64_tipspot_3_0", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 35.0, - "y": 71.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_3_1", - "name": "tip_box_64_tipspot_3_1", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 35.0, - "y": 62.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_3_2", - "name": "tip_box_64_tipspot_3_2", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 35.0, - "y": 53.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_3_3", - "name": "tip_box_64_tipspot_3_3", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 35.0, - "y": 44.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_3_4", - "name": "tip_box_64_tipspot_3_4", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 35.0, - "y": 35.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_3_5", - "name": "tip_box_64_tipspot_3_5", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 35.0, - "y": 26.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_3_6", - "name": "tip_box_64_tipspot_3_6", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 35.0, - "y": 17.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_3_7", - "name": "tip_box_64_tipspot_3_7", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 35.0, - "y": 8.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_4_0", - "name": "tip_box_64_tipspot_4_0", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 44.0, - "y": 71.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_4_1", - "name": "tip_box_64_tipspot_4_1", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 44.0, - "y": 62.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_4_2", - "name": "tip_box_64_tipspot_4_2", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 44.0, - "y": 53.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_4_3", - "name": "tip_box_64_tipspot_4_3", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 44.0, - "y": 44.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_4_4", - "name": "tip_box_64_tipspot_4_4", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 44.0, - "y": 35.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_4_5", - "name": "tip_box_64_tipspot_4_5", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 44.0, - "y": 26.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_4_6", - "name": "tip_box_64_tipspot_4_6", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 44.0, - "y": 17.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_4_7", - "name": "tip_box_64_tipspot_4_7", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 44.0, - "y": 8.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_5_0", - "name": "tip_box_64_tipspot_5_0", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 53.0, - "y": 71.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_5_1", - "name": "tip_box_64_tipspot_5_1", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 53.0, - "y": 62.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_5_2", - "name": "tip_box_64_tipspot_5_2", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 53.0, - "y": 53.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_5_3", - "name": "tip_box_64_tipspot_5_3", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 53.0, - "y": 44.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_5_4", - "name": "tip_box_64_tipspot_5_4", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 53.0, - "y": 35.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_5_5", - "name": "tip_box_64_tipspot_5_5", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 53.0, - "y": 26.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_5_6", - "name": "tip_box_64_tipspot_5_6", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 53.0, - "y": 17.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_5_7", - "name": "tip_box_64_tipspot_5_7", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 53.0, - "y": 8.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_6_0", - "name": "tip_box_64_tipspot_6_0", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 62.0, - "y": 71.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_6_1", - "name": "tip_box_64_tipspot_6_1", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 62.0, - "y": 62.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_6_2", - "name": "tip_box_64_tipspot_6_2", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 62.0, - "y": 53.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_6_3", - "name": "tip_box_64_tipspot_6_3", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 62.0, - "y": 44.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_6_4", - "name": "tip_box_64_tipspot_6_4", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 62.0, - "y": 35.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_6_5", - "name": "tip_box_64_tipspot_6_5", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 62.0, - "y": 26.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_6_6", - "name": "tip_box_64_tipspot_6_6", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 62.0, - "y": 17.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_6_7", - "name": "tip_box_64_tipspot_6_7", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 62.0, - "y": 8.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_7_0", - "name": "tip_box_64_tipspot_7_0", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 71.0, - "y": 71.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_7_1", - "name": "tip_box_64_tipspot_7_1", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 71.0, - "y": 62.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_7_2", - "name": "tip_box_64_tipspot_7_2", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 71.0, - "y": 53.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_7_3", - "name": "tip_box_64_tipspot_7_3", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 71.0, - "y": 44.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_7_4", - "name": "tip_box_64_tipspot_7_4", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 71.0, - "y": 35.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_7_5", - "name": "tip_box_64_tipspot_7_5", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 71.0, - "y": 26.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_7_6", - "name": "tip_box_64_tipspot_7_6", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 71.0, - "y": 17.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "tip_box_64_tipspot_7_7", - "name": "tip_box_64_tipspot_7_7", - "sample_id": null, - "children": [], - "parent": "tip_box_64", - "type": "container", - "class": "", - "position": { - "x": 71.0, - "y": 8.0, - "z": 0.0 - }, - "config": { - "type": "TipSpot", - "size_x": 10, - "size_y": 10, - "size_z": 0.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "tip_spot", - "model": null, - "barcode": null, - "prototype_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - }, - "data": { - "tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - }, - "tip_state": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - }, - "pending_tip": { - "type": "Tip", - "total_tip_length": 20.0, - "has_filter": false, - "maximal_volume": 1000, - "fitting_depth": 8.0 - } - } - }, - { - "id": "waste_tip_box", - "name": "waste_tip_box", - "sample_id": null, - "children": [], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 300, - "y": 200, - "z": 0 - }, - "config": { - "type": "WasteTipBox", - "size_x": 127.8, - "size_y": 85.5, - "size_z": 60.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "waste_tip_box", - "model": null, - "barcode": null, - "max_volume": "Infinity", - "material_z_thickness": 0, - "compute_volume_from_height": null, - "compute_height_from_volume": null - }, - "data": { - "liquids": [], - "pending_liquids": [], - "liquid_history": [] - } - } - ], - "links": [] -} \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/test.ipynb b/unilabos/devices/workstation/coin_cell_assembly/test.ipynb deleted file mode 100644 index 76c07d7..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/test.ipynb +++ /dev/null @@ -1,750 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 22, - "id": "80bc9500", - "metadata": {}, - "outputs": [], - "source": [ - "from __future__ import annotations\n", - "\n", - "from collections import OrderedDict\n", - "from typing import Any, Dict, List, Optional, TypedDict, Union, cast\n", - "\n", - "from pylabrobot.resources.coordinate import Coordinate\n", - "from pylabrobot.resources.container import Container\n", - "from pylabrobot.resources.deck import Deck\n", - "from pylabrobot.resources.itemized_resource import ItemizedResource\n", - "from pylabrobot.resources.resource import Resource\n", - "from pylabrobot.resources.resource_stack import ResourceStack\n", - "from pylabrobot.resources.tip_rack import TipRack, TipSpot\n", - "from pylabrobot.resources.trash import Trash\n", - "from pylabrobot.resources.utils import create_ordered_items_2d" - ] - }, - { - "cell_type": "markdown", - "id": "498a9159", - "metadata": {}, - "source": [ - "物料类型构建" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "f4a27241", - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "class ElectrodeSheetState(TypedDict):\n", - " diameter: float # 直径 (mm)\n", - " thickness: float # 厚度 (mm)\n", - " mass: float # 质量 (g)\n", - " material_type: str # 材料类型(正极、负极、隔膜、弹片、垫片、铝箔等)\n", - " info: Optional[str] # 附加信息\n", - "\n", - "class ElectrodeSheet(Resource):\n", - " \"\"\"极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料\"\"\"\n", - "\n", - " def __init__(\n", - " self,\n", - " name: str = \"极片\",\n", - " size_x=10,\n", - " size_y=10,\n", - " size_z=10,\n", - " category: str = \"electrode_sheet\",\n", - " model: Optional[str] = None,\n", - " ):\n", - " \"\"\"初始化极片\n", - "\n", - " Args:\n", - " name: 极片名称\n", - " category: 类别\n", - " model: 型号\n", - " \"\"\"\n", - " super().__init__(\n", - " name=name,\n", - " size_x=size_x,\n", - " size_y=size_y,\n", - " size_z=size_z,\n", - " category=category,\n", - " model=model,\n", - " )\n", - " self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState(\n", - " diameter=14,\n", - " thickness=0.1,\n", - " mass=0.5,\n", - " material_type=\"copper\",\n", - " info=None\n", - " )\n", - "\n", - " # TODO: 这个还要不要?给self._unilabos_state赋值的?\n", - " def load_state(self, state: Dict[str, Any]) -> None:\n", - " \"\"\"格式不变\"\"\"\n", - " super().load_state(state)\n", - " self._unilabos_state = state\n", - " #序列化\n", - " def serialize_state(self) -> Dict[str, Dict[str, Any]]:\n", - " \"\"\"格式不变\"\"\"\n", - " data = super().serialize_state()\n", - " data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)\n", - " return data\n" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "830f052e", - "metadata": {}, - "outputs": [], - "source": [ - "# TODO: 这个应该只能放一个极片\n", - "class MaterialHoleState(TypedDict):\n", - " diameter: int\n", - " depth: int\n", - " max_sheets: int\n", - " info: Optional[str] # 附加信息\n", - "\n", - "class MaterialHole(Resource):\n", - " \"\"\"料板洞位类\"\"\"\n", - " children: List[ElectrodeSheet] = []\n", - "\n", - " def __init__(\n", - " self,\n", - " name: str,\n", - " size_x: float,\n", - " size_y: float,\n", - " size_z: float,\n", - " category: str = \"material_hole\",\n", - " **kwargs\n", - " ):\n", - " super().__init__(\n", - " name=name,\n", - " size_x=size_x,\n", - " size_y=size_y,\n", - " size_z=size_z,\n", - " category=category,\n", - " )\n", - " self._unilabos_state: MaterialHoleState = MaterialHoleState(\n", - " diameter=20,\n", - " depth=10,\n", - " max_sheets=1,\n", - " info=None\n", - " )\n", - "\n", - " def get_all_sheet_info(self):\n", - " info_list = []\n", - " for sheet in self.children:\n", - " info_list.append(sheet._unilabos_state[\"info\"])\n", - " return info_list\n", - " \n", - " #这个函数函数好像没用,一般不会集中赋值质量\n", - " def set_all_sheet_mass(self):\n", - " for sheet in self.children:\n", - " sheet._unilabos_state[\"mass\"] = 0.5 # 示例:设置质量为0.5g\n", - "\n", - " def load_state(self, state: Dict[str, Any]) -> None:\n", - " \"\"\"格式不变\"\"\"\n", - " super().load_state(state)\n", - " self._unilabos_state = state\n", - "\n", - " def serialize_state(self) -> Dict[str, Dict[str, Any]]:\n", - " \"\"\"格式不变\"\"\"\n", - " data = super().serialize_state()\n", - " data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)\n", - " return data\n", - " #移动极片前先取出对象\n", - " def get_sheet_with_name(self, name: str) -> Optional[ElectrodeSheet]:\n", - " for sheet in self.children:\n", - " if sheet.name == name:\n", - " return sheet\n", - " return None\n", - "\n", - " def has_electrode_sheet(self) -> bool:\n", - " \"\"\"检查洞位是否有极片\"\"\"\n", - " return len(self.children) > 0\n", - "\n", - " def assign_child_resource(\n", - " self,\n", - " resource: ElectrodeSheet,\n", - " location: Optional[Coordinate],\n", - " reassign: bool = True,\n", - " ):\n", - " \"\"\"放置极片\"\"\"\n", - " # TODO: 这里要改,diameter找不到,加入._unilabos_state后应该没问题\n", - " if resource._unilabos_state[\"diameter\"] > self._unilabos_state[\"diameter\"]:\n", - " raise ValueError(f\"极片直径 {resource._unilabos_state['diameter']} 超过洞位直径 {self._unilabos_state['diameter']}\")\n", - " if len(self.children) >= self._unilabos_state[\"max_sheets\"]:\n", - " raise ValueError(f\"洞位已满,无法放置更多极片\")\n", - " super().assign_child_resource(resource, location, reassign)\n", - "\n", - " # 根据children的编号取物料对象。\n", - " def get_electrode_sheet_info(self, index: int) -> ElectrodeSheet:\n", - " return self.children[index]\n", - "\n", - "\n", - "#料板\n", - "class MaterialPlateState(TypedDict):\n", - " hole_spacing_x: float\n", - " hole_spacing_y: float\n", - " hole_diameter: float\n", - " info: Optional[str] # 附加信息\n", - "\n", - "class MaterialPlate(ItemizedResource[MaterialHole]):\n", - " \"\"\"料板类 - 4x4个洞位,每个洞位放1个极片\"\"\"\n", - " \n", - " children: List[MaterialHole]\n", - "\n", - " def __init__(\n", - " self,\n", - " name: str,\n", - " size_x: float,\n", - " size_y: float,\n", - " size_z: float,\n", - " ordered_items: Optional[Dict[str, MaterialHole]] = None,\n", - " ordering: Optional[OrderedDict[str, str]] = None,\n", - " category: str = \"material_plate\",\n", - " model: Optional[str] = None,\n", - " fill: bool = False\n", - " ):\n", - " \"\"\"初始化料板\n", - "\n", - " Args:\n", - " name: 料板名称\n", - " size_x: 长度 (mm)\n", - " size_y: 宽度 (mm)\n", - " size_z: 高度 (mm)\n", - " hole_diameter: 洞直径 (mm)\n", - " hole_depth: 洞深度 (mm)\n", - " hole_spacing_x: X方向洞位间距 (mm)\n", - " hole_spacing_y: Y方向洞位间距 (mm)\n", - " number: 编号\n", - " category: 类别\n", - " model: 型号\n", - " \"\"\"\n", - " self._unilabos_state: MaterialPlateState = MaterialPlateState(\n", - " hole_spacing_x=24.0,\n", - " hole_spacing_y=24.0,\n", - " hole_diameter=20.0,\n", - " info=\"\",\n", - " )\n", - " # 创建4x4的洞位\n", - " # TODO: 这里要改,对应不同形状\n", - " holes = create_ordered_items_2d(\n", - " klass=MaterialHole,\n", - " num_items_x=4,\n", - " num_items_y=4,\n", - " dx=(size_x - 4 * self._unilabos_state[\"hole_spacing_x\"]) / 2, # 居中\n", - " dy=(size_y - 4 * self._unilabos_state[\"hole_spacing_y\"]) / 2, # 居中\n", - " dz=size_z,\n", - " item_dx=self._unilabos_state[\"hole_spacing_x\"],\n", - " item_dy=self._unilabos_state[\"hole_spacing_y\"],\n", - " size_x = 16,\n", - " size_y = 16,\n", - " size_z = 16,\n", - " )\n", - " if fill:\n", - " super().__init__(\n", - " name=name,\n", - " size_x=size_x,\n", - " size_y=size_y,\n", - " size_z=size_z,\n", - " ordered_items=holes,\n", - " category=category,\n", - " model=model,\n", - " )\n", - " else:\n", - " super().__init__(\n", - " name=name,\n", - " size_x=size_x,\n", - " size_y=size_y,\n", - " size_z=size_z,\n", - " ordered_items=ordered_items,\n", - " ordering=ordering,\n", - " category=category,\n", - " model=model,\n", - " )\n", - "\n", - " def update_locations(self):\n", - " # TODO:调多次相加\n", - " holes = create_ordered_items_2d(\n", - " klass=MaterialHole,\n", - " num_items_x=4,\n", - " num_items_y=4,\n", - " dx=(self._size_x - 3 * self._unilabos_state[\"hole_spacing_x\"]) / 2, # 居中\n", - " dy=(self._size_y - 3 * self._unilabos_state[\"hole_spacing_y\"]) / 2, # 居中\n", - " dz=self._size_z,\n", - " item_dx=self._unilabos_state[\"hole_spacing_x\"],\n", - " item_dy=self._unilabos_state[\"hole_spacing_y\"],\n", - " size_x = 1,\n", - " size_y = 1,\n", - " size_z = 1,\n", - " )\n", - " for item, original_item in zip(holes.items(), self.children):\n", - " original_item.location = item[1].location" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "8318ccca", - "metadata": {}, - "outputs": [], - "source": [ - "class CoincellDeck(Deck):\n", - " \"\"\"纽扣电池组装工作站台面类\"\"\"\n", - "\n", - " def __init__(\n", - " self,\n", - " name: str = \"coin_cell_deck\",\n", - " size_x: float = 1620.0, # 3.66m\n", - " size_y: float = 1270.0, # 1.23m\n", - " size_z: float = 500.0,\n", - " origin: Coordinate = Coordinate(0, 0, 0),\n", - " category: str = \"coin_cell_deck\",\n", - " ):\n", - " \"\"\"初始化纽扣电池组装工作站台面\n", - "\n", - " Args:\n", - " name: 台面名称\n", - " size_x: 长度 (mm) - 3.66m\n", - " size_y: 宽度 (mm) - 1.23m\n", - " size_z: 高度 (mm)\n", - " origin: 原点坐标\n", - " category: 类别\n", - " \"\"\"\n", - " super().__init__(\n", - " name=name,\n", - " size_x=size_x,\n", - " size_y=size_y,\n", - " size_z=size_z,\n", - " origin=origin,\n", - " category=category,\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "c73bae21", - "metadata": {}, - "outputs": [], - "source": [ - "import json" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "3369a1dd", - "metadata": {}, - "outputs": [], - "source": [ - "def upload_resources_to_unilab(wuliao: List[Resource]):\n", - " from unilabos.resources.graphio import convert_resources_from_type\n", - " from unilabos.config.config import BasicConfig \n", - " BasicConfig.ak = \"beb0c15f-2279-46a1-aba5-00eaf89aef55\"\n", - " BasicConfig.sk = \"15d4f25e-3512-4f9c-9bfb-43ab85e7b561\"\n", - " from unilabos.app.web.client import http_client\n", - " resources = convert_resources_from_type(wuliao, [Resource])\n", - " json.dump({\"nodes\": resources, \"links\": []}, open(\"button_battery_station_resources_unilab.json\", \"w\"), indent=2)\n", - " \n", - " #print(resources)\n", - " http_client.remote_addr = \"https://uni-lab.test.bohrium.com/api/v1\"\n", - " \n", - " http_client.resource_add(resources)" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "1543ddab", - "metadata": {}, - "outputs": [], - "source": [ - "liaopan1 = MaterialPlate(name=\"liaopan1\", size_x=120.8, size_y=120.5, size_z=10.0, fill=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "b732754a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "MaterialPlate(name=liaopan1, size_x=120.8, size_y=120.5, size_z=10.0, location=None)\n" - ] - } - ], - "source": [ - "print(liaopan1)" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "7e6e7252", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[MaterialHole(name=liaopan1_materialhole_0_0, location=Coordinate(012.400, 084.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_0_1, location=Coordinate(012.400, 060.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_0_2, location=Coordinate(012.400, 036.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_0_3, location=Coordinate(012.400, 012.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_1_0, location=Coordinate(036.400, 084.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_1_1, location=Coordinate(036.400, 060.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_1_2, location=Coordinate(036.400, 036.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_1_3, location=Coordinate(036.400, 012.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_2_0, location=Coordinate(060.400, 084.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_2_1, location=Coordinate(060.400, 060.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_2_2, location=Coordinate(060.400, 036.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_2_3, location=Coordinate(060.400, 012.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_3_0, location=Coordinate(084.400, 084.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_3_1, location=Coordinate(084.400, 060.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_3_2, location=Coordinate(084.400, 036.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_3_3, location=Coordinate(084.400, 012.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole)]\n" - ] - } - ], - "source": [ - "print(liaopan1.children)" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "836ff68d", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[37m25-09-22 [15:15:08,950]\u001b[0m \u001b[1;36m[DEBUG]\u001b[0m \u001b[37mStarting new HTTPS connection (1): uni-lab.test.bohrium.com:443\u001b[37m [_new_conn:1049] [urllib3.connectionpool.connectionpool]\u001b[0m\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CoincellDeck(name=coin_cell_deck, location=Coordinate(000.000, 000.000, 000.000), size_x=1620.0, size_y=1270.0, size_z=500.0, category=coin_cell_deck)\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_plate\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_plate\n", - "转换pylabrobot的时候,出现未知类型 coin_cell_deck\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[37m25-09-22 [15:15:09,218]\u001b[0m \u001b[1;36m[DEBUG]\u001b[0m \u001b[37mhttps://uni-lab.test.bohrium.com:443 \"POST /api/v1/lab/material HTTP/1.1\" 200 10\u001b[37m [_make_request:544] [urllib3.connectionpool.connectionpool]\u001b[0m\n" - ] - } - ], - "source": [ - "deck = CoincellDeck()\n", - "#创建一个4*4的物料板\n", - "liaopan1 = MaterialPlate(name=\"liaopan1\", size_x=120.8, size_y=120.5, size_z=10.0, fill=True)\n", - "#把物料板放到桌子上\n", - "deck.assign_child_resource(liaopan1, Coordinate(x=0, y=0, z=0))\n", - "#创建一个极片\n", - "for i in range(16):\n", - " jipian = ElectrodeSheet(name=f\"jipian_{i}\", size_x= 12, size_y=12, size_z=0.1)\n", - " liaopan1.children[i].assign_child_resource(jipian, location=None)\n", - "#创建一个4*4的物料板\n", - "liaopan2 = MaterialPlate(name=\"liaopan2\", size_x=120.8, size_y=120.5, size_z=10.0, fill=True)\n", - "#把物料板放到桌子上\n", - "deck.assign_child_resource(liaopan2, Coordinate(x=500, y=0, z=0))\n", - "#liaopan.children[3].assign_child_resource(jipian, location=None)\n", - "print(deck)\n", - "\n", - "upload_resources_to_unilab([deck])" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "00aab9cf", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "MaterialPlate(name=liaopan1, size_x=120.8, size_y=120.5, size_z=10.0, location=Coordinate(000.000, 000.000, 000.000))\n" - ] - } - ], - "source": [ - "liaopan1 = deck.get_resource(\"liaopan1\")\n", - "print(liaopan1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7409969c", - "metadata": {}, - "outputs": [], - "source": [ - "liaopan1 = deck.get_resource(\"liaopan1\")\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "096dde04", - "metadata": {}, - "outputs": [], - "source": [ - "print()" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "5528df96", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[37m25-09-22 [15:17:44,322]\u001b[0m \u001b[1;36m[DEBUG]\u001b[0m \u001b[37mStarting new HTTPS connection (1): uni-lab.test.bohrium.com:443\u001b[37m [_new_conn:1049] [urllib3.connectionpool.connectionpool]\u001b[0m\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ElectrodeSheet(name=jipian_1, location=None, size_x=12, size_y=12, size_z=0.1, category=electrode_sheet)\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_plate\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_plate\n", - "转换pylabrobot的时候,出现未知类型 coin_cell_deck\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[37m25-09-22 [15:17:44,599]\u001b[0m \u001b[1;36m[DEBUG]\u001b[0m \u001b[37mhttps://uni-lab.test.bohrium.com:443 \"POST /api/v1/lab/material HTTP/1.1\" 200 10\u001b[37m [_make_request:544] [urllib3.connectionpool.connectionpool]\u001b[0m\n" - ] - } - ], - "source": [ - "#在台面上找到料盘和极片\n", - "liaopan1 = deck.get_resource(\"liaopan1\")\n", - "liaopan2 = deck.get_resource(\"liaopan2\")\n", - "jipian1 = liaopan1.children[1].children[0]\n", - "#\n", - "print(jipian1)\n", - "#把物料解绑后放到另一盘上\n", - "jipian1.parent.unassign_child_resource(jipian1)\n", - "liaopan2.children[1].assign_child_resource(jipian1, location=None)\n", - "#print(jipian2.parent)\n", - "upload_resources_to_unilab([deck])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "43736700", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[37m25-09-22 [14:31:50,027]\u001b[0m \u001b[1;36m[DEBUG]\u001b[0m \u001b[37mStarting new HTTPS connection (1): uni-lab.test.bohrium.com:443\u001b[37m [_new_conn:1049] [urllib3.connectionpool.connectionpool]\u001b[0m\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_plate\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 electrode_sheet\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_hole\n", - "转换pylabrobot的时候,出现未知类型 material_plate\n", - "转换pylabrobot的时候,出现未知类型 coin_cell_deck\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[37m25-09-22 [14:31:50,358]\u001b[0m \u001b[1;36m[DEBUG]\u001b[0m \u001b[37mhttps://uni-lab.test.bohrium.com:443 \"POST /api/v1/lab/material HTTP/1.1\" 200 10\u001b[37m [_make_request:544] [urllib3.connectionpool.connectionpool]\u001b[0m\n" - ] - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "unilab", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.11" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/unilabos/devices/workstation/coin_cell_assembly/work_station.yaml b/unilabos/devices/workstation/coin_cell_assembly/work_station.yaml deleted file mode 100644 index 29e8437..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/work_station.yaml +++ /dev/null @@ -1,6674 +0,0 @@ -bettery_station_registry: - category: - - work_station - class: - action_value_mappings: - auto-change_hole_sheet_to_2: - feedback: {} - goal: {} - goal_default: - hole: null - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - hole: - type: object - required: - - hole - type: object - result: {} - required: - - goal - title: change_hole_sheet_to_2参数 - type: object - type: UniLabJsonCommandAsync - auto-fill_plate: - feedback: {} - goal: {} - goal_default: {} - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: fill_plate参数 - type: object - type: UniLabJsonCommandAsync - auto-fun_wuliao_test: - feedback: {} - goal: {} - goal_default: {} - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: fun_wuliao_test参数 - type: object - type: UniLabJsonCommand - auto-func_allpack_cmd: - feedback: {} - goal: {} - goal_default: - elec_num: null - elec_use_num: null - file_path: D:\coin_cell_data - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - elec_num: - type: string - elec_use_num: - type: string - file_path: - default: D:\coin_cell_data - type: string - required: - - elec_num - - elec_use_num - type: object - result: {} - required: - - goal - title: func_allpack_cmd参数 - type: object - type: UniLabJsonCommand - auto-func_get_csv_export_status: - feedback: {} - goal: {} - goal_default: {} - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: func_get_csv_export_status参数 - type: object - type: UniLabJsonCommand - auto-func_pack_device_auto: - feedback: {} - goal: {} - goal_default: {} - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: func_pack_device_auto参数 - type: object - type: UniLabJsonCommand - auto-func_pack_device_init: - feedback: {} - goal: {} - goal_default: {} - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: func_pack_device_init参数 - type: object - type: UniLabJsonCommand - auto-func_pack_device_start: - feedback: {} - goal: {} - goal_default: {} - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: func_pack_device_start参数 - type: object - type: UniLabJsonCommand - auto-func_pack_device_stop: - feedback: {} - goal: {} - goal_default: {} - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: func_pack_device_stop参数 - type: object - type: UniLabJsonCommand - auto-func_pack_get_msg_cmd: - feedback: {} - goal: {} - goal_default: - file_path: D:\coin_cell_data - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - file_path: - default: D:\coin_cell_data - type: string - required: [] - type: object - result: {} - required: - - goal - title: func_pack_get_msg_cmd参数 - type: object - type: UniLabJsonCommand - auto-func_pack_send_bottle_num: - feedback: {} - goal: {} - goal_default: - bottle_num: null - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - bottle_num: - type: integer - required: - - bottle_num - type: object - result: {} - required: - - goal - title: func_pack_send_bottle_num参数 - type: object - type: UniLabJsonCommand - auto-func_pack_send_finished_cmd: - feedback: {} - goal: {} - goal_default: {} - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: func_pack_send_finished_cmd参数 - type: object - type: UniLabJsonCommand - auto-func_pack_send_msg_cmd: - feedback: {} - goal: {} - goal_default: - elec_use_num: null - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - elec_use_num: - type: string - required: - - elec_use_num - type: object - result: {} - required: - - goal - title: func_pack_send_msg_cmd参数 - type: object - type: UniLabJsonCommand - auto-func_read_data_and_output: - feedback: {} - goal: {} - goal_default: - file_path: D:\coin_cell_data - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - file_path: - default: D:\coin_cell_data - type: string - required: [] - type: object - result: {} - required: - - goal - title: func_read_data_and_output参数 - type: object - type: UniLabJsonCommand - auto-func_stop_read_data: - feedback: {} - goal: {} - goal_default: {} - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: func_stop_read_data参数 - type: object - type: UniLabJsonCommand - auto-modify_deck_name: - feedback: {} - goal: {} - goal_default: - resource_name: null - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - resource_name: - type: string - required: - - resource_name - type: object - result: {} - required: - - goal - title: modify_deck_name参数 - type: object - type: UniLabJsonCommand - auto-post_init: - feedback: {} - goal: {} - goal_default: - ros_node: null - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - ros_node: - type: object - required: - - ros_node - type: object - result: {} - required: - - goal - title: post_init参数 - type: object - type: UniLabJsonCommand - module: unilabos.devices.workstation.coin_cell_assembly.coin_cell_assembly:CoinCellAssemblyWorkstation - status_types: - data_assembly_coin_cell_num: int - data_assembly_pressure: int - data_assembly_time: float - data_axis_x_pos: float - data_axis_y_pos: float - data_axis_z_pos: float - data_coin_cell_code: str - data_coin_num: int - data_electrolyte_code: str - data_electrolyte_volume: int - data_glove_box_o2_content: float - data_glove_box_pressure: float - data_glove_box_water_content: float - data_open_circuit_voltage: float - data_pole_weight: float - request_rec_msg_status: bool - request_send_msg_status: bool - sys_mode: str - sys_status: str - type: python - config_info: [] - description: '' - handles: [] - icon: '' - init_param_schema: - config: - properties: - address: - default: 192.168.1.20 - type: string - debug_mode: - default: true - type: boolean - port: - default: '502' - type: string - station_resource: - type: object - required: - - station_resource - type: object - data: - properties: - data_assembly_coin_cell_num: - type: integer - data_assembly_pressure: - type: integer - data_assembly_time: - type: number - data_axis_x_pos: - type: number - data_axis_y_pos: - type: number - data_axis_z_pos: - type: number - data_coin_cell_code: - type: string - data_coin_num: - type: integer - data_electrolyte_code: - type: string - data_electrolyte_volume: - type: integer - data_glove_box_o2_content: - type: number - data_glove_box_pressure: - type: number - data_glove_box_water_content: - type: number - data_open_circuit_voltage: - type: number - data_pole_weight: - type: number - request_rec_msg_status: - type: boolean - request_send_msg_status: - type: boolean - sys_mode: - type: string - sys_status: - type: string - required: - - sys_status - - sys_mode - - request_rec_msg_status - - request_send_msg_status - - data_assembly_coin_cell_num - - data_assembly_time - - data_open_circuit_voltage - - data_axis_x_pos - - data_axis_y_pos - - data_axis_z_pos - - data_pole_weight - - data_assembly_pressure - - data_electrolyte_volume - - data_coin_num - - data_coin_cell_code - - data_electrolyte_code - - data_glove_box_pressure - - data_glove_box_o2_content - - data_glove_box_water_content - type: object - version: 1.0.0 -workstation: - category: - - work_station - class: - action_value_mappings: - AGVTransferProtocol: - feedback: {} - goal: - from_repo: from_repo - from_repo_position: from_repo_position - to_repo: to_repo - to_repo_position: to_repo_position - goal_default: - from_repo: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - from_repo_position: '' - to_repo: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - to_repo_position: '' - handles: {} - result: {} - schema: - description: '' - properties: - feedback: - properties: - status: - type: string - required: - - status - title: AGVTransfer_Feedback - type: object - goal: - properties: - from_repo: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: from_repo - type: object - from_repo_position: - type: string - to_repo: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: to_repo - type: object - to_repo_position: - type: string - required: - - from_repo - - from_repo_position - - to_repo - - to_repo_position - title: AGVTransfer_Goal - type: object - result: - properties: - return_info: - type: string - success: - type: boolean - required: - - return_info - - success - title: AGVTransfer_Result - type: object - required: - - goal - title: AGVTransfer - type: object - type: AGVTransfer - AddProtocol: - feedback: {} - goal: - amount: amount - equiv: equiv - event: event - mass: mass - mol: mol - purpose: purpose - rate_spec: rate_spec - ratio: ratio - reagent: reagent - stir: stir - stir_speed: stir_speed - time: time - vessel: vessel - viscous: viscous - volume: volume - goal_default: - amount: '' - equiv: '' - event: '' - mass: '' - mol: '' - purpose: '' - rate_spec: '' - ratio: '' - reagent: '' - stir: false - stir_speed: 0.0 - time: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - viscous: false - volume: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - - data_key: reagent - data_source: handle - data_type: resource - handler_key: reagent - label: Reagent - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_status: - type: string - progress: - type: number - required: - - progress - - current_status - title: Add_Feedback - type: object - goal: - properties: - amount: - type: string - equiv: - type: string - event: - type: string - mass: - type: string - mol: - type: string - purpose: - type: string - rate_spec: - type: string - ratio: - type: string - reagent: - type: string - stir: - type: boolean - stir_speed: - type: number - time: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - viscous: - type: boolean - volume: - type: string - required: - - vessel - - reagent - - volume - - mass - - amount - - time - - stir - - stir_speed - - viscous - - purpose - - event - - mol - - rate_spec - - equiv - - ratio - title: Add_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Add_Result - type: object - required: - - goal - title: Add - type: object - type: Add - AdjustPHProtocol: - feedback: {} - goal: - ph_value: ph_value - reagent: reagent - settling_time: settling_time - stir: stir - stir_speed: stir_speed - stir_time: stir_time - vessel: vessel - volume: volume - goal_default: - ph_value: 0.0 - reagent: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - - data_key: reagent - data_source: handle - data_type: resource - handler_key: reagent - label: Reagent - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: AdjustPH_Feedback - type: object - goal: - properties: - ph_value: - type: number - reagent: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - - ph_value - - reagent - title: AdjustPH_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: AdjustPH_Result - type: object - required: - - goal - title: AdjustPH - type: object - type: AdjustPH - CentrifugeProtocol: - feedback: {} - goal: - speed: speed - temp: temp - time: time - vessel: vessel - goal_default: - speed: 0.0 - temp: 0.0 - time: 0.0 - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_speed: - type: number - current_status: - type: string - current_temp: - type: number - progress: - type: number - required: - - progress - - current_speed - - current_temp - - current_status - title: Centrifuge_Feedback - type: object - goal: - properties: - speed: - type: number - temp: - type: number - time: - type: number - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - - speed - - time - - temp - title: Centrifuge_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Centrifuge_Result - type: object - required: - - goal - title: Centrifuge - type: object - type: Centrifuge - CleanProtocol: - feedback: {} - goal: - repeats: repeats - solvent: solvent - temp: temp - vessel: vessel - volume: volume - goal_default: - repeats: 0 - solvent: '' - temp: 0.0 - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - volume: 0.0 - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Solvent - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_device: - type: string - status: - type: string - time_remaining: - properties: - nanosec: - maximum: 4294967295 - minimum: 0 - type: integer - sec: - maximum: 2147483647 - minimum: -2147483648 - type: integer - required: - - sec - - nanosec - title: time_remaining - type: object - time_spent: - properties: - nanosec: - maximum: 4294967295 - minimum: 0 - type: integer - sec: - maximum: 2147483647 - minimum: -2147483648 - type: integer - required: - - sec - - nanosec - title: time_spent - type: object - required: - - status - - current_device - - time_spent - - time_remaining - title: Clean_Feedback - type: object - goal: - properties: - repeats: - maximum: 2147483647 - minimum: -2147483648 - type: integer - solvent: - type: string - temp: - type: number - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - volume: - type: number - required: - - vessel - - solvent - - volume - - temp - - repeats - title: Clean_Goal - type: object - result: - properties: - return_info: - type: string - success: - type: boolean - required: - - return_info - - success - title: Clean_Result - type: object - required: - - goal - title: Clean - type: object - type: Clean - CleanVesselProtocol: - feedback: {} - goal: - repeats: repeats - solvent: solvent - temp: temp - vessel: vessel - volume: volume - goal_default: - repeats: 0 - solvent: '' - temp: 0.0 - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - volume: 0.0 - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Solvent - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: CleanVessel_Feedback - type: object - goal: - properties: - repeats: - maximum: 2147483647 - minimum: -2147483648 - type: integer - solvent: - type: string - temp: - type: number - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - volume: - type: number - required: - - vessel - - solvent - - volume - - temp - - repeats - title: CleanVessel_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: CleanVessel_Result - type: object - required: - - goal - title: CleanVessel - type: object - type: CleanVessel - DissolveProtocol: - feedback: {} - goal: - amount: amount - event: event - mass: mass - mol: mol - reagent: reagent - solvent: solvent - stir_speed: stir_speed - temp: temp - time: time - vessel: vessel - volume: volume - goal_default: - amount: '' - event: '' - mass: '' - mol: '' - reagent: '' - solvent: '' - stir_speed: 0.0 - temp: '' - time: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - volume: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Solvent - - data_key: reagent - data_source: handle - data_type: resource - handler_key: reagent - label: Reagent - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: Dissolve_Feedback - type: object - goal: - properties: - amount: - type: string - event: - type: string - mass: - type: string - mol: - type: string - reagent: - type: string - solvent: - type: string - stir_speed: - type: number - temp: - type: string - time: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - volume: - type: string - required: - - vessel - - solvent - - volume - - amount - - temp - - time - - stir_speed - - mass - - mol - - reagent - - event - title: Dissolve_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Dissolve_Result - type: object - required: - - goal - title: Dissolve - type: object - type: Dissolve - DryProtocol: - feedback: {} - goal: - compound: compound - vessel: vessel - goal_default: - compound: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: Dry_Feedback - type: object - goal: - properties: - compound: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - compound - - vessel - title: Dry_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Dry_Result - type: object - required: - - goal - title: Dry - type: object - type: Dry - EvacuateAndRefillProtocol: - feedback: {} - goal: - gas: gas - vessel: vessel - goal_default: - gas: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_device: - type: string - status: - type: string - time_remaining: - properties: - nanosec: - maximum: 4294967295 - minimum: 0 - type: integer - sec: - maximum: 2147483647 - minimum: -2147483648 - type: integer - required: - - sec - - nanosec - title: time_remaining - type: object - time_spent: - properties: - nanosec: - maximum: 4294967295 - minimum: 0 - type: integer - sec: - maximum: 2147483647 - minimum: -2147483648 - type: integer - required: - - sec - - nanosec - title: time_spent - type: object - required: - - status - - current_device - - time_spent - - time_remaining - title: EvacuateAndRefill_Feedback - type: object - goal: - properties: - gas: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - - gas - title: EvacuateAndRefill_Goal - type: object - result: - properties: - return_info: - type: string - success: - type: boolean - required: - - return_info - - success - title: EvacuateAndRefill_Result - type: object - required: - - goal - title: EvacuateAndRefill - type: object - type: EvacuateAndRefill - EvaporateProtocol: - feedback: {} - goal: - pressure: pressure - solvent: solvent - stir_speed: stir_speed - temp: temp - time: time - vessel: vessel - goal_default: - pressure: 0.0 - solvent: '' - stir_speed: 0.0 - temp: 0.0 - time: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Evaporation Vessel - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Eluting Solvent - output: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: VesselOut - label: Evaporation Vessel - placeholder_keys: - vessel: unilabos_nodes - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_device: - type: string - status: - type: string - time_remaining: - properties: - nanosec: - maximum: 4294967295 - minimum: 0 - type: integer - sec: - maximum: 2147483647 - minimum: -2147483648 - type: integer - required: - - sec - - nanosec - title: time_remaining - type: object - time_spent: - properties: - nanosec: - maximum: 4294967295 - minimum: 0 - type: integer - sec: - maximum: 2147483647 - minimum: -2147483648 - type: integer - required: - - sec - - nanosec - title: time_spent - type: object - required: - - status - - current_device - - time_spent - - time_remaining - title: Evaporate_Feedback - type: object - goal: - properties: - pressure: - type: number - solvent: - type: string - stir_speed: - type: number - temp: - type: number - time: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - - pressure - - temp - - time - - stir_speed - - solvent - title: Evaporate_Goal - type: object - result: - properties: - return_info: - type: string - success: - type: boolean - required: - - return_info - - success - title: Evaporate_Result - type: object - required: - - goal - title: Evaporate - type: object - type: Evaporate - FilterProtocol: - feedback: {} - goal: - continue_heatchill: continue_heatchill - filtrate_vessel: filtrate_vessel - stir: stir - stir_speed: stir_speed - temp: temp - vessel: vessel - volume: volume - goal_default: - continue_heatchill: false - filtrate_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - stir: false - stir_speed: 0.0 - temp: 0.0 - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - volume: 0.0 - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - - data_key: filtrate_vessel - data_source: handle - data_type: resource - handler_key: FiltrateVessel - label: Filtrate Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - - data_key: filtrate_vessel - data_source: executor - data_type: resource - handler_key: FiltrateOut - label: Filtrate Vessel - placeholder_keys: - filtrate_vessel: unilabos_resources - vessel: unilabos_nodes - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_status: - type: string - current_temp: - type: number - filtered_volume: - type: number - progress: - type: number - required: - - progress - - current_temp - - filtered_volume - - current_status - title: Filter_Feedback - type: object - goal: - properties: - continue_heatchill: - type: boolean - filtrate_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: filtrate_vessel - type: object - stir: - type: boolean - stir_speed: - type: number - temp: - type: number - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - volume: - type: number - required: - - vessel - - filtrate_vessel - - stir - - stir_speed - - temp - - continue_heatchill - - volume - title: Filter_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Filter_Result - type: object - required: - - goal - title: Filter - type: object - type: Filter - FilterThroughProtocol: - feedback: {} - goal: - eluting_repeats: eluting_repeats - eluting_solvent: eluting_solvent - eluting_volume: eluting_volume - filter_through: filter_through - from_vessel: from_vessel - residence_time: residence_time - to_vessel: to_vessel - goal_default: - eluting_repeats: 0 - eluting_solvent: '' - eluting_volume: 0.0 - filter_through: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - from_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - residence_time: 0.0 - to_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVessel - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVessel - label: To Vessel - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Eluting Solvent - output: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVesselOut - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVesselOut - label: To Vessel - placeholder_keys: - from_vessel: unilabos_resources - to_vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: FilterThrough_Feedback - type: object - goal: - properties: - eluting_repeats: - maximum: 2147483647 - minimum: -2147483648 - type: integer - eluting_solvent: - type: string - eluting_volume: - type: number - filter_through: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: filter_through - type: object - from_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: from_vessel - type: object - residence_time: - type: number - to_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: to_vessel - type: object - required: - - from_vessel - - to_vessel - - filter_through - - eluting_solvent - - eluting_volume - - eluting_repeats - - residence_time - title: FilterThrough_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: FilterThrough_Result - type: object - required: - - goal - title: FilterThrough - type: object - type: FilterThrough - HeatChillProtocol: - feedback: {} - goal: - pressure: pressure - purpose: purpose - reflux_solvent: reflux_solvent - stir: stir - stir_speed: stir_speed - temp: temp - temp_spec: temp_spec - time: time - time_spec: time_spec - vessel: vessel - goal_default: - pressure: '' - purpose: '' - reflux_solvent: '' - stir: false - stir_speed: 0.0 - temp: 0.0 - temp_spec: '' - time: '' - time_spec: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - status: - type: string - required: - - status - title: HeatChill_Feedback - type: object - goal: - properties: - pressure: - type: string - purpose: - type: string - reflux_solvent: - type: string - stir: - type: boolean - stir_speed: - type: number - temp: - type: number - temp_spec: - type: string - time: - type: string - time_spec: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - - temp - - time - - temp_spec - - time_spec - - pressure - - reflux_solvent - - stir - - stir_speed - - purpose - title: HeatChill_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: HeatChill_Result - type: object - required: - - goal - title: HeatChill - type: object - type: HeatChill - HeatChillStartProtocol: - feedback: {} - goal: - purpose: purpose - temp: temp - vessel: vessel - goal_default: - purpose: '' - temp: 0.0 - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - status: - type: string - required: - - status - title: HeatChillStart_Feedback - type: object - goal: - properties: - purpose: - type: string - temp: - type: number - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - - temp - - purpose - title: HeatChillStart_Goal - type: object - result: - properties: - return_info: - type: string - success: - type: boolean - required: - - return_info - - success - title: HeatChillStart_Result - type: object - required: - - goal - title: HeatChillStart - type: object - type: HeatChillStart - HeatChillStopProtocol: - feedback: {} - goal: - vessel: vessel - goal_default: - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - status: - type: string - required: - - status - title: HeatChillStop_Feedback - type: object - goal: - properties: - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - title: HeatChillStop_Goal - type: object - result: - properties: - return_info: - type: string - success: - type: boolean - required: - - return_info - - success - title: HeatChillStop_Result - type: object - required: - - goal - title: HeatChillStop - type: object - type: HeatChillStop - HydrogenateProtocol: - feedback: {} - goal: - temp: temp - time: time - vessel: vessel - goal_default: - temp: '' - time: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: Hydrogenate_Feedback - type: object - goal: - properties: - temp: - type: string - time: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - temp - - time - - vessel - title: Hydrogenate_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Hydrogenate_Result - type: object - required: - - goal - title: Hydrogenate - type: object - type: Hydrogenate - PumpTransferProtocol: - feedback: {} - goal: - amount: amount - event: event - flowrate: flowrate - from_vessel: from_vessel - rate_spec: rate_spec - rinsing_repeats: rinsing_repeats - rinsing_solvent: rinsing_solvent - rinsing_volume: rinsing_volume - solid: solid - through: through - time: time - to_vessel: to_vessel - transfer_flowrate: transfer_flowrate - viscous: viscous - volume: volume - goal_default: - amount: '' - event: '' - flowrate: 0.0 - from_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - rate_spec: '' - rinsing_repeats: 0 - rinsing_solvent: '' - rinsing_volume: 0.0 - solid: false - through: '' - time: 0.0 - to_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - transfer_flowrate: 0.0 - viscous: false - volume: 0.0 - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVessel - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVessel - label: To Vessel - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Rinsing Solvent - output: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVesselOut - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVesselOut - label: To Vessel - placeholder_keys: - from_vessel: unilabos_nodes - to_vessel: unilabos_nodes - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_device: - type: string - status: - type: string - time_remaining: - properties: - nanosec: - maximum: 4294967295 - minimum: 0 - type: integer - sec: - maximum: 2147483647 - minimum: -2147483648 - type: integer - required: - - sec - - nanosec - title: time_remaining - type: object - time_spent: - properties: - nanosec: - maximum: 4294967295 - minimum: 0 - type: integer - sec: - maximum: 2147483647 - minimum: -2147483648 - type: integer - required: - - sec - - nanosec - title: time_spent - type: object - required: - - status - - current_device - - time_spent - - time_remaining - title: PumpTransfer_Feedback - type: object - goal: - properties: - amount: - type: string - event: - type: string - flowrate: - type: number - from_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: from_vessel - type: object - rate_spec: - type: string - rinsing_repeats: - maximum: 2147483647 - minimum: -2147483648 - type: integer - rinsing_solvent: - type: string - rinsing_volume: - type: number - solid: - type: boolean - through: - type: string - time: - type: number - to_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: to_vessel - type: object - transfer_flowrate: - type: number - viscous: - type: boolean - volume: - type: number - required: - - from_vessel - - to_vessel - - volume - - amount - - time - - viscous - - rinsing_solvent - - rinsing_volume - - rinsing_repeats - - solid - - flowrate - - transfer_flowrate - - rate_spec - - event - - through - title: PumpTransfer_Goal - type: object - result: - properties: - return_info: - type: string - success: - type: boolean - required: - - return_info - - success - title: PumpTransfer_Result - type: object - required: - - goal - title: PumpTransfer - type: object - type: PumpTransfer - RecrystallizeProtocol: - feedback: {} - goal: - ratio: ratio - solvent1: solvent1 - solvent2: solvent2 - vessel: vessel - volume: volume - goal_default: - ratio: '' - solvent1: '' - solvent2: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - volume: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - - data_key: solvent1 - data_source: handle - data_type: resource - handler_key: solvent1 - label: Solvent 1 - - data_key: solvent2 - data_source: handle - data_type: resource - handler_key: solvent2 - label: Solvent 2 - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: Recrystallize_Feedback - type: object - goal: - properties: - ratio: - type: string - solvent1: - type: string - solvent2: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - volume: - type: string - required: - - ratio - - solvent1 - - solvent2 - - vessel - - volume - title: Recrystallize_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Recrystallize_Result - type: object - required: - - goal - title: Recrystallize - type: object - type: Recrystallize - ResetHandlingProtocol: - feedback: {} - goal: - solvent: solvent - goal_default: - solvent: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Solvent - output: [] - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: ResetHandling_Feedback - type: object - goal: - properties: - solvent: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - solvent - - vessel - title: ResetHandling_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: ResetHandling_Result - type: object - required: - - goal - title: ResetHandling - type: object - type: ResetHandling - RunColumnProtocol: - feedback: {} - goal: - column: column - from_vessel: from_vessel - to_vessel: to_vessel - goal_default: - column: '' - from_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - pct1: '' - pct2: '' - ratio: '' - rf: '' - solvent1: '' - solvent2: '' - to_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVessel - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVessel - label: To Vessel - output: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVesselOut - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVesselOut - label: To Vessel - placeholder_keys: - column: unilabos_devices - from_vessel: unilabos_resources - to_vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: RunColumn_Feedback - type: object - goal: - properties: - column: - type: string - from_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: from_vessel - type: object - pct1: - type: string - pct2: - type: string - ratio: - type: string - rf: - type: string - solvent1: - type: string - solvent2: - type: string - to_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: to_vessel - type: object - required: - - from_vessel - - to_vessel - - column - - rf - - pct1 - - pct2 - - solvent1 - - solvent2 - - ratio - title: RunColumn_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: RunColumn_Result - type: object - required: - - goal - title: RunColumn - type: object - type: RunColumn - SeparateProtocol: - feedback: {} - goal: - from_vessel: from_vessel - product_phase: product_phase - purpose: purpose - repeats: repeats - separation_vessel: separation_vessel - settling_time: settling_time - solvent: solvent - solvent_volume: solvent_volume - stir_speed: stir_speed - stir_time: stir_time - through: through - to_vessel: to_vessel - waste_phase_to_vessel: waste_phase_to_vessel - goal_default: - from_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - product_phase: '' - product_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - purpose: '' - repeats: 0 - separation_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - settling_time: 0.0 - solvent: '' - solvent_volume: '' - stir_speed: 0.0 - stir_time: 0.0 - through: '' - to_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - volume: '' - waste_phase_to_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - waste_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVessel - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVessel - label: To Vessel - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Solvent - output: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVesselOut - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVesselOut - label: To Vessel - placeholder_keys: - from_vessel: unilabos_resources - to_vessel: unilabos_resources - waste_phase_to_vessel: unilabos_resources - waste_vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: Separate_Feedback - type: object - goal: - properties: - from_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: from_vessel - type: object - product_phase: - type: string - product_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: product_vessel - type: object - purpose: - type: string - repeats: - maximum: 2147483647 - minimum: -2147483648 - type: integer - separation_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: separation_vessel - type: object - settling_time: - type: number - solvent: - type: string - solvent_volume: - type: string - stir_speed: - type: number - stir_time: - type: number - through: - type: string - to_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: to_vessel - type: object - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - volume: - type: string - waste_phase_to_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: waste_phase_to_vessel - type: object - waste_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: waste_vessel - type: object - required: - - vessel - - purpose - - product_phase - - from_vessel - - separation_vessel - - to_vessel - - waste_phase_to_vessel - - product_vessel - - waste_vessel - - solvent - - solvent_volume - - volume - - through - - repeats - - stir_time - - stir_speed - - settling_time - title: Separate_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Separate_Result - type: object - required: - - goal - title: Separate - type: object - type: Separate - StartStirProtocol: - feedback: {} - goal: - purpose: purpose - stir_speed: stir_speed - vessel: vessel - goal_default: - purpose: '' - stir_speed: 0.0 - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_speed: - type: number - current_status: - type: string - progress: - type: number - required: - - progress - - current_speed - - current_status - title: StartStir_Feedback - type: object - goal: - properties: - purpose: - type: string - stir_speed: - type: number - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - - stir_speed - - purpose - title: StartStir_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: StartStir_Result - type: object - required: - - goal - title: StartStir - type: object - type: StartStir - StirProtocol: - feedback: {} - goal: - event: event - settling_time: settling_time - stir_speed: stir_speed - stir_time: stir_time - time: time - time_spec: time_spec - vessel: vessel - goal_default: - event: '' - settling_time: '' - stir_speed: 0.0 - stir_time: 0.0 - time: '' - time_spec: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - status: - type: string - required: - - status - title: Stir_Feedback - type: object - goal: - properties: - event: - type: string - settling_time: - type: string - stir_speed: - type: number - stir_time: - type: number - time: - type: string - time_spec: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - - time - - event - - time_spec - - stir_time - - stir_speed - - settling_time - title: Stir_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Stir_Result - type: object - required: - - goal - title: Stir - type: object - type: Stir - StopStirProtocol: - feedback: {} - goal: - vessel: vessel - goal_default: - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_status: - type: string - progress: - type: number - required: - - progress - - current_status - title: StopStir_Feedback - type: object - goal: - properties: - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - title: StopStir_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: StopStir_Result - type: object - required: - - goal - title: StopStir - type: object - type: StopStir - TransferProtocol: - feedback: {} - goal: - amount: amount - from_vessel: from_vessel - rinsing_repeats: rinsing_repeats - rinsing_solvent: rinsing_solvent - rinsing_volume: rinsing_volume - solid: solid - time: time - to_vessel: to_vessel - viscous: viscous - volume: volume - goal_default: - amount: '' - from_vessel: '' - rinsing_repeats: 0 - rinsing_solvent: '' - rinsing_volume: 0.0 - solid: false - time: 0.0 - to_vessel: '' - viscous: false - volume: 0.0 - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVessel - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVessel - label: To Vessel - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Rinsing Solvent - output: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVesselOut - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVesselOut - label: To Vessel - placeholder_keys: - from_vessel: unilabos_nodes - to_vessel: unilabos_nodes - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_status: - type: string - progress: - type: number - transferred_volume: - type: number - required: - - progress - - transferred_volume - - current_status - title: Transfer_Feedback - type: object - goal: - properties: - amount: - type: string - from_vessel: - type: string - rinsing_repeats: - maximum: 2147483647 - minimum: -2147483648 - type: integer - rinsing_solvent: - type: string - rinsing_volume: - type: number - solid: - type: boolean - time: - type: number - to_vessel: - type: string - viscous: - type: boolean - volume: - type: number - required: - - from_vessel - - to_vessel - - volume - - amount - - time - - viscous - - rinsing_solvent - - rinsing_volume - - rinsing_repeats - - solid - title: Transfer_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Transfer_Result - type: object - required: - - goal - title: Transfer - type: object - type: Transfer - WashSolidProtocol: - feedback: {} - goal: - filtrate_vessel: filtrate_vessel - repeats: repeats - solvent: solvent - stir: stir - stir_speed: stir_speed - temp: temp - time: time - vessel: vessel - volume: volume - goal_default: - event: '' - filtrate_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - mass: '' - repeats: 0 - repeats_spec: '' - solvent: '' - stir: false - stir_speed: 0.0 - temp: 0.0 - time: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - volume: '' - volume_spec: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Solvent - - data_key: filtrate_vessel - data_source: handle - data_type: resource - handler_key: filtrate_vessel - label: Filtrate Vessel - output: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: VesselOut - label: Vessel Out - - data_key: filtrate_vessel - data_source: executor - data_type: resource - handler_key: filtrate_vessel_out - label: Filtrate Vessel - placeholder_keys: - filtrate_vessel: unilabos_resources - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: WashSolid_Feedback - type: object - goal: - properties: - event: - type: string - filtrate_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: filtrate_vessel - type: object - mass: - type: string - repeats: - maximum: 2147483647 - minimum: -2147483648 - type: integer - repeats_spec: - type: string - solvent: - type: string - stir: - type: boolean - stir_speed: - type: number - temp: - type: number - time: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - volume: - type: string - volume_spec: - type: string - required: - - vessel - - solvent - - volume - - filtrate_vessel - - temp - - stir - - stir_speed - - time - - repeats - - volume_spec - - repeats_spec - - mass - - event - title: WashSolid_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: WashSolid_Result - type: object - required: - - goal - title: WashSolid - type: object - type: WashSolid - auto-create_ros_action_server: - feedback: {} - goal: {} - goal_default: - action_name: null - action_value_mapping: null - handles: {} - result: {} - schema: - description: create_ros_action_server的参数schema - properties: - feedback: {} - goal: - properties: - action_name: - type: string - action_value_mapping: - type: string - required: - - action_name - - action_value_mapping - type: object - result: {} - required: - - goal - title: create_ros_action_server参数 - type: object - type: UniLabJsonCommand - auto-execute_single_action: - feedback: {} - goal: {} - goal_default: - action_kwargs: null - action_name: null - device_id: null - handles: {} - result: {} - schema: - description: execute_single_action的参数schema - properties: - feedback: {} - goal: - properties: - action_kwargs: - type: string - action_name: - type: string - device_id: - type: string - required: - - device_id - - action_name - - action_kwargs - type: object - result: {} - required: - - goal - title: execute_single_action参数 - type: object - type: UniLabJsonCommandAsync - auto-initialize_device: - feedback: {} - goal: {} - goal_default: - device_config: null - device_id: null - handles: {} - result: {} - schema: - description: initialize_device的参数schema - properties: - feedback: {} - goal: - properties: - device_config: - type: string - device_id: - type: string - required: - - device_id - - device_config - type: object - result: {} - required: - - goal - title: initialize_device参数 - type: object - type: UniLabJsonCommand - module: unilabos.ros.nodes.presets.workstation:ROS2WorkstationNode - status_types: {} - type: ros2 - config_info: [] - description: Workstation - handles: [] - icon: '' - init_param_schema: - config: - properties: - action_value_mappings: - type: object - children: - type: object - device_id: - type: string - driver_instance: - type: string - hardware_interface: - type: object - print_publish: - default: true - type: string - protocol_type: - items: - type: string - type: array - resource_tracker: - type: string - status_types: - type: object - required: - - protocol_type - - children - - driver_instance - - device_id - - status_types - - action_value_mappings - - hardware_interface - type: object - data: - properties: {} - required: [] - type: object - version: 1.0.0 -workstation.example: - category: - - work_station - class: - action_value_mappings: {} - module: unilabos.devices.workstation.workstation_base:WorkstationExample - status_types: {} - type: python - config_info: [] - description: '' - handles: [] - icon: '' - init_param_schema: - config: - properties: - station_resource: - type: object - required: - - station_resource - type: object - data: - properties: {} - required: [] - type: object - version: 1.0.0 From 42f7010134998e78f6a1f692905068fe823a11c0 Mon Sep 17 00:00:00 2001 From: h840473807 <47357934+h840473807@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:57:57 +0800 Subject: [PATCH 022/104] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=89=A3=E7=94=B5?= =?UTF-8?q?=E5=B7=A5=E7=AB=99=E6=9C=80=E6=96=B0=E4=BB=A3=E7=A0=81=E5=88=B0?= =?UTF-8?q?YB3=E5=88=86=E6=94=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 提交扣电工站最新代码到YB3分支,更新注册表 --- .../coin_cell_assembly/__init__.py | 0 .../button_battery_station.py | 1006 ++++++++++++++ .../coin_cell_assembly/coin_cell_assembly.py | 1172 +++++++++++++++++ .../coin_cell_assembly_a.csv | 63 + .../coin_cell_assembly/new_cellconfig3c.json | 691 ++++++++++ .../coin_cell_assembly/workstation_base.py | 489 +++++++ unilabos/registry/devices/work_station.yaml | 1056 +++++++++------ 7 files changed, 4063 insertions(+), 414 deletions(-) create mode 100644 unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/__init__.py create mode 100644 unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/button_battery_station.py create mode 100644 unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/coin_cell_assembly.py create mode 100644 unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/coin_cell_assembly_a.csv create mode 100644 unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/new_cellconfig3c.json create mode 100644 unilabos/devices/workstation/coin_cell_assembly/workstation_base.py diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/__init__.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/button_battery_station.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/button_battery_station.py new file mode 100644 index 0000000..eae09b8 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/button_battery_station.py @@ -0,0 +1,1006 @@ +""" +纽扣电池组装工作站物料类定义 +Button Battery Assembly Station Resource Classes +""" + +from __future__ import annotations + +from collections import OrderedDict +from typing import Any, Dict, List, Optional, TypedDict, Union, cast + +from pylabrobot.resources.coordinate import Coordinate +from pylabrobot.resources.container import Container +from pylabrobot.resources.deck import Deck +from pylabrobot.resources.itemized_resource import ItemizedResource +from pylabrobot.resources.resource import Resource +from pylabrobot.resources.resource_stack import ResourceStack +from pylabrobot.resources.tip_rack import TipRack, TipSpot +from pylabrobot.resources.trash import Trash +from pylabrobot.resources.utils import create_ordered_items_2d + + +class ElectrodeSheetState(TypedDict): + diameter: float # 直径 (mm) + thickness: float # 厚度 (mm) + mass: float # 质量 (g) + material_type: str # 材料类型(正极、负极、隔膜、弹片、垫片、铝箔等) + height: float + electrolyte_name: str + data_electrolyte_code: str + open_circuit_voltage: float + assembly_pressure: float + electrolyte_volume: float + + info: Optional[str] # 附加信息 + +class ElectrodeSheet(Resource): + """极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料""" + + def __init__( + self, + name: str = "极片", + size_x=10, + size_y=10, + size_z=10, + category: str = "electrode_sheet", + model: Optional[str] = None, + ): + """初始化极片 + + Args: + name: 极片名称 + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + category=category, + model=model, + ) + self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState( + diameter=14, + thickness=0.1, + mass=0.5, + material_type="copper", + info=None + ) + + # TODO: 这个还要不要?给self._unilabos_state赋值的? + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + #序列化 + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + +# TODO: 这个应该只能放一个极片 +class MaterialHoleState(TypedDict): + diameter: int + depth: int + max_sheets: int + info: Optional[str] # 附加信息 + +class MaterialHole(Resource): + """料板洞位类""" + children: List[ElectrodeSheet] = [] + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + category: str = "material_hole", + **kwargs + ): + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + category=category, + ) + self._unilabos_state: MaterialHoleState = MaterialHoleState( + diameter=20, + depth=10, + max_sheets=1, + info=None + ) + + def get_all_sheet_info(self): + info_list = [] + for sheet in self.children: + info_list.append(sheet._unilabos_state["info"]) + return info_list + + #这个函数函数好像没用,一般不会集中赋值质量 + def set_all_sheet_mass(self): + for sheet in self.children: + sheet._unilabos_state["mass"] = 0.5 # 示例:设置质量为0.5g + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + #移动极片前先取出对象 + def get_sheet_with_name(self, name: str) -> Optional[ElectrodeSheet]: + for sheet in self.children: + if sheet.name == name: + return sheet + return None + + def has_electrode_sheet(self) -> bool: + """检查洞位是否有极片""" + return len(self.children) > 0 + + def assign_child_resource( + self, + resource: ElectrodeSheet, + location: Optional[Coordinate], + reassign: bool = True, + ): + """放置极片""" + # TODO: 这里要改,diameter找不到,加入._unilabos_state后应该没问题 + #if resource._unilabos_state["diameter"] > self._unilabos_state["diameter"]: + # raise ValueError(f"极片直径 {resource._unilabos_state['diameter']} 超过洞位直径 {self._unilabos_state['diameter']}") + #if len(self.children) >= self._unilabos_state["max_sheets"]: + # raise ValueError(f"洞位已满,无法放置更多极片") + super().assign_child_resource(resource, location, reassign) + + # 根据children的编号取物料对象。 + def get_electrode_sheet_info(self, index: int) -> ElectrodeSheet: + return self.children[index] + + + +class MaterialPlateState(TypedDict): + hole_spacing_x: float + hole_spacing_y: float + hole_diameter: float + info: Optional[str] # 附加信息 + +class MaterialPlate(ItemizedResource[MaterialHole]): + """料板类 - 4x4个洞位,每个洞位放1个极片""" + + children: List[MaterialHole] + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + ordered_items: Optional[Dict[str, MaterialHole]] = None, + ordering: Optional[OrderedDict[str, str]] = None, + category: str = "material_plate", + model: Optional[str] = None, + fill: bool = False + ): + """初始化料板 + + Args: + name: 料板名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + hole_diameter: 洞直径 (mm) + hole_depth: 洞深度 (mm) + hole_spacing_x: X方向洞位间距 (mm) + hole_spacing_y: Y方向洞位间距 (mm) + number: 编号 + category: 类别 + model: 型号 + """ + self._unilabos_state: MaterialPlateState = MaterialPlateState( + hole_spacing_x=24.0, + hole_spacing_y=24.0, + hole_diameter=20.0, + info="", + ) + # 创建4x4的洞位 + # TODO: 这里要改,对应不同形状 + holes = create_ordered_items_2d( + klass=MaterialHole, + num_items_x=4, + num_items_y=4, + dx=(size_x - 4 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中 + dy=(size_y - 4 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中 + dz=size_z, + item_dx=self._unilabos_state["hole_spacing_x"], + item_dy=self._unilabos_state["hole_spacing_y"], + size_x = 16, + size_y = 16, + size_z = 16, + ) + if fill: + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=holes, + category=category, + model=model, + ) + else: + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=ordered_items, + ordering=ordering, + category=category, + model=model, + ) + + def update_locations(self): + # TODO:调多次相加 + holes = create_ordered_items_2d( + klass=MaterialHole, + num_items_x=4, + num_items_y=4, + dx=(self._size_x - 3 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中 + dy=(self._size_y - 3 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中 + dz=self._size_z, + item_dx=self._unilabos_state["hole_spacing_x"], + item_dy=self._unilabos_state["hole_spacing_y"], + size_x = 1, + size_y = 1, + size_z = 1, + ) + for item, original_item in zip(holes.items(), self.children): + original_item.location = item[1].location + + +class PlateSlot(ResourceStack): + """板槽位类 - 1个槽上能堆放8个板,移板只能操作最上方的板""" + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + max_plates: int = 8, + category: str = "plate_slot", + model: Optional[str] = None + ): + """初始化板槽位 + + Args: + name: 槽位名称 + max_plates: 最大板数量 + category: 类别 + """ + super().__init__( + name=name, + direction="z", # Z方向堆叠 + resources=[], + ) + self.max_plates = max_plates + self.category = category + + def can_add_plate(self) -> bool: + """检查是否可以添加板""" + return len(self.children) < self.max_plates + + def add_plate(self, plate: MaterialPlate) -> None: + """添加料板""" + if not self.can_add_plate(): + raise ValueError(f"槽位 {self.name} 已满,无法添加更多板") + self.assign_child_resource(plate) + + def get_top_plate(self) -> MaterialPlate: + """获取最上方的板""" + if len(self.children) == 0: + raise ValueError(f"槽位 {self.name} 为空") + return cast(MaterialPlate, self.get_top_item()) + + def take_top_plate(self) -> MaterialPlate: + """取出最上方的板""" + top_plate = self.get_top_plate() + self.unassign_child_resource(top_plate) + return top_plate + + def can_access_for_picking(self) -> bool: + """检查是否可以进行取料操作(只有最上方的板能进行取料操作)""" + return len(self.children) > 0 + + def serialize(self) -> dict: + return { + **super().serialize(), + "max_plates": self.max_plates, + } + + +class ClipMagazineHole(Container): + """子弹夹洞位类""" + + def __init__( + self, + name: str, + diameter: float, + depth: float, + max_sheets: int = 100, + category: str = "clip_magazine_hole", + ): + """初始化子弹夹洞位 + + Args: + name: 洞位名称 + diameter: 洞直径 (mm) + depth: 洞深度 (mm) + max_sheets: 最大极片数量 + category: 类别 + """ + super().__init__( + name=name, + size_x=diameter, + size_y=diameter, + size_z=depth, + category=category, + ) + self.diameter = diameter + self.depth = depth + self.max_sheets = max_sheets + self._sheets: List[ElectrodeSheet] = [] + + def can_add_sheet(self, sheet: ElectrodeSheet) -> bool: + """检查是否可以添加极片""" + return (len(self._sheets) < self.max_sheets and + sheet.diameter <= self.diameter) + + def add_sheet(self, sheet: ElectrodeSheet) -> None: + """添加极片""" + if not self.can_add_sheet(sheet): + raise ValueError(f"无法向洞位 {self.name} 添加极片") + self._sheets.append(sheet) + + def take_sheet(self) -> ElectrodeSheet: + """取出极片""" + if len(self._sheets) == 0: + raise ValueError(f"洞位 {self.name} 没有极片") + return self._sheets.pop() + + def get_sheet_count(self) -> int: + """获取极片数量""" + return len(self._sheets) + + def serialize_state(self) -> Dict[str, Any]: + return { + "sheet_count": len(self._sheets), + "sheets": [sheet.serialize() for sheet in self._sheets], + } + +# TODO: 这个要改 +class ClipMagazine(Resource): + """子弹夹类 - 有6个洞位,每个洞位放多个极片""" + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + hole_spacing: float = 25.0, + max_sheets_per_hole: int = 100, + category: str = "clip_magazine", + model: Optional[str] = None, + ): + """初始化子弹夹 + + Args: + name: 子弹夹名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + hole_diameter: 洞直径 (mm) + hole_depth: 洞深度 (mm) + hole_spacing: 洞位间距 (mm) + max_sheets_per_hole: 每个洞位最大极片数量 + category: 类别 + model: 型号 + """ + # 创建6个洞位,排成2x3布局 + holes = create_ordered_items_2d( + klass=ClipMagazineHole, + num_items_x=3, + num_items_y=2, + dx=(size_x - 2 * hole_spacing) / 2, # 居中 + dy=(size_y - hole_spacing) / 2, # 居中 + dz=size_z - 0, + item_dx=hole_spacing, + item_dy=hole_spacing, + diameter=0, + depth=0, + ) + + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=holes, + category=category, + model=model, + ) + + self.hole_diameter = hole_diameter + self.hole_depth = hole_depth + self.max_sheets_per_hole = max_sheets_per_hole + + def serialize(self) -> dict: + return { + **super().serialize(), + "hole_diameter": self.hole_diameter, + "hole_depth": self.hole_depth, + "max_sheets_per_hole": self.max_sheets_per_hole, + } +#是一种类型注解,不用self +class BatteryState(TypedDict): + """电池状态字典""" + diameter: float + height: float + assembly_pressure: float + electrolyte_volume: float + electrolyte_name: str + +class Battery(Resource): + """电池类 - 可容纳极片""" + children: List[ElectrodeSheet] = [] + + def __init__( + self, + name: str, + size_x=1, + size_y=1, + size_z=1, + category: str = "battery", + ): + """初始化电池 + + Args: + name: 电池名称 + diameter: 直径 (mm) + height: 高度 (mm) + max_volume: 最大容量 (μL) + barcode: 二维码编号 + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=1, + size_y=1, + size_z=1, + category=category, + ) + self._unilabos_state: BatteryState = BatteryState( + diameter = 1.0, + height = 1.0, + assembly_pressure = 1.0, + electrolyte_volume = 1.0, + electrolyte_name = "DP001" + ) + + def add_electrolyte_with_bottle(self, bottle: Bottle) -> bool: + to_add_name = bottle._unilabos_state["electrolyte_name"] + if bottle.aspirate_electrolyte(10): + if self.add_electrolyte(to_add_name, 10): + pass + else: + bottle._unilabos_state["electrolyte_volume"] += 10 + + def set_electrolyte(self, name: str, volume: float) -> None: + """设置电解液信息""" + self._unilabos_state["electrolyte_name"] = name + self._unilabos_state["electrolyte_volume"] = volume + #这个应该没用,不会有加了后再加的事情 + def add_electrolyte(self, name: str, volume: float) -> bool: + """添加电解液信息""" + if name != self._unilabos_state["electrolyte_name"]: + return False + self._unilabos_state["electrolyte_volume"] += volume + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + +# 电解液作为属性放进去 + +class BatteryPressSlotState(TypedDict): + """电池状态字典""" + diameter: float =20.0 + depth: float = 4.0 + +class BatteryPressSlot(Resource): + """电池压制槽类 - 设备,可容纳一个电池""" + children: List[Battery] = [] + + def __init__( + self, + name: str = "BatteryPressSlot", + category: str = "battery_press_slot", + ): + """初始化电池压制槽 + + Args: + name: 压制槽名称 + diameter: 直径 (mm) + depth: 深度 (mm) + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=10, + size_y=12, + size_z=13, + category=category, + ) + self._unilabos_state: BatteryPressSlotState = BatteryPressSlotState() + + def has_battery(self) -> bool: + """检查是否有电池""" + return len(self.children) > 0 + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + + def assign_child_resource( + self, + resource: Battery, + location: Optional[Coordinate], + reassign: bool = True, + ): + """放置极片""" + # TODO: 让高京看下槽位只有一个电池时是否这么写。 + if self.has_battery(): + raise ValueError(f"槽位已含有一个电池,无法再放置其他电池") + super().assign_child_resource(resource, location, reassign) + + # 根据children的编号取物料对象。 + def get_battery_info(self, index: int) -> Battery: + return self.children[0] + +# TODO:这个移液枪架子看一下从哪继承 +class TipBox64State(TypedDict): + """电池状态字典""" + tip_diameter: float = 5.0 + tip_length: float = 50.0 + with_tips: bool = True + +class TipBox64(TipRack): + """64孔枪头盒类""" + + children: List[TipSpot] = [] + def __init__( + self, + name: str, + size_x: float = 127.8, + size_y: float = 85.5, + size_z: float = 60.0, + category: str = "tip_box_64", + model: Optional[str] = None, + ): + """初始化64孔枪头盒 + + Args: + name: 枪头盒名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + tip_diameter: 枪头直径 (mm) + tip_length: 枪头长度 (mm) + category: 类别 + model: 型号 + with_tips: 是否带枪头 + """ + from pylabrobot.resources.tip import Tip + + # 创建8x8=64个枪头位 + def make_tip(): + return Tip( + has_filter=False, + total_tip_length=20.0, + maximal_volume=1000, # 1mL + fitting_depth=8.0, + ) + + tip_spots = create_ordered_items_2d( + klass=TipSpot, + num_items_x=8, + num_items_y=8, + dx=8.0, + dy=8.0, + dz=0.0, + item_dx=9.0, + item_dy=9.0, + size_x=10, + size_y=10, + size_z=0.0, + make_tip=make_tip, + ) + self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate() + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=tip_spots, + category=category, + model=model, + with_tips=True, + ) + + + +class WasteTipBoxstate(TypedDict): + """"废枪头盒状态字典""" + max_tips: int = 100 + tip_count: int = 0 + +#枪头不是一次性的(同一溶液则反复使用),根据寄存器判断 +class WasteTipBox(Trash): + """废枪头盒类 - 100个枪头容量""" + + def __init__( + self, + name: str, + size_x: float = 127.8, + size_y: float = 85.5, + size_z: float = 60.0, + category: str = "waste_tip_box", + model: Optional[str] = None, + ): + """初始化废枪头盒 + + Args: + name: 废枪头盒名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + max_tips: 最大枪头容量 + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + category=category, + model=model, + ) + self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate() + + def add_tip(self) -> None: + """添加废枪头""" + if self._unilabos_state["tip_count"] >= self._unilabos_state["max_tips"]: + raise ValueError(f"废枪头盒 {self.name} 已满") + self._unilabos_state["tip_count"] += 1 + + def get_tip_count(self) -> int: + """获取枪头数量""" + return self._unilabos_state["tip_count"] + + def empty(self) -> None: + """清空废枪头盒""" + self._unilabos_state["tip_count"] = 0 + + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + + +class BottleRackState(TypedDict): + """ bottle_diameter: 瓶子直径 (mm) + bottle_height: 瓶子高度 (mm) + position_spacing: 位置间距 (mm)""" + bottle_diameter: float + bottle_height: float + name_to_index: dict + + + +class BottleRack(Resource): + """瓶架类 - 12个待配位置+12个已配位置""" + children: List[Bottle] = [] + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + category: str = "bottle_rack", + model: Optional[str] = None, + ): + """初始化瓶架 + + Args: + name: 瓶架名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + category=category, + model=model, + ) + # TODO: 添加瓶位坐标映射 + self.index_to_pos = { + 0: Coordinate.zero(), + 1: Coordinate(x=1, y=2, z=3) # 添加 + } + self.name_to_index = {} + self.name_to_pos = {} + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + + # TODO: 这里有些问题要重新写一下 + def assign_child_resource(self, resource: Bottle, location=Coordinate.zero(), reassign = True): + assert len(self.children) <= 12, "瓶架已满,无法添加更多瓶子" + index = len(self.children) + location = Coordinate(x=20 + (index % 4) * 15, y=20 + (index // 4) * 15, z=0) + self.name_to_pos[resource.name] = location + self.name_to_index[resource.name] = index + return super().assign_child_resource(resource, location, reassign) + + def assign_child_resource_by_index(self, resource: Bottle, index: int): + assert 0 <= index < 12, "无效的瓶子索引" + self.name_to_index[resource.name] = index + location = self.index_to_pos[index] + return super().assign_child_resource(resource, location) + + def unassign_child_resource(self, resource: Bottle): + super().unassign_child_resource(resource) + self.index_to_pos.pop(self.name_to_index.pop(resource.name, None), None) + + # def serialize(self): + # self.children.sort(key=lambda x: self.name_to_index.get(x.name, 0)) + # return super().serialize() + + +class BottleState(TypedDict): + diameter: float + height: float + electrolyte_name: str + electrolyte_volume: float + max_volume: float + +class Bottle(Resource): + """瓶子类 - 容纳电解液""" + + def __init__( + self, + name: str, + category: str = "bottle", + ): + """初始化瓶子 + + Args: + name: 瓶子名称 + diameter: 直径 (mm) + height: 高度 (mm) + max_volume: 最大体积 (μL) + barcode: 二维码 + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=1, + size_y=1, + size_z=1, + category=category, + ) + self._unilabos_state: BottleState = BottleState() + + def aspirate_electrolyte(self, volume: float) -> bool: + current_volume = self._unilabos_state["electrolyte_volume"] + assert current_volume > volume, f"Cannot aspirate {volume}μL, only {current_volume}μL available." + self._unilabos_state["electrolyte_volume"] -= volume + return True + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + +class CoincellDeck(Deck): + """纽扣电池组装工作站台面类""" + + def __init__( + self, + name: str = "coin_cell_deck", + size_x: float = 1620.0, # 3.66m + size_y: float = 1270.0, # 1.23m + size_z: float = 500.0, + origin: Coordinate = Coordinate(0, 0, 0), + category: str = "coin_cell_deck", + ): + """初始化纽扣电池组装工作站台面 + + Args: + name: 台面名称 + size_x: 长度 (mm) - 3.66m + size_y: 宽度 (mm) - 1.23m + size_z: 高度 (mm) + origin: 原点坐标 + category: 类别 + """ + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + origin=origin, + category=category, + ) + +#if __name__ == "__main__": +# # 转移极片的测试代码 +# deck = CoincellDeck("coin_cell_deck") +# ban_cao_wei = PlateSlot("ban_cao_wei", max_plates=8) +# deck.assign_child_resource(ban_cao_wei, Coordinate(x=0, y=0, z=0)) +# +# plate_1 = MaterialPlate("plate_1", 1,1,1, fill=True) +# for i, hole in enumerate(plate_1.children): +# sheet = ElectrodeSheet(f"hole_{i}_sheet_1") +# sheet._unilabos_state = { +# "diameter": 14, +# "info": "NMC", +# "mass": 5.0, +# "material_type": "positive_electrode", +# "thickness": 0.1 +# } +# hole._unilabos_state = { +# "depth": 1.0, +# "diameter": 14, +# "info": "", +# "max_sheets": 1 +# } +# hole.assign_child_resource(sheet, Coordinate.zero()) +# plate_1._unilabos_state = { +# "hole_spacing_x": 20.0, +# "hole_spacing_y": 20.0, +# "hole_diameter": 5, +# "info": "这是第一块料板" +# } +# plate_1.update_locations() +# ban_cao_wei.assign_child_resource(plate_1, Coordinate.zero()) +# # zi_dan_jia = ClipMagazine("zi_dan_jia", 1, 1, 1) +# # deck.assign_child_resource(ban_cao_wei, Coordinate(x=200, y=200, z=0)) +# +# from unilabos.resources.graphio import * +# A = tree_to_list([resource_plr_to_ulab(deck)]) +# with open("test.json", "w") as f: +# json.dump(A, f) +# +# +#def get_plate_with_14mm_hole(name=""): +# plate = MaterialPlate(name=name) +# for i in range(4): +# for j in range(4): +# hole = MaterialHole(f"{i+1}x{j+1}") +# hole._unilabos_state["diameter"] = 14 +# hole._unilabos_state["max_sheets"] = 1 +# plate.assign_child_resource(hole) +# return plate + +import json + +if __name__ == "__main__": + #electrode1 = BatteryPressSlot() + #print(electrode1.get_size_x()) + #print(electrode1.get_size_y()) + #print(electrode1.get_size_z()) + #jipian = ElectrodeSheet() + #jipian._unilabos_state["diameter"] = 18 + #print(jipian.serialize()) + #print(jipian.serialize_state()) + + deck = CoincellDeck(size_x=1000, + size_y=1000, + size_z=900) + + #liaopan = TipBox64(name="liaopan") + + #创建一个4*4的物料板 + liaopan1 = MaterialPlate(name="liaopan1", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) + #把物料板放到桌子上 + deck.assign_child_resource(liaopan1, Coordinate(x=0, y=0, z=0)) + #创建一个极片 + for i in range(16): + jipian = ElectrodeSheet(name=f"jipian1_{i}", size_x= 12, size_y=12, size_z=0.1) + liaopan1.children[i].assign_child_resource(jipian, location=None) +# + #创建一个4*4的物料板 + liaopan2 = MaterialPlate(name="liaopan2", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) + #把物料板放到桌子上 + deck.assign_child_resource(liaopan2, Coordinate(x=500, y=0, z=0)) + + #创建一个4*4的物料板 + liaopan3 = MaterialPlate(name="电池料盘", size_x=120.8, size_y=160.5, size_z=10.0, fill=True) + #把物料板放到桌子上 + deck.assign_child_resource(liaopan3, Coordinate(x=100, y=100, z=0)) + + + + #liaopan.children[3].assign_child_resource(jipian, location=None) + print(deck) + + + from unilabos.resources.graphio import convert_resources_from_type + from unilabos.config.config import BasicConfig + BasicConfig.ak = "4d5ce6ae-7234-4639-834e-93899b9caf94" + BasicConfig.sk = "505d3b0a-620e-459a-9905-1efcffce382a" + from unilabos.app.web.client import http_client + + resources = convert_resources_from_type([deck], [Resource]) + json.dump({"nodes": resources, "links": []}, open("button_battery_station_resources_unilab.json", "w"), indent=2) + + + #print(resources) + http_client.remote_addr = "https://uni-lab.test.bohrium.com/api/v1" + + http_client.resource_add(resources) \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/coin_cell_assembly.py new file mode 100644 index 0000000..750a34f --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/coin_cell_assembly.py @@ -0,0 +1,1172 @@ +import csv +import json +import os +import threading +import time +from datetime import datetime +from typing import Any, Dict, Optional +from pylabrobot.resources import Resource as PLRResource +from unilabos_msgs.msg import Resource +from unilabos.device_comms.modbus_plc.client import ModbusTcpClient +from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import MaterialHole, MaterialPlate +from unilabos.devices.workstation.workstation_base import WorkstationBase +from unilabos.device_comms.modbus_plc.client import TCPClient, ModbusNode, PLCWorkflow, ModbusWorkflow, WorkflowAction, BaseClient +from unilabos.device_comms.modbus_plc.modbus import DeviceType, Base as ModbusNodeBase, DataType, WorderOrder +from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import * +from unilabos.ros.nodes.base_device_node import ROS2DeviceNode, BaseROS2DeviceNode +from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode + +#构建物料系统 + +class CoinCellAssemblyWorkstation(WorkstationBase): + def __init__( + self, + station_resource: CoincellDeck, + address: str = "192.168.1.20", + port: str = "502", + debug_mode: bool = True, + *args, + **kwargs, + ): + super().__init__( + #桌子 + station_resource=station_resource, + *args, + **kwargs, + ) + self.debug_mode = debug_mode + self.station_resource = station_resource + """ 连接初始化 """ + modbus_client = TCPClient(addr=address, port=port) + print("modbus_client", modbus_client) + if not debug_mode: + modbus_client.client.connect() + count = 100 + while count >0: + count -=1 + if modbus_client.client.is_socket_open(): + break + time.sleep(2) + if not modbus_client.client.is_socket_open(): + raise ValueError('modbus tcp connection failed') + else: + print("测试模式,跳过连接") + + """ 工站的配置 """ + self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_a.csv')) + self.client = modbus_client.register_node_list(self.nodes) + self.success = False + self.allow_data_read = False #允许读取函数运行标志位 + self.csv_export_thread = None + self.csv_export_running = False + self.csv_export_file = None + self.coin_num_N = 0 #已组装电池数量 + #创建一个物料台面,包含两个极片板 + #self.deck = create_a_coin_cell_deck() + + #self._ros_node.update_resource(self.deck) + + #ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ + # "resources": [self.deck] + #}) + + + def post_init(self, ros_node: ROS2WorkstationNode): + self._ros_node = ros_node + #self.deck = create_a_coin_cell_deck() + ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ + "resources": [self.station_resource] + }) + + # 批量操作在这里写 + async def change_hole_sheet_to_2(self, hole: MaterialHole): + hole._unilabos_state["max_sheets"] = 2 + return await self._ros_node.update_resource(hole) + + + async def fill_plate(self): + plate_1: MaterialPlate = self.station_resource.children[0].children[0] + #plate_1 + return await self._ros_node.update_resource(plate_1) + + #def run_assembly(self, wf_name: str, resource: PLRResource, params: str = "\{\}"): + # """启动工作流""" + # self.current_workflow_status = WorkflowStatus.RUNNING + # logger.info(f"工作站 {self.device_id} 启动工作流: {wf_name}") +# + # # TODO: 实现工作流逻辑 +# + # anode_sheet = self.deck.get_resource("anode_sheet") + + """ Action逻辑代码 """ + def _sys_start_cmd(self, cmd=None): + """设备启动命令 (可读写)""" + if cmd is not None: # 写入模式 + self.success = False + node = self.client.use_node('COIL_SYS_START_CMD') + ret = node.write(cmd) + print(ret) + self.success = True + return self.success + else: # 读取模式 + cmd_feedback, read_err = self.client.use_node('COIL_SYS_START_CMD').read(1) + return cmd_feedback[0] + + def _sys_stop_cmd(self, cmd=None): + """设备停止命令 (可读写)""" + if cmd is not None: # 写入模式 + self.success = False + node = self.client.use_node('COIL_SYS_STOP_CMD') + node.write(cmd) + self.success = True + return self.success + else: # 读取模式 + cmd_feedback, read_err = self.client.use_node('COIL_SYS_STOP_CMD').read(1) + return cmd_feedback[0] + + def _sys_reset_cmd(self, cmd=None): + """设备复位命令 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_SYS_RESET_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_SYS_RESET_CMD').read(1) + return cmd_feedback[0] + + def _sys_hand_cmd(self, cmd=None): + """手动模式命令 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_SYS_HAND_CMD').write(cmd) + self.success = True + print("步骤0") + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_SYS_HAND_CMD').read(1) + return cmd_feedback[0] + + def _sys_auto_cmd(self, cmd=None): + """自动模式命令 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_SYS_AUTO_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_SYS_AUTO_CMD').read(1) + return cmd_feedback[0] + + def _sys_init_cmd(self, cmd=None): + """初始化命令 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_SYS_INIT_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_SYS_INIT_CMD').read(1) + return cmd_feedback[0] + + def _unilab_send_msg_succ_cmd(self, cmd=None): + """UNILAB发送配方完毕 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_UNILAB_SEND_MSG_SUCC_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_UNILAB_SEND_MSG_SUCC_CMD').read(1) + return cmd_feedback[0] + + def _unilab_rec_msg_succ_cmd(self, cmd=None): + """UNILAB接收测试电池数据完毕 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_UNILAB_REC_MSG_SUCC_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_UNILAB_REC_MSG_SUCC_CMD').read(1) + return cmd_feedback + + + # ====================== 命令类指令(REG_x_) ====================== + def _unilab_send_msg_electrolyte_num(self, num=None): + """UNILAB写电解液使用瓶数(可读写)""" + if num is not None: + self.success = False + ret = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').write(num) + print(ret) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').read(1) + return cmd_feedback[0] + + def _unilab_send_msg_electrolyte_use_num(self, use_num=None): + """UNILAB写单次电解液使用瓶数(可读写)""" + if use_num is not None: + self.success = False + self.client.use_node('REG_MSG_ELECTROLYTE_USE_NUM').write(use_num) + self.success = True + return self.success + else: + return False + + def _unilab_send_msg_assembly_type(self, num=None): + """UNILAB写组装参数""" + if num is not None: + self.success = False + self.client.use_node('REG_MSG_ASSEMBLY_TYPE').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ASSEMBLY_TYPE').read(1) + return cmd_feedback[0] + + def _unilab_send_msg_electrolyte_vol(self, vol=None): + """UNILAB写电解液吸取量参数""" + if vol is not None: + self.success = False + self.client.use_node('REG_MSG_ELECTROLYTE_VOLUME').write(vol, data_type=DataType.FLOAT32, word_order=WorderOrder.LITTLE) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_VOLUME').read(2, word_order=WorderOrder.LITTLE) + return cmd_feedback[0] + + def _unilab_send_msg_assembly_pressure(self, vol=None): + """UNILAB写电池压制力""" + if vol is not None: + self.success = False + self.client.use_node('REG_MSG_ASSEMBLY_PRESSURE').write(vol, data_type=DataType.FLOAT32, word_order=WorderOrder.LITTLE) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ASSEMBLY_PRESSURE').read(2, word_order=WorderOrder.LITTLE) + return cmd_feedback[0] + + # ==================== 0905新增内容(COIL_x_STATUS) ==================== + def _unilab_send_electrolyte_bottle_num(self, num=None): + """UNILAB发送电解液瓶数完毕""" + if num is not None: + self.success = False + self.client.use_node('UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM').read(1) + return cmd_feedback[0] + + def _unilab_rece_electrolyte_bottle_num(self, num=None): + """设备请求接受电解液瓶数""" + if num is not None: + self.success = False + self.client.use_node('UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM').read(1) + return cmd_feedback[0] + + def _reg_msg_electrolyte_num(self, num=None): + """电解液已使用瓶数""" + if num is not None: + self.success = False + self.client.use_node('REG_MSG_ELECTROLYTE_NUM').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').read(1) + return cmd_feedback[0] + + def _reg_data_electrolyte_use_num(self, num=None): + """单瓶电解液完成组装数""" + if num is not None: + self.success = False + self.client.use_node('REG_DATA_ELECTROLYTE_USE_NUM').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_USE_NUM').read(1) + return cmd_feedback[0] + + def _unilab_send_finished_cmd(self, num=None): + """Unilab发送已知一组组装完成信号""" + if num is not None: + self.success = False + self.client.use_node('UNILAB_SEND_FINISHED_CMD').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('UNILAB_SEND_FINISHED_CMD').read(1) + return cmd_feedback[0] + + def _unilab_rece_finished_cmd(self, num=None): + """Unilab接收已知一组组装完成信号""" + if num is not None: + self.success = False + self.client.use_node('UNILAB_RECE_FINISHED_CMD').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('UNILAB_RECE_FINISHED_CMD').read(1) + return cmd_feedback[0] + + + + # ==================== 状态类属性(COIL_x_STATUS) ==================== + def _sys_start_status(self) -> bool: + """设备启动中( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_START_STATUS').read(1) + return status[0] + + def _sys_stop_status(self) -> bool: + """设备停止中( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_STOP_STATUS').read(1) + return status[0] + + def _sys_reset_status(self) -> bool: + """设备复位中( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_RESET_STATUS').read(1) + return status[0] + + def _sys_init_status(self) -> bool: + """设备初始化完成( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_INIT_STATUS').read(1) + return status[0] + + # 查找资源 + def modify_deck_name(self, resource_name: str): + # figure_res = self._ros_node.resource_tracker.figure_resource({"name": resource_name}) + # print(f"!!! figure_res: {type(figure_res)}") + self.station_resource.children[1] + return + + @property + def sys_status(self) -> str: + if self.debug_mode: + return "设备调试模式" + if self._sys_start_status(): + return "设备启动中" + elif self._sys_stop_status(): + return "设备停止中" + elif self._sys_reset_status(): + return "设备复位中" + elif self._sys_init_status(): + return "设备初始化中" + else: + return "未知状态" + + def _sys_hand_status(self) -> bool: + """设备手动模式( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_HAND_STATUS').read(1) + return status[0] + + def _sys_auto_status(self) -> bool: + """设备自动模式( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_AUTO_STATUS').read(1) + return status[0] + + @property + def sys_mode(self) -> str: + if self.debug_mode: + return "设备调试模式" + if self._sys_hand_status(): + return "设备手动模式" + elif self._sys_auto_status(): + return "设备自动模式" + else: + return "未知模式" + + @property + def request_rec_msg_status(self) -> bool: + """设备请求接受配方( BOOL)""" + if self.debug_mode: + return True + status, read_err = self.client.use_node('COIL_REQUEST_REC_MSG_STATUS').read(1) + return status[0] + + @property + def request_send_msg_status(self) -> bool: + """设备请求发送测试数据( BOOL)""" + if self.debug_mode: + return True + status, read_err = self.client.use_node('COIL_REQUEST_SEND_MSG_STATUS').read(1) + return status[0] + + # ======================= 其他属性(特殊功能) ======================== + ''' + @property + def warning_1(self) -> bool: + status, read_err = self.client.use_node('COIL_WARNING_1').read(1) + return status[0] + ''' + # ===================== 生产数据区 ====================== + + @property + def data_assembly_coin_cell_num(self) -> int: + """已完成电池数量 (INT16)""" + if self.debug_mode: + return 0 + num, read_err = self.client.use_node('REG_DATA_ASSEMBLY_COIN_CELL_NUM').read(1) + return num + + @property + def data_assembly_time(self) -> float: + """单颗电池组装时间 (秒, REAL/FLOAT32)""" + if self.debug_mode: + return 0 + time, read_err = self.client.use_node('REG_DATA_ASSEMBLY_PER_TIME').read(2, word_order=WorderOrder.LITTLE) + return time + + @property + def data_open_circuit_voltage(self) -> float: + """开路电压值 (FLOAT32)""" + if self.debug_mode: + return 0 + vol, read_err = self.client.use_node('REG_DATA_OPEN_CIRCUIT_VOLTAGE').read(2, word_order=WorderOrder.LITTLE) + return vol + + @property + def data_axis_x_pos(self) -> float: + """分液X轴当前位置 (FLOAT32)""" + if self.debug_mode: + return 0 + pos, read_err = self.client.use_node('REG_DATA_AXIS_X_POS').read(2, word_order=WorderOrder.LITTLE) + return pos + + @property + def data_axis_y_pos(self) -> float: + """分液Y轴当前位置 (FLOAT32)""" + if self.debug_mode: + return 0 + pos, read_err = self.client.use_node('REG_DATA_AXIS_Y_POS').read(2, word_order=WorderOrder.LITTLE) + return pos + + @property + def data_axis_z_pos(self) -> float: + """分液Z轴当前位置 (FLOAT32)""" + if self.debug_mode: + return 0 + pos, read_err = self.client.use_node('REG_DATA_AXIS_Z_POS').read(2, word_order=WorderOrder.LITTLE) + return pos + + @property + def data_pole_weight(self) -> float: + """当前电池正极片称重数据 (FLOAT32)""" + if self.debug_mode: + return 0 + weight, read_err = self.client.use_node('REG_DATA_POLE_WEIGHT').read(2, word_order=WorderOrder.LITTLE) + return weight + + @property + def data_assembly_pressure(self) -> int: + """当前电池压制力 (INT16)""" + if self.debug_mode: + return 0 + pressure, read_err = self.client.use_node('REG_DATA_ASSEMBLY_PRESSURE').read(1) + return pressure + + @property + def data_electrolyte_volume(self) -> int: + """当前电解液加注量 (INT16)""" + if self.debug_mode: + return 0 + vol, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_VOLUME').read(1) + return vol + + @property + def data_coin_num(self) -> int: + """当前电池数量 (INT16)""" + if self.debug_mode: + return 0 + num, read_err = self.client.use_node('REG_DATA_COIN_NUM').read(1) + return num + + @property + def data_coin_cell_code(self) -> str: + """电池二维码序列号 (STRING)""" + try: + # 尝试不同的字节序读取 + code_little, read_err = self.client.use_node('REG_DATA_COIN_CELL_CODE').read(10, word_order=WorderOrder.LITTLE) + print(code_little) + clean_code = code_little[-8:][::-1] + return clean_code + except Exception as e: + print(f"读取电池二维码失败: {e}") + return "N/A" + + + @property + def data_electrolyte_code(self) -> str: + try: + # 尝试不同的字节序读取 + code_little, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_CODE').read(10, word_order=WorderOrder.LITTLE) + print(code_little) + clean_code = code_little[-8:][::-1] + return clean_code + except Exception as e: + print(f"读取电解液二维码失败: {e}") + return "N/A" + + # ===================== 环境监控区 ====================== + @property + def data_glove_box_pressure(self) -> float: + """手套箱压力 (bar, FLOAT32)""" + if self.debug_mode: + return 0 + status, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_PRESSURE').read(2, word_order=WorderOrder.LITTLE) + return status + + @property + def data_glove_box_o2_content(self) -> float: + """手套箱氧含量 (ppm, FLOAT32)""" + if self.debug_mode: + return 0 + value, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_O2_CONTENT').read(2, word_order=WorderOrder.LITTLE) + return value + + @property + def data_glove_box_water_content(self) -> float: + """手套箱水含量 (ppm, FLOAT32)""" + if self.debug_mode: + return 0 + value, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_WATER_CONTENT').read(2, word_order=WorderOrder.LITTLE) + return value + +# @property +# def data_stack_vision_code(self) -> int: +# """物料堆叠复检图片编码 (INT16)""" +# if self.debug_mode: +# return 0 +# code, read_err = self.client.use_node('REG_DATA_STACK_VISON_CODE').read(1) +# #code, _ = self.client.use_node('REG_DATA_STACK_VISON_CODE').read(1).type +# print(f"读取物料堆叠复检图片编码", {code}, "error", type(code)) +# #print(code.type) +# # print(read_err) +# return int(code) + + def func_pack_device_init(self): + #切换手动模式 + print("切换手动模式") + self._sys_hand_cmd(True) + time.sleep(1) + while (self._sys_hand_status()) == False: + print("waiting for hand_cmd") + time.sleep(1) + #设备初始化 + self._sys_init_cmd(True) + time.sleep(1) + #sys_init_status为bool值,不加括号 + while (self._sys_init_status())== False: + print("waiting for init_cmd") + time.sleep(1) + #手动按钮置回False + self._sys_hand_cmd(False) + time.sleep(1) + while (self._sys_hand_cmd()) == True: + print("waiting for hand_cmd to False") + time.sleep(1) + #初始化命令置回False + self._sys_init_cmd(False) + time.sleep(1) + while (self._sys_init_cmd()) == True: + print("waiting for init_cmd to False") + time.sleep(1) + + def func_pack_device_auto(self): + #切换自动 + print("切换自动模式") + self._sys_auto_cmd(True) + time.sleep(1) + while (self._sys_auto_status()) == False: + print("waiting for auto_status") + time.sleep(1) + #自动按钮置False + self._sys_auto_cmd(False) + time.sleep(1) + while (self._sys_auto_cmd()) == True: + print("waiting for auto_cmd") + time.sleep(1) + + def func_pack_device_start(self): + #切换自动 + print("启动") + self._sys_start_cmd(True) + time.sleep(1) + while (self._sys_start_status()) == False: + print("waiting for start_status") + time.sleep(1) + #自动按钮置False + self._sys_start_cmd(False) + time.sleep(1) + while (self._sys_start_cmd()) == True: + print("waiting for start_cmd") + time.sleep(1) + + def func_pack_send_bottle_num(self, bottle_num): + bottle_num = int(bottle_num) + #发送电解液平台数 + print("启动") + while (self._unilab_rece_electrolyte_bottle_num()) == False: + print("waiting for rece_electrolyte_bottle_num to True") + # self.client.use_node('8520').write(True) + time.sleep(1) + #发送电解液瓶数为2 + self._reg_msg_electrolyte_num(bottle_num) + time.sleep(1) + #完成信号置True + self._unilab_send_electrolyte_bottle_num(True) + time.sleep(1) + #检测到依华已接收 + while (self._unilab_rece_electrolyte_bottle_num()) == True: + print("waiting for rece_electrolyte_bottle_num to False") + time.sleep(1) + #完成信号置False + self._unilab_send_electrolyte_bottle_num(False) + time.sleep(1) + #自动按钮置False + + + # 下发参数 + #def func_pack_send_msg_cmd(self, elec_num: int, elec_use_num: int, elec_vol: float, assembly_type: int, assembly_pressure: int) -> bool: + # """UNILAB写参数""" + # while (self.request_rec_msg_status) == False: + # print("wait for res_msg") + # time.sleep(1) + # self.success = False + # self._unilab_send_msg_electrolyte_num(elec_num) + # time.sleep(1) + # self._unilab_send_msg_electrolyte_use_num(elec_use_num) + # time.sleep(1) + # self._unilab_send_msg_electrolyte_vol(elec_vol) + # time.sleep(1) + # self._unilab_send_msg_assembly_type(assembly_type) + # time.sleep(1) + # self._unilab_send_msg_assembly_pressure(assembly_pressure) + # time.sleep(1) + # self._unilab_send_msg_succ_cmd(True) + # time.sleep(1) + # self._unilab_send_msg_succ_cmd(False) + # #将允许读取标志位置True + # self.allow_data_read = True + # self.success = True + # return self.success + + def func_pack_send_msg_cmd(self, elec_use_num, elec_vol, assembly_type, assembly_pressure) -> bool: + """UNILAB写参数""" + while (self.request_rec_msg_status) == False: + print("wait for request_rec_msg_status to True") + time.sleep(1) + self.success = False + #self._unilab_send_msg_electrolyte_num(elec_num) + #设置平行样数目 + self._unilab_send_msg_electrolyte_use_num(elec_use_num) + time.sleep(1) + #发送电解液加注量 + self._unilab_send_msg_electrolyte_vol(elec_vol) + time.sleep(1) + #发送电解液组装类型 + self._unilab_send_msg_assembly_type(assembly_type) + time.sleep(1) + #发送电池压制力 + self._unilab_send_msg_assembly_pressure(assembly_pressure) + time.sleep(1) + self._unilab_send_msg_succ_cmd(True) + time.sleep(1) + while (self.request_rec_msg_status) == True: + print("wait for request_rec_msg_status to False") + time.sleep(1) + self._unilab_send_msg_succ_cmd(False) + #将允许读取标志位置True + self.allow_data_read = True + self.success = True + return self.success + + def func_pack_get_msg_cmd(self, file_path: str="D:\\coin_cell_data") -> bool: + """UNILAB读参数""" + while self.request_send_msg_status == False: + print("waiting for send_read_msg_status to True") + time.sleep(1) + data_open_circuit_voltage = self.data_open_circuit_voltage + data_pole_weight = self.data_pole_weight + data_assembly_time = self.data_assembly_time + data_assembly_pressure = self.data_assembly_pressure + data_electrolyte_volume = self.data_electrolyte_volume + data_coin_num = self.data_coin_num + data_electrolyte_code = self.data_electrolyte_code + data_coin_cell_code = self.data_coin_cell_code + print("data_open_circuit_voltage", data_open_circuit_voltage) + print("data_pole_weight", data_pole_weight) + print("data_assembly_time", data_assembly_time) + print("data_assembly_pressure", data_assembly_pressure) + print("data_electrolyte_volume", data_electrolyte_volume) + print("data_coin_num", data_coin_num) + print("data_electrolyte_code", data_electrolyte_code) + print("data_coin_cell_code", data_coin_cell_code) + #接收完信息后,读取完毕标志位置True + liaopan3 = self.station_resource.get_resource("\u7535\u6c60\u6599\u76d8") + #把物料解绑后放到另一盘上 + battery = ElectrodeSheet(name=f"battery_{self.coin_num_N}", size_x=14, size_y=14, size_z=2) + battery._unilabos_state = { + "electrolyte_name": data_coin_cell_code, + "data_electrolyte_code": data_electrolyte_code, + "open_circuit_voltage": data_open_circuit_voltage, + "assembly_pressure": data_assembly_pressure, + "electrolyte_volume": data_electrolyte_volume + } + liaopan3.children[self.coin_num_N].assign_child_resource(battery, location=None) + #print(jipian2.parent) + ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ + "resources": [self.station_resource] + }) + + + self._unilab_rec_msg_succ_cmd(True) + time.sleep(1) + #等待允许读取标志位置False + while self.request_send_msg_status == True: + print("waiting for send_msg_status to False") + time.sleep(1) + self._unilab_rec_msg_succ_cmd(False) + time.sleep(1) + #将允许读取标志位置True + time_date = datetime.now().strftime("%Y%m%d") + #秒级时间戳用于标记每一行电池数据 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + #生成输出文件的变量 + self.csv_export_file = os.path.join(file_path, f"date_{time_date}.csv") + #将数据存入csv文件 + if not os.path.exists(self.csv_export_file): + #创建一个表头 + with open(self.csv_export_file, 'w', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow([ + 'Time', 'open_circuit_voltage', 'pole_weight', + 'assembly_time', 'assembly_pressure', 'electrolyte_volume', + 'coin_num', 'electrolyte_code', 'coin_cell_code' + ]) + #立刻写入磁盘 + csvfile.flush() + #开始追加电池信息 + with open(self.csv_export_file, 'a', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow([ + timestamp, data_open_circuit_voltage, data_pole_weight, + data_assembly_time, data_assembly_pressure, data_electrolyte_volume, + data_coin_num, data_electrolyte_code, data_coin_cell_code + ]) + #立刻写入磁盘 + csvfile.flush() + self.success = True + return self.success + + + + def func_pack_send_finished_cmd(self) -> bool: + """UNILAB写参数""" + while (self._unilab_rece_finished_cmd()) == False: + print("wait for rece_finished_cmd to True") + time.sleep(1) + self.success = False + self._unilab_send_finished_cmd(True) + time.sleep(1) + while (self._unilab_rece_finished_cmd()) == True: + print("wait for rece_finished_cmd to False") + time.sleep(1) + self._unilab_send_finished_cmd(False) + #将允许读取标志位置True + self.success = True + return self.success + + + + def func_allpack_cmd(self, elec_num, elec_use_num, elec_vol:int=50, assembly_type:int=7, assembly_pressure:int=4200, file_path: str="D:\\coin_cell_data") -> bool: + elec_num, elec_use_num, elec_vol, assembly_type, assembly_pressure = int(elec_num), int(elec_use_num), int(elec_vol), int(assembly_type), int(assembly_pressure) + summary_csv_file = os.path.join(file_path, "duandian.csv") + # 如果断点文件存在,先读取之前的进度 + if os.path.exists(summary_csv_file): + read_status_flag = True + with open(summary_csv_file, 'r', newline='', encoding='utf-8') as csvfile: + reader = csv.reader(csvfile) + header = next(reader) # 跳过标题行 + data_row = next(reader) # 读取数据行 + if len(data_row) >= 2: + elec_num_r = int(data_row[0]) + elec_use_num_r = int(data_row[1]) + elec_num_N = int(data_row[2]) + elec_use_num_N = int(data_row[3]) + coin_num_N = int(data_row[4]) + if elec_num_r == elec_num and elec_use_num_r == elec_use_num: + print("断点文件与当前任务匹配,继续") + else: + print("断点文件中elec_num、elec_use_num与当前任务不匹配,请检查任务下发参数或修改断点文件") + return False + print(f"从断点文件读取进度: elec_num_N={elec_num_N}, elec_use_num_N={elec_use_num_N}, coin_num_N={coin_num_N}") + + else: + read_status_flag = False + print("未找到断点文件,从头开始") + elec_num_N = 0 + elec_use_num_N = 0 + coin_num_N = 0 + for i in range(20): + print(f"剩余电解液瓶数: {elec_num}, 已组装电池数: {elec_use_num}") + print(f"剩余电解液瓶数: {type(elec_num)}, 已组装电池数: {type(elec_use_num)}") + print(f"剩余电解液瓶数: {type(int(elec_num))}, 已组装电池数: {type(int(elec_use_num))}") + + #如果是第一次运行,则进行初始化、切换自动、启动, 如果是断点重启则跳过。 + if read_status_flag == False: + pass + #初始化 + #self.func_pack_device_init() + #切换自动 + #self.func_pack_device_auto() + #启动,小车收回 + #self.func_pack_device_start() + #发送电解液瓶数量,启动搬运,多搬运没事 + #self.func_pack_send_bottle_num(elec_num) + last_i = elec_num_N + last_j = elec_use_num_N + for i in range(last_i, elec_num): + print(f"开始第{last_i+i+1}瓶电解液的组装") + #第一个循环从上次断点继续,后续循环从0开始 + j_start = last_j if i == last_i else 0 + self.func_pack_send_msg_cmd(elec_use_num-j_start, elec_vol, assembly_type, assembly_pressure) + + for j in range(j_start, elec_use_num): + print(f"开始第{last_i+i+1}瓶电解液的第{j+j_start+1}个电池组装") + #读取电池组装数据并存入csv + self.func_pack_get_msg_cmd(file_path) + time.sleep(1) + # TODO:读完再将电池数加一还是进入循环就将电池数加一需要考虑 + + + + # 生成断点文件 + # 生成包含elec_num_N、coin_num_N、timestamp的CSV文件 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + with open(summary_csv_file, 'w', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow(['elec_num','elec_use_num', 'elec_num_N', 'elec_use_num_N', 'coin_num_N', 'timestamp']) + writer.writerow([elec_num, elec_use_num, elec_num_N, elec_use_num_N, coin_num_N, timestamp]) + csvfile.flush() + coin_num_N += 1 + self.coin_num_N = coin_num_N + elec_use_num_N += 1 + elec_num_N += 1 + elec_use_num_N = 0 + + #循环正常结束,则删除断点文件 + os.remove(summary_csv_file) + #全部完成后等待依华发送完成信号 + self.func_pack_send_finished_cmd() + + + def func_pack_device_stop(self) -> bool: + """打包指令:设备停止""" + for i in range(3): + time.sleep(2) + print(f"输出{i}") + #print("_sys_hand_cmd", self._sys_hand_cmd()) + #time.sleep(1) + #print("_sys_hand_status", self._sys_hand_status()) + #time.sleep(1) + #print("_sys_init_cmd", self._sys_init_cmd()) + #time.sleep(1) + #print("_sys_init_status", self._sys_init_status()) + #time.sleep(1) + #print("_sys_auto_status", self._sys_auto_status()) + #time.sleep(1) + #print("data_axis_y_pos", self.data_axis_y_pos) + #time.sleep(1) + #self.success = False + #with open('action_device_stop.json', 'r', encoding='utf-8') as f: + # action_json = json.load(f) + #self.client.execute_procedure_from_json(action_json) + #self.success = True + #return self.success + + def fun_wuliao_test(self) -> bool: + #找到data_init中构建的2个物料盘 + liaopan3 = self.station_resource.get_resource("\u7535\u6c60\u6599\u76d8") + for i in range(16): + battery = ElectrodeSheet(name=f"battery_{i}", size_x=16, size_y=16, size_z=2) + battery._unilabos_state = { + "diameter": 20.0, + "height": 20.0, + "assembly_pressure": i, + "electrolyte_volume": 20.0, + "electrolyte_name": f"DP{i}" + } + liaopan3.children[i].assign_child_resource(battery, location=None) + + ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ + "resources": [self.station_resource] + }) + time.sleep(4) + # 数据读取与输出 + def func_read_data_and_output(self, file_path: str="D:\\coin_cell_data"): + # 检查CSV导出是否正在运行,已运行则跳出,防止同时启动两个while循环 + if self.csv_export_running: + return False, "读取已在运行中" + + #若不存在该目录则创建 + if not os.path.exists(file_path): + os.makedirs(file_path) + print(f"创建目录: {file_path}") + + # 只要允许读取标志位为true,就持续运行该函数,直到触发停止条件 + while self.allow_data_read: + + #函数运行标志位,确保只同时启动一个导出函数 + self.csv_export_running = True + + #等待接收结果标志位置True + while self.request_send_msg_status == False: + print("waiting for send_msg_status to True") + time.sleep(1) + #日期时间戳用于按天存放csv文件 + time_date = datetime.now().strftime("%Y%m%d") + #秒级时间戳用于标记每一行电池数据 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + #生成输出文件的变量 + self.csv_export_file = os.path.join(file_path, f"date_{time_date}.csv") + + #接收信息 + data_open_circuit_voltage = self.data_open_circuit_voltage + data_pole_weight = self.data_pole_weight + data_assembly_time = self.data_assembly_time + data_assembly_pressure = self.data_assembly_pressure + data_electrolyte_volume = self.data_electrolyte_volume + data_coin_num = self.data_coin_num + data_electrolyte_code = self.data_electrolyte_code + data_coin_cell_code = self.data_coin_cell_code + # 电解液瓶位置 + elec_bottle_site = 2 + # 极片夹取位置(应当通过寄存器读光标) + Pos_elec_site = 0 + Al_elec_site = 0 + Gasket_site = 0 + + #接收完信息后,读取完毕标志位置True + self._unilab_rec_msg_succ_cmd()# = True + #等待允许读取标志位置False + while self.request_send_msg_status == True: + print("waiting for send_msg_status to False") + time.sleep(1) + self._unilab_rec_msg_succ_cmd()# = False + + #此处操作物料信息(如果中途报错停止,如何) + #报错怎么办(加个判断标志位,如果发生错误,则根据停止位置扣除物料) + #根据物料光标判断取哪个物料(人工摆盘,电解液瓶,移液枪头都有光标位置,寄存器读即可) + + #物料读取操作写在这里 + #在这里进行物料调取 + #转移物料瓶,elec_bottle_site对应第几瓶电解液(从依华寄存器读取) + # transfer_bottle(deck, elec_bottle_site) + # #找到电解液瓶的对象 + # electrolyte_rack = deck.get_resource("electrolyte_rack") + # pending_positions = electrolyte_rack.get_pending_positions()[elec_bottle_site] + # # TODO: 瓶子取液体操作需要加入 +# +# + # #找到压制工站对应的对象 + # battery_press_slot = deck.get_resource("battery_press_1") + # #创建一个新电池 + # test_battery = Battery( + # name=f"test_battery_{data_coin_num}", + # diameter=20.0, # 与压制槽直径匹配 + # height=3.0, # 电池高度 + # max_volume=100.0, # 100μL容量 + # barcode=data_coin_cell_code, # 电池条码 + # ) + # if battery_press_slot.has_battery(): + # return False, "压制工站已有电池,无法放置新电池" + # #在压制位放置电池 + # battery_press_slot.place_battery(test_battery) + # #从第一个子弹夹中取料 + # clip_magazine_1_hole = self.deck.get_resource("clip_magazine_1").get_item(Pos_elec_site) + # clip_magazine_2_hole = self.deck.get_resource("clip_magazine_2").get_item(Al_elec_site) + # clip_magazine_3_hole = self.deck.get_resource("clip_magazine_3").get_item(Gasket_site) + # + # if clip_magazine_1_hole.get_sheet_count() > 0: # 检查洞位是否有极片 + # electrode_sheet_1 = clip_magazine_1_hole.take_sheet() # 从洞位取出极片 + # test_battery.add_electrode_sheet(electrode_sheet_1) # 添加到电池中 + # print(f"已将极片 {electrode_sheet_1.name} 从子弹夹转移到电池") + # else: + # print("子弹夹洞位0没有极片") +# + # if clip_magazine_2_hole.get_sheet_count() > 0: # 检查洞位是否有极片 + # electrode_sheet_2 = clip_magazine_2_hole.take_sheet() # 从洞位取出极片 + # test_battery.add_electrode_sheet(electrode_sheet_2) # 添加到电池中 + # print(f"已将极片 {electrode_sheet_2.name} 从子弹夹转移到电池") + # else: + # print("子弹夹洞位0没有极片") +# + # if clip_magazine_3_hole.get_sheet_count() > 0: # 检查洞位是否有极片 + # electrode_sheet_3 = clip_magazine_3_hole.take_sheet() # 从洞位取出极片 + # test_battery.add_electrode_sheet(electrode_sheet_3) # 添加到电池中 + # print(f"已将极片 {electrode_sheet_3.name} 从子弹夹转移到电池") + # else: + # print("子弹夹洞位0没有极片") + # + # # TODO:#把电解液从瓶中取到电池夹子中 + # battery_site = deck.get_resource("battery_press_1") + # clip_magazine_battery = deck.get_resource("clip_magazine_battery") + # if battery_site.has_battery(): + # battery = battery_site.take_battery() #从压制槽取出电池 + # clip_magazine_battery.add_battery(battery) #从压制槽取出电池 +# +# +# +# + # # 保存配置到文件 + # self.deck.save("button_battery_station_layout.json", indent=2) + # print("\n台面配置已保存到: button_battery_station_layout.json") + # + # # 保存状态到文件 + # self.deck.save_state_to_file("button_battery_station_state.json", indent=2) + # print("台面状态已保存到: button_battery_station_state.json") + + + + + + + #将数据写入csv中 + #如当前目录下无同名文件则新建一个csv用于存放数据 + if not os.path.exists(self.csv_export_file): + #创建一个表头 + with open(self.csv_export_file, 'w', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow([ + 'Time', 'open_circuit_voltage', 'pole_weight', + 'assembly_time', 'assembly_pressure', 'electrolyte_volume', + 'coin_num', 'electrolyte_code', 'coin_cell_code' + ]) + #立刻写入磁盘 + csvfile.flush() + #开始追加电池信息 + with open(self.csv_export_file, 'a', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow([ + timestamp, data_open_circuit_voltage, data_pole_weight, + data_assembly_time, data_assembly_pressure, data_electrolyte_volume, + data_coin_num, data_electrolyte_code, data_coin_cell_code + ]) + #立刻写入磁盘 + csvfile.flush() + + # 只要不在自动模式运行中,就将允许标志位置False + if self.sys_auto_status == False or self.sys_start_status == False: + self.allow_data_read = False + self.csv_export_running = False + time.sleep(1) + + def func_stop_read_data(self): + """停止CSV导出""" + if not self.csv_export_running: + return False, "read data未在运行" + + self.csv_export_running = False + self.allow_data_read = False + + if self.csv_export_thread and self.csv_export_thread.is_alive(): + self.csv_export_thread.join(timeout=5) + + def func_get_csv_export_status(self): + """获取CSV导出状态""" + return { + 'allow_read': self.allow_data_read, + 'running': self.csv_export_running, + 'thread_alive': self.csv_export_thread.is_alive() if self.csv_export_thread else False + } + + + ''' + # ===================== 物料管理区 ====================== + @property + def data_material_inventory(self) -> int: + """主物料库存 (数量, INT16)""" + inventory, read_err = self.client.use_node('REG_DATA_MATERIAL_INVENTORY').read(1) + return inventory + + @property + def data_tips_inventory(self) -> int: + """移液枪头库存 (数量, INT16)""" + inventory, read_err = self.client.register_node_list(self.nodes).use_node('REG_DATA_TIPS_INVENTORY').read(1) + return inventory + + ''' + + +if __name__ == "__main__": + from pylabrobot.resources import Resource + Coin_Cell = CoinCellAssemblyWorkstation(Resource("1", 1, 1, 1), debug_mode=True) + #Coin_Cell.func_pack_device_init() + #Coin_Cell.func_pack_device_auto() + #Coin_Cell.func_pack_device_start() + #Coin_Cell.func_pack_send_bottle_num(2) + #Coin_Cell.func_pack_send_msg_cmd(2) + #Coin_Cell.func_pack_get_msg_cmd() + #Coin_Cell.func_pack_get_msg_cmd() + #Coin_Cell.func_pack_send_finished_cmd() +# + #Coin_Cell.func_allpack_cmd(3, 2) + #print(Coin_Cell.data_stack_vision_code) + #print("success") + #创建一个物料台面 + + deck = create_a_coin_cell_deck() + #deck = create_a_full_coin_cell_deck() + + + ##在台面上找到料盘和极片 + #liaopan1 = deck.get_resource("liaopan1") + #liaopan2 = deck.get_resource("liaopan2") + #jipian1 = liaopan1.children[1].children[0] +## + #print(jipian1) + ##把物料解绑后放到另一盘上 + #jipian1.parent.unassign_child_resource(jipian1) + #liaopan2.children[1].assign_child_resource(jipian1, location=None) + ##print(jipian2.parent) + + liaopan1 = deck.get_resource("liaopan1") + liaopan2 = deck.get_resource("liaopan2") + for i in range(16): + #找到liaopan1上每一个jipian + jipian_linshi = liaopan1.children[i].children[0] + #把物料解绑后放到另一盘上 + print("极片:", jipian_linshi) + jipian_linshi.parent.unassign_child_resource(jipian_linshi) + liaopan2.children[i].assign_child_resource(jipian_linshi, location=None) + + + from unilabos.resources.graphio import resource_ulab_to_plr, convert_resources_to_type + #with open("./button_battery_station_resources_unilab.json", "r", encoding="utf-8") as f: + # bioyond_resources_unilab = json.load(f) + #print(f"成功读取 JSON 文件,包含 {len(bioyond_resources_unilab)} 个资源") + #ulab_resources = convert_resources_to_type(bioyond_resources_unilab, List[PLRResource]) + #print(f"转换结果类型: {type(ulab_resources)}") + #print(ulab_resources) + + + + from unilabos.resources.graphio import convert_resources_from_type + from unilabos.config.config import BasicConfig + BasicConfig.ak = "beb0c15f-2279-46a1-aba5-00eaf89aef55" + BasicConfig.sk = "15d4f25e-3512-4f9c-9bfb-43ab85e7b561" + from unilabos.app.web.client import http_client + + resources = convert_resources_from_type([deck], [Resource]) + json.dump({"nodes": resources, "links": []}, open("button_battery_station_resources_unilab.json", "w"), indent=2) + + #print(resources) + http_client.remote_addr = "https://uni-lab.test.bohrium.com/api/v1" + + http_client.resource_add(resources) \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/coin_cell_assembly_a.csv b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/coin_cell_assembly_a.csv new file mode 100644 index 0000000..836fb71 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/coin_cell_assembly_a.csv @@ -0,0 +1,63 @@ +Name,DataType,InitValue,Comment,Attribute,DeviceType,Address, +COIL_SYS_START_CMD,BOOL,,,,coil,8010, +COIL_SYS_STOP_CMD,BOOL,,,,coil,8020, +COIL_SYS_RESET_CMD,BOOL,,,,coil,8030, +COIL_SYS_HAND_CMD,BOOL,,,,coil,8040, +COIL_SYS_AUTO_CMD,BOOL,,,,coil,8050, +COIL_SYS_INIT_CMD,BOOL,,,,coil,8060, +COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,,,,coil,8700, +COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,,,,coil,8710,unilab_rec_msg_succ_cmd +COIL_SYS_START_STATUS,BOOL,,,,coil,8210, +COIL_SYS_STOP_STATUS,BOOL,,,,coil,8220, +COIL_SYS_RESET_STATUS,BOOL,,,,coil,8230, +COIL_SYS_HAND_STATUS,BOOL,,,,coil,8240, +COIL_SYS_AUTO_STATUS,BOOL,,,,coil,8250, +COIL_SYS_INIT_STATUS,BOOL,,,,coil,8260, +COIL_REQUEST_REC_MSG_STATUS,BOOL,,,,coil,8500, +COIL_REQUEST_SEND_MSG_STATUS,BOOL,,,,coil,8510,request_send_msg_status +REG_MSG_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,11000, +REG_MSG_ELECTROLYTE_NUM,INT16,,,,hold_register,11002,unilab_send_msg_electrolyte_num +REG_MSG_ELECTROLYTE_VOLUME,INT16,,,,hold_register,11004,unilab_send_msg_electrolyte_vol +REG_MSG_ASSEMBLY_TYPE,INT16,,,,hold_register,11006,unilab_send_msg_assembly_type +REG_MSG_ASSEMBLY_PRESSURE,INT16,,,,hold_register,11008,unilab_send_msg_assembly_pressure +REG_DATA_ASSEMBLY_COIN_CELL_NUM,INT16,,,,hold_register,10000,data_assembly_coin_cell_num +REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,,,,hold_register,10002,data_open_circuit_voltage +REG_DATA_AXIS_X_POS,FLOAT32,,,,hold_register,10004, +REG_DATA_AXIS_Y_POS,FLOAT32,,,,hold_register,10006, +REG_DATA_AXIS_Z_POS,FLOAT32,,,,hold_register,10008, +REG_DATA_POLE_WEIGHT,FLOAT32,,,,hold_register,10010,data_pole_weight +REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,,,,hold_register,10012,data_assembly_time +REG_DATA_ASSEMBLY_PRESSURE,INT16,,,,hold_register,10014,data_assembly_pressure +REG_DATA_ELECTROLYTE_VOLUME,INT16,,,,hold_register,10016,data_electrolyte_volume +REG_DATA_COIN_NUM,INT16,,,,hold_register,10018,data_coin_num +REG_DATA_ELECTROLYTE_CODE,STRING,,,,hold_register,10020,data_electrolyte_code() +REG_DATA_COIN_CELL_CODE,STRING,,,,hold_register,10030,data_coin_cell_code() +REG_DATA_STACK_VISON_CODE,STRING,,,,hold_register,12004,data_stack_vision_code() +REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,,,,hold_register,10050,data_glove_box_pressure +REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,,,,hold_register,10052,data_glove_box_water_content +REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,,,,hold_register,10054,data_glove_box_o2_content +UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8720, +UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8520, +REG_MSG_ELECTROLYTE_NUM_USED,INT16,,,,hold_register,496, +REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,10000, +UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,8730, +UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,8530, +REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,10018,ASSEMBLY_TYPE7or8 +COIL_ALUMINUM_FOIL,BOOL,,ʹ,,coil,8340, +REG_MSG_NE_PLATE_MATRIX,INT16,,Ƭλ,,hold_register,440, +REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,Ĥλ,,hold_register,450, +REG_MSG_TIP_BOX_MATRIX,INT16,,Һǹͷλ,,hold_register,480, +REG_MSG_NE_PLATE_NUM,INT16,,Ƭ,,hold_register,443, +REG_MSG_SEPARATOR_PLATE_NUM,INT16,,Ĥ,,hold_register,453, +REG_MSG_PRESS_MODE,BOOL,,ѹģʽfalse:ѹģʽTrue:ģʽ,,coil,8360,ѹģʽ +,,,,,,, +,BOOL,,Ӿλfalse:ʹãtrue:ԣ,,coil,8300,Ӿλ +,BOOL,,죨false:ʹãtrue:ԣ,,coil,8310,Ӿ +,BOOL,,_֣false:ʹãtrue:ԣ,,coil,8320, +,BOOL,,_Ҳ֣false:ʹãtrue:ԣ,,coil,8420,Ҳ +,BOOL,,ռ֪false:ʹãtrue:ԣ,,coil,8350,ռ֪ +,BOOL,,Һģʽfalse:εҺtrue:εҺ,,coil,8370,Һģʽ +,BOOL,,Ƭأfalse:ʹãtrue:ԣ,,coil,8380,Ƭ +,BOOL,,Ƭװʽfalse:װtrue:װ,,coil,8390,װ +,BOOL,,ѹࣨfalse:ʹãtrue:ԣ,,coil,8400,ѹ +,BOOL,,̷̰ʽfalse:ˮƽ̣true:ѵ̣,,coil,8410,Ƭ̷ʽ diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/new_cellconfig3c.json b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/new_cellconfig3c.json new file mode 100644 index 0000000..630faa5 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/new_cellconfig3c.json @@ -0,0 +1,691 @@ +{ + "nodes": [ + { + "id": "BatteryStation", + "name": "扣电工作站", + "children": [ + "coin_cell_deck" + ], + "parent": null, + "type": "device", + "class": "bettery_station_registry", + "position": { + "x": 600, + "y": 400, + "z": 0 + }, + "config": { + "debug_mode": false, + "_comment": "protocol_type接外部工站固定写法字段,一般为空,station_resource写法也固定", + "protocol_type": [], + "station_resource": { + "data": { + "_resource_child_name": "coin_cell_deck", + "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" + } + }, + + "address": "192.168.1.20", + "port": 502 + }, + "data": {} + }, + { + "id": "coin_cell_deck", + "name": "coin_cell_deck", + "sample_id": null, + "children": [ + "\u7535\u6c60\u6599\u76d8" + ], + "parent": null, + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "CoincellDeck", + "size_x": 1000, + "size_y": 1000, + "size_z": 900, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "coin_cell_deck", + "barcode": null + }, + "data": {} + }, + { + "id": "\u7535\u6c60\u6599\u76d8", + "name": "\u7535\u6c60\u6599\u76d8", + "sample_id": null, + "children": [ + "\u7535\u6c60\u6599\u76d8_materialhole_0_0", + "\u7535\u6c60\u6599\u76d8_materialhole_0_1", + "\u7535\u6c60\u6599\u76d8_materialhole_0_2", + "\u7535\u6c60\u6599\u76d8_materialhole_0_3", + "\u7535\u6c60\u6599\u76d8_materialhole_1_0", + "\u7535\u6c60\u6599\u76d8_materialhole_1_1", + "\u7535\u6c60\u6599\u76d8_materialhole_1_2", + "\u7535\u6c60\u6599\u76d8_materialhole_1_3", + "\u7535\u6c60\u6599\u76d8_materialhole_2_0", + "\u7535\u6c60\u6599\u76d8_materialhole_2_1", + "\u7535\u6c60\u6599\u76d8_materialhole_2_2", + "\u7535\u6c60\u6599\u76d8_materialhole_2_3", + "\u7535\u6c60\u6599\u76d8_materialhole_3_0", + "\u7535\u6c60\u6599\u76d8_materialhole_3_1", + "\u7535\u6c60\u6599\u76d8_materialhole_3_2", + "\u7535\u6c60\u6599\u76d8_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 100, + "y": 100, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120.8, + "size_y": 160.5, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", + "B1": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", + "C1": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", + "D1": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", + "A2": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", + "B2": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", + "C2": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", + "D2": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", + "A3": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", + "B3": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", + "C3": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", + "D3": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", + "A4": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", + "B4": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", + "C4": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", + "D4": "\u7535\u6c60\u6599\u76d8_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 104.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 80.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 56.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 32.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 104.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 80.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 56.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 32.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 104.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 80.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 56.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 32.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 104.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 80.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 56.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_3", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_3", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 32.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + } + ], + "links": [] +} \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/workstation_base.py b/unilabos/devices/workstation/coin_cell_assembly/workstation_base.py new file mode 100644 index 0000000..65b34a9 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/workstation_base.py @@ -0,0 +1,489 @@ +""" +工作站基类 +Workstation Base Class - 简化版 + +基于PLR Deck的简化工作站架构 +专注于核心物料系统和工作流管理 +""" + +import collections +import time +from typing import Dict, Any, List, Optional, Union +from abc import ABC, abstractmethod +from dataclasses import dataclass +from enum import Enum +from pylabrobot.resources import Deck, Plate, Resource as PLRResource + +from pylabrobot.resources.coordinate import Coordinate +from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode + +from unilabos.utils.log import logger + + +class WorkflowStatus(Enum): + """工作流状态""" + + IDLE = "idle" + INITIALIZING = "initializing" + RUNNING = "running" + PAUSED = "paused" + STOPPING = "stopping" + STOPPED = "stopped" + ERROR = "error" + COMPLETED = "completed" + + +@dataclass +class WorkflowInfo: + """工作流信息""" + + name: str + description: str + estimated_duration: float # 预估持续时间(秒) + required_materials: List[str] # 所需物料类型 + output_product: str # 输出产品类型 + parameters_schema: Dict[str, Any] # 参数架构 + + +class WorkStationContainer(Plate): + """ + WorkStation 专用 Container 类,继承自 Plate和TipRack + 注意这个物料必须通过plr_additional_res_reg.py注册到edge,才能正常序列化 + """ + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + category: str, + ordering: collections.OrderedDict, + model: Optional[str] = None, + ): + """ + 这里的初始化入参要和plr的保持一致 + """ + super().__init__(name, size_x, size_y, size_z, category=category, ordering=ordering, model=model) + self._unilabos_state = {} # 必须有此行,自己的类描述的是物料的 + + def load_state(self, state: Dict[str, Any]) -> None: + """从给定的状态加载工作台信息。""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + data = super().serialize_state() + data.update( + self._unilabos_state + ) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + + +def get_workstation_plate_resource(name: str) -> PLRResource: # 要给定一个返回plr的方法 + """ + 用于获取一些模板,例如返回一个带有特定信息/子物料的 Plate,这里需要到注册表注册,例如unilabos/registry/resources/organic/workstation.yaml + 可以直接运行该函数或者利用注册表补全机制,来检查是否资源出错 + :param name: 资源名称 + :return: Resource对象 + """ + plate = WorkStationContainer( + name, size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict() + ) + tip_rack = WorkStationContainer( + "tip_rack_inside_plate", + size_x=50, + size_y=50, + size_z=10, + category="tip_rack", + ordering=collections.OrderedDict(), + ) + plate.assign_child_resource(tip_rack, Coordinate.zero()) + return plate + + +class ResourceSynchronizer(ABC): + """资源同步器基类 + + 负责与外部物料系统的同步,并对 self.deck 做修改 + """ + + def __init__(self, workstation: "WorkstationBase"): + self.workstation = workstation + + @abstractmethod + async def sync_from_external(self) -> bool: + """从外部系统同步物料到本地deck""" + pass + + @abstractmethod + async def sync_to_external(self, plr_resource: PLRResource) -> bool: + """将本地物料同步到外部系统""" + pass + + @abstractmethod + async def handle_external_change(self, change_info: Dict[str, Any]) -> bool: + """处理外部系统的变更通知""" + pass + + +class WorkstationBase(ABC): + """工作站基类 - 简化版 + + 核心功能: + 1. 基于 PLR Deck 的物料系统,支持格式转换 + 2. 可选的资源同步器支持外部物料系统 + 3. 简化的工作流管理 + """ + + _ros_node: ROS2WorkstationNode + + @property + def _children(self) -> Dict[str, Any]: # 不要删除这个下划线,不然会自动导入注册表,后面改成装饰器识别 + return self._ros_node.children + + async def update_resource_example(self): + return await self._ros_node.update_resource([get_workstation_plate_resource("test")]) + + def __init__( + self, + station_resource: PLRResource, + *args, + **kwargs, # 必须有kwargs + ): + # 基本配置 + print(station_resource) + self.deck_config = station_resource + + # PLR 物料系统 + self.deck: Optional[Deck] = None + self.plr_resources: Dict[str, PLRResource] = {} + + # 资源同步器(可选) + # self.resource_synchronizer = ResourceSynchronizer(self) # 要在driver中自行初始化,只有workstation用 + + # 硬件接口 + self.hardware_interface: Union[Any, str] = None + + # 工作流状态 + self.current_workflow_status = WorkflowStatus.IDLE + self.current_workflow_info = None + self.workflow_start_time = None + self.workflow_parameters = {} + + # 支持的工作流(静态预定义) + self.supported_workflows: Dict[str, WorkflowInfo] = {} + + # 初始化物料系统 + self._initialize_material_system() + + # 注册支持的工作流 + # self._register_supported_workflows() + + # logger.info(f"工作站 {device_id} 初始化完成(简化版)") + + def _initialize_material_system(self): + """初始化物料系统 - 使用 graphio 转换""" + try: + from unilabos.resources.graphio import resource_ulab_to_plr + + # # 1. 合并 deck_config 和 children 创建完整的资源树 + # complete_resource_config = self._create_complete_resource_config() + + # # 2. 使用 graphio 转换为 PLR 资源 + # self.deck = resource_ulab_to_plr(complete_resource_config, plr_model=True) + + # # 3. 建立资源映射 + # self._build_resource_mappings(self.deck) + + # # 4. 如果有资源同步器,执行初始同步 + # if self.resource_synchronizer: + # # 这里可以异步执行,暂时跳过 + # pass + + # logger.info(f"工作站 {self.device_id} 物料系统初始化成功,创建了 {len(self.plr_resources)} 个资源") + pass + except Exception as e: + # logger.error(f"工作站 {self.device_id} 物料系统初始化失败: {e}") + raise + + def _create_complete_resource_config(self) -> Dict[str, Any]: + """创建完整的资源配置 - 合并 deck_config 和 children""" + # 创建主 deck 配置 + deck_resource = { + "id": f"{self.device_id}_deck", + "name": f"{self.device_id}_deck", + "type": "deck", + "position": {"x": 0, "y": 0, "z": 0}, + "config": { + "size_x": self.deck_config.get("size_x", 1000.0), + "size_y": self.deck_config.get("size_y", 1000.0), + "size_z": self.deck_config.get("size_z", 100.0), + **{k: v for k, v in self.deck_config.items() if k not in ["size_x", "size_y", "size_z"]}, + }, + "data": {}, + "children": [], + "parent": None, + } + + # 添加子资源 + if self._children: + children_list = [] + for child_id, child_config in self._children.items(): + child_resource = self._normalize_child_resource(child_id, child_config, deck_resource["id"]) + children_list.append(child_resource) + deck_resource["children"] = children_list + + return deck_resource + + def _normalize_child_resource(self, resource_id: str, config: Dict[str, Any], parent_id: str) -> Dict[str, Any]: + """标准化子资源配置""" + return { + "id": resource_id, + "name": config.get("name", resource_id), + "type": config.get("type", "container"), + "position": self._normalize_position(config.get("position", {})), + "config": config.get("config", {}), + "data": config.get("data", {}), + "children": [], # 简化版本:只支持一层子资源 + "parent": parent_id, + } + + def _normalize_position(self, position: Any) -> Dict[str, float]: + """标准化位置信息""" + if isinstance(position, dict): + return { + "x": float(position.get("x", 0)), + "y": float(position.get("y", 0)), + "z": float(position.get("z", 0)), + } + elif isinstance(position, (list, tuple)) and len(position) >= 2: + return { + "x": float(position[0]), + "y": float(position[1]), + "z": float(position[2]) if len(position) > 2 else 0.0, + } + else: + return {"x": 0.0, "y": 0.0, "z": 0.0} + + def _build_resource_mappings(self, deck: Deck): + """递归构建资源映射""" + + def add_resource_recursive(resource: PLRResource): + if hasattr(resource, "name"): + self.plr_resources[resource.name] = resource + + if hasattr(resource, "children"): + for child in resource.children: + add_resource_recursive(child) + + add_resource_recursive(deck) + + # ============ 硬件接口管理 ============ + + def set_hardware_interface(self, hardware_interface: Union[Any, str]): + """设置硬件接口""" + self.hardware_interface = hardware_interface + logger.info(f"工作站 {self.device_id} 硬件接口设置: {type(hardware_interface).__name__}") + + def set_workstation_node(self, workstation_node: "ROS2WorkstationNode"): + """设置协议节点引用(用于代理模式)""" + self._ros_node = workstation_node + logger.info(f"工作站 {self.device_id} 关联协议节点") + + # ============ 设备操作接口 ============ + + def call_device_method(self, method: str, *args, **kwargs) -> Any: + """调用设备方法的统一接口""" + # 1. 代理模式:通过协议节点转发 + if isinstance(self.hardware_interface, str) and self.hardware_interface.startswith("proxy:"): + if not self._ros_node: + raise RuntimeError("代理模式需要设置workstation_node") + + device_id = self.hardware_interface[6:] # 移除 "proxy:" 前缀 + return self._ros_node.call_device_method(device_id, method, *args, **kwargs) + + # 2. 直接模式:直接调用硬件接口方法 + elif self.hardware_interface and hasattr(self.hardware_interface, method): + return getattr(self.hardware_interface, method)(*args, **kwargs) + + else: + raise AttributeError(f"硬件接口不支持方法: {method}") + + def get_device_status(self) -> Dict[str, Any]: + """获取设备状态""" + try: + return self.call_device_method("get_status") + except AttributeError: + # 如果设备不支持get_status方法,返回基础状态 + return { + "status": "unknown", + "interface_type": type(self.hardware_interface).__name__, + "timestamp": time.time(), + } + + def is_device_available(self) -> bool: + """检查设备是否可用""" + try: + self.get_device_status() + return True + except: + return False + + # ============ 物料系统接口 ============ + + def get_deck(self) -> Deck: + """获取主 Deck""" + return self.deck + + def get_all_resources(self) -> Dict[str, PLRResource]: + """获取所有 PLR 资源""" + return self.plr_resources.copy() + + def find_resource_by_name(self, name: str) -> Optional[PLRResource]: + """按名称查找资源""" + return self.plr_resources.get(name) + + def find_resources_by_type(self, resource_type: type) -> List[PLRResource]: + """按类型查找资源""" + return [res for res in self.plr_resources.values() if isinstance(res, resource_type)] + + async def sync_with_external_system(self) -> bool: + """与外部物料系统同步""" + if not self.resource_synchronizer: + logger.info(f"工作站 {self.device_id} 没有配置资源同步器") + return True + + try: + success = await self.resource_synchronizer.sync_from_external() + if success: + logger.info(f"工作站 {self.device_id} 外部同步成功") + else: + logger.warning(f"工作站 {self.device_id} 外部同步失败") + return success + except Exception as e: + logger.error(f"工作站 {self.device_id} 外部同步异常: {e}") + return False + + # ============ 简化的工作流控制 ============ + + def execute_workflow(self, workflow_name: str, parameters: Dict[str, Any]) -> bool: + """执行工作流""" + try: + # 设置工作流状态 + self.current_workflow_status = WorkflowStatus.INITIALIZING + self.workflow_parameters = parameters + self.workflow_start_time = time.time() + + # 委托给子类实现 + success = self._execute_workflow_impl(workflow_name, parameters) + + if success: + self.current_workflow_status = WorkflowStatus.RUNNING + logger.info(f"工作站 {self.device_id} 工作流 {workflow_name} 启动成功") + else: + self.current_workflow_status = WorkflowStatus.ERROR + logger.error(f"工作站 {self.device_id} 工作流 {workflow_name} 启动失败") + + return success + + except Exception as e: + self.current_workflow_status = WorkflowStatus.ERROR + logger.error(f"工作站 {self.device_id} 执行工作流失败: {e}") + return False + + def stop_workflow(self, emergency: bool = False) -> bool: + """停止工作流""" + try: + if self.current_workflow_status in [WorkflowStatus.IDLE, WorkflowStatus.STOPPED]: + logger.warning(f"工作站 {self.device_id} 没有正在运行的工作流") + return True + + self.current_workflow_status = WorkflowStatus.STOPPING + + # 委托给子类实现 + success = self._stop_workflow_impl(emergency) + + if success: + self.current_workflow_status = WorkflowStatus.STOPPED + logger.info(f"工作站 {self.device_id} 工作流停止成功 (紧急: {emergency})") + else: + self.current_workflow_status = WorkflowStatus.ERROR + logger.error(f"工作站 {self.device_id} 工作流停止失败") + + return success + + except Exception as e: + self.current_workflow_status = WorkflowStatus.ERROR + logger.error(f"工作站 {self.device_id} 停止工作流失败: {e}") + return False + + # ============ 状态属性 ============ + + @property + def workflow_status(self) -> WorkflowStatus: + """获取当前工作流状态""" + return self.current_workflow_status + + @property + def is_busy(self) -> bool: + """检查工作站是否忙碌""" + return self.current_workflow_status in [ + WorkflowStatus.INITIALIZING, + WorkflowStatus.RUNNING, + WorkflowStatus.STOPPING, + ] + + @property + def workflow_runtime(self) -> float: + """获取工作流运行时间(秒)""" + if self.workflow_start_time is None: + return 0.0 + return time.time() - self.workflow_start_time + + # ============ 抽象方法 - 子类必须实现 ============ + + # @abstractmethod + # def _register_supported_workflows(self): + # """注册支持的工作流 - 子类必须实现""" + # pass + + # @abstractmethod + # def _execute_workflow_impl(self, workflow_name: str, parameters: Dict[str, Any]) -> bool: + # """执行工作流的具体实现 - 子类必须实现""" + # pass + + # @abstractmethod + # def _stop_workflow_impl(self, emergency: bool = False) -> bool: + # """停止工作流的具体实现 - 子类必须实现""" + # pass + +class WorkstationExample(WorkstationBase): + """工作站示例实现""" + + def _register_supported_workflows(self): + """注册支持的工作流""" + self.supported_workflows["example_workflow"] = WorkflowInfo( + name="example_workflow", + description="这是一个示例工作流", + estimated_duration=300.0, + required_materials=["sample_plate"], + output_product="processed_plate", + parameters_schema={"param1": "string", "param2": "integer"}, + ) + + def _execute_workflow_impl(self, workflow_name: str, parameters: Dict[str, Any]) -> bool: + """执行工作流的具体实现""" + if workflow_name not in self.supported_workflows: + logger.error(f"工作站 {self.device_id} 不支持工作流: {workflow_name}") + return False + + # 这里添加实际的工作流逻辑 + logger.info(f"工作站 {self.device_id} 正在执行工作流: {workflow_name} with parameters {parameters}") + return True + + def _stop_workflow_impl(self, emergency: bool = False) -> bool: + """停止工作流的具体实现""" + # 这里添加实际的停止逻辑 + logger.info(f"工作站 {self.device_id} 正在停止工作流 (紧急: {emergency})") + return True \ No newline at end of file diff --git a/unilabos/registry/devices/work_station.yaml b/unilabos/registry/devices/work_station.yaml index 380c3f6..ba07691 100644 --- a/unilabos/registry/devices/work_station.yaml +++ b/unilabos/registry/devices/work_station.yaml @@ -1,3 +1,517 @@ +bettery_station_registry: + category: + - work_station + class: + action_value_mappings: + auto-change_hole_sheet_to_2: + feedback: {} + goal: {} + goal_default: + hole: null + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + hole: + type: object + required: + - hole + type: object + result: {} + required: + - goal + title: change_hole_sheet_to_2参数 + type: object + type: UniLabJsonCommandAsync + auto-fill_plate: + feedback: {} + goal: {} + goal_default: {} + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: fill_plate参数 + type: object + type: UniLabJsonCommandAsync + auto-fun_wuliao_test: + feedback: {} + goal: {} + goal_default: {} + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: fun_wuliao_test参数 + type: object + type: UniLabJsonCommand + auto-func_allpack_cmd: + feedback: {} + goal: {} + goal_default: + assembly_pressure: 4200 + assembly_type: 7 + elec_num: null + elec_use_num: null + elec_vol: 50 + file_path: D:\coin_cell_data + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + assembly_pressure: + default: 4200 + type: integer + assembly_type: + default: 7 + type: integer + elec_num: + type: string + elec_use_num: + type: string + elec_vol: + default: 50 + type: integer + file_path: + default: D:\coin_cell_data + type: string + required: + - elec_num + - elec_use_num + type: object + result: {} + required: + - goal + title: func_allpack_cmd参数 + type: object + type: UniLabJsonCommand + auto-func_get_csv_export_status: + feedback: {} + goal: {} + goal_default: {} + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_get_csv_export_status参数 + type: object + type: UniLabJsonCommand + auto-func_pack_device_auto: + feedback: {} + goal: {} + goal_default: {} + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_pack_device_auto参数 + type: object + type: UniLabJsonCommand + auto-func_pack_device_init: + feedback: {} + goal: {} + goal_default: {} + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_pack_device_init参数 + type: object + type: UniLabJsonCommand + auto-func_pack_device_start: + feedback: {} + goal: {} + goal_default: {} + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_pack_device_start参数 + type: object + type: UniLabJsonCommand + auto-func_pack_device_stop: + feedback: {} + goal: {} + goal_default: {} + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_pack_device_stop参数 + type: object + type: UniLabJsonCommand + auto-func_pack_get_msg_cmd: + feedback: {} + goal: {} + goal_default: + file_path: D:\coin_cell_data + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + file_path: + default: D:\coin_cell_data + type: string + required: [] + type: object + result: {} + required: + - goal + title: func_pack_get_msg_cmd参数 + type: object + type: UniLabJsonCommand + auto-func_pack_send_bottle_num: + feedback: {} + goal: {} + goal_default: + bottle_num: null + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + bottle_num: + type: string + required: + - bottle_num + type: object + result: {} + required: + - goal + title: func_pack_send_bottle_num参数 + type: object + type: UniLabJsonCommand + auto-func_pack_send_finished_cmd: + feedback: {} + goal: {} + goal_default: {} + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_pack_send_finished_cmd参数 + type: object + type: UniLabJsonCommand + auto-func_pack_send_msg_cmd: + feedback: {} + goal: {} + goal_default: + assembly_pressure: null + assembly_type: null + elec_use_num: null + elec_vol: null + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + assembly_pressure: + type: string + assembly_type: + type: string + elec_use_num: + type: string + elec_vol: + type: string + required: + - elec_use_num + - elec_vol + - assembly_type + - assembly_pressure + type: object + result: {} + required: + - goal + title: func_pack_send_msg_cmd参数 + type: object + type: UniLabJsonCommand + auto-func_read_data_and_output: + feedback: {} + goal: {} + goal_default: + file_path: D:\coin_cell_data + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + file_path: + default: D:\coin_cell_data + type: string + required: [] + type: object + result: {} + required: + - goal + title: func_read_data_and_output参数 + type: object + type: UniLabJsonCommand + auto-func_stop_read_data: + feedback: {} + goal: {} + goal_default: {} + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_stop_read_data参数 + type: object + type: UniLabJsonCommand + auto-modify_deck_name: + feedback: {} + goal: {} + goal_default: + resource_name: null + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + resource_name: + type: string + required: + - resource_name + type: object + result: {} + required: + - goal + title: modify_deck_name参数 + type: object + type: UniLabJsonCommand + auto-post_init: + feedback: {} + goal: {} + goal_default: + ros_node: null + handles: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + ros_node: + type: object + required: + - ros_node + type: object + result: {} + required: + - goal + title: post_init参数 + type: object + type: UniLabJsonCommand + module: unilabos.devices.workstation.coin_cell_assembly.coin_cell_assembly:CoinCellAssemblyWorkstation + status_types: + data_assembly_coin_cell_num: int + data_assembly_pressure: int + data_assembly_time: float + data_axis_x_pos: float + data_axis_y_pos: float + data_axis_z_pos: float + data_coin_cell_code: str + data_coin_num: int + data_electrolyte_code: str + data_electrolyte_volume: int + data_glove_box_o2_content: float + data_glove_box_pressure: float + data_glove_box_water_content: float + data_open_circuit_voltage: float + data_pole_weight: float + request_rec_msg_status: bool + request_send_msg_status: bool + sys_mode: str + sys_status: str + type: python + config_info: [] + description: '' + handles: [] + icon: '' + init_param_schema: + config: + properties: + address: + default: 192.168.1.20 + type: string + debug_mode: + default: true + type: boolean + port: + default: '502' + type: string + station_resource: + type: object + required: + - station_resource + type: object + data: + properties: + data_assembly_coin_cell_num: + type: integer + data_assembly_pressure: + type: integer + data_assembly_time: + type: number + data_axis_x_pos: + type: number + data_axis_y_pos: + type: number + data_axis_z_pos: + type: number + data_coin_cell_code: + type: string + data_coin_num: + type: integer + data_electrolyte_code: + type: string + data_electrolyte_volume: + type: integer + data_glove_box_o2_content: + type: number + data_glove_box_pressure: + type: number + data_glove_box_water_content: + type: number + data_open_circuit_voltage: + type: number + data_pole_weight: + type: number + request_rec_msg_status: + type: boolean + request_send_msg_status: + type: boolean + sys_mode: + type: string + sys_status: + type: string + required: + - sys_status + - sys_mode + - request_rec_msg_status + - request_send_msg_status + - data_assembly_coin_cell_num + - data_assembly_time + - data_open_circuit_voltage + - data_axis_x_pos + - data_axis_y_pos + - data_axis_z_pos + - data_pole_weight + - data_assembly_pressure + - data_electrolyte_volume + - data_coin_num + - data_coin_cell_code + - data_electrolyte_code + - data_glove_box_pressure + - data_glove_box_o2_content + - data_glove_box_water_content + type: object + version: 1.0.0 workstation: category: - work_station @@ -6024,9 +6538,97 @@ workstation: title: WashSolid type: object type: WashSolid - module: unilabos.devices.workstation.workstation_base:ProtocolNode + auto-create_ros_action_server: + feedback: {} + goal: {} + goal_default: + action_name: null + action_value_mapping: null + handles: {} + result: {} + schema: + description: create_ros_action_server的参数schema + properties: + feedback: {} + goal: + properties: + action_name: + type: string + action_value_mapping: + type: string + required: + - action_name + - action_value_mapping + type: object + result: {} + required: + - goal + title: create_ros_action_server参数 + type: object + type: UniLabJsonCommand + auto-execute_single_action: + feedback: {} + goal: {} + goal_default: + action_kwargs: null + action_name: null + device_id: null + handles: {} + result: {} + schema: + description: execute_single_action的参数schema + properties: + feedback: {} + goal: + properties: + action_kwargs: + type: string + action_name: + type: string + device_id: + type: string + required: + - device_id + - action_name + - action_kwargs + type: object + result: {} + required: + - goal + title: execute_single_action参数 + type: object + type: UniLabJsonCommandAsync + auto-initialize_device: + feedback: {} + goal: {} + goal_default: + device_config: null + device_id: null + handles: {} + result: {} + schema: + description: initialize_device的参数schema + properties: + feedback: {} + goal: + properties: + device_config: + type: string + device_id: + type: string + required: + - device_id + - device_config + type: object + result: {} + required: + - goal + title: initialize_device参数 + type: object + type: UniLabJsonCommand + module: unilabos.ros.nodes.presets.workstation:ROS2WorkstationNode status_types: {} - type: python + type: ros2 config_info: [] description: Workstation handles: [] @@ -6034,437 +6636,63 @@ workstation: init_param_schema: config: properties: - deck: + action_value_mappings: + type: object + children: + type: object + device_id: type: string + driver_instance: + type: string + hardware_interface: + type: object + print_publish: + default: true + type: string + protocol_type: + items: + type: string + type: array + resource_tracker: + type: string + status_types: + type: object required: - - deck + - protocol_type + - children + - driver_instance + - device_id + - status_types + - action_value_mappings + - hardware_interface type: object data: properties: {} required: [] type: object version: 1.0.0 -workstation.bioyond: +workstation.example: category: - work_station class: - action_value_mappings: - auto-append_to_workflow_sequence: - feedback: {} - goal: {} - goal_default: - web_workflow_name: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - web_workflow_name: - type: string - required: - - web_workflow_name - type: object - result: {} - required: - - goal - title: append_to_workflow_sequence参数 - type: object - type: UniLabJsonCommand - auto-clear_workflows: - feedback: {} - goal: {} - goal_default: {} - handles: {} - placeholder_keys: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: clear_workflows参数 - type: object - type: UniLabJsonCommand - auto-load_bioyond_data_from_file: - feedback: {} - goal: {} - goal_default: - file_path: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - file_path: - type: string - required: - - file_path - type: object - result: {} - required: - - goal - title: load_bioyond_data_from_file参数 - type: object - type: UniLabJsonCommand - auto-post_init: - feedback: {} - goal: {} - goal_default: - ros_node: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - ros_node: - type: object - required: - - ros_node - type: object - result: {} - required: - - goal - title: post_init参数 - type: object - type: UniLabJsonCommand - auto-process_web_workflows: - feedback: {} - goal: {} - goal_default: - json_str: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - json_str: - type: string - required: - - json_str - type: object - result: {} - required: - - goal - title: process_web_workflows参数 - type: object - type: UniLabJsonCommand - auto-reset_workstation: - feedback: {} - goal: {} - goal_default: {} - handles: {} - placeholder_keys: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: reset_workstation参数 - type: object - type: UniLabJsonCommand - auto-resource_tree_add: - feedback: {} - goal: {} - goal_default: - resources: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - resources: - items: - type: object - type: array - required: - - resources - type: object - result: {} - required: - - goal - title: resource_tree_add参数 - type: object - type: UniLabJsonCommand - auto-set_workflow_sequence: - feedback: {} - goal: {} - goal_default: - json_str: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - json_str: - type: string - required: - - json_str - type: object - result: {} - required: - - goal - title: set_workflow_sequence参数 - type: object - type: UniLabJsonCommand - auto-transfer_resource_to_another: - feedback: {} - goal: {} - goal_default: - mount_device_id: null - mount_resource: null - resource: null - sites: null - handles: {} - placeholder_keys: - mount_device_id: unilabos_devices - mount_resource: unilabos_resources - resource: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - mount_device_id: - type: object - mount_resource: - items: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: mount_resource - type: object - type: array - resource: - items: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: resource - type: object - type: array - sites: - items: - type: string - type: array - required: - - resource - - mount_resource - - sites - - mount_device_id - type: object - result: {} - required: - - goal - title: transfer_resource_to_another参数 - type: object - type: UniLabJsonCommand - module: unilabos.devices.workstation.bioyond_studio.station:BioyondWorkstation - status_types: - all_workflows: dict - bioyond_status: dict - station_info: dict - workstation_status: dict + action_value_mappings: {} + module: unilabos.devices.workstation.workstation_base:WorkstationExample + status_types: {} type: python config_info: [] description: '' handles: [] - icon: 反应站.webp + icon: '' init_param_schema: config: properties: - bioyond_config: - type: string - deck: - type: string - required: [] - type: object - data: - properties: - all_workflows: - type: object - bioyond_status: - type: object - station_info: - type: object - workstation_status: + station_resource: type: object required: - - bioyond_status - - all_workflows - - station_info - - workstation_status + - station_resource + type: object + data: + properties: {} + required: [] type: object version: 1.0.0 From ceef342860e0124efa048183c303fa5b7f5cdbeb Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Mon, 27 Oct 2025 18:16:26 +0800 Subject: [PATCH 023/104] 1027byxinyu --- test/resources/YB_materials_info.json | 12 + .../electrolysis_water_platfrom.py | 296 +++++++++ .../bioyond_cell/bioyond_cell_workstation.py | 24 +- .../workstation/bioyond_studio/config.py | 38 +- .../workstation/bioyond_studio/station.py | 1 + .../resources/bioyond/bottle_carriers.yaml | 126 +--- .../registry/resources/bioyond/bottles.yaml | 134 +--- .../resources/bioyond/bottle_carriers copy.py | 148 +++++ unilabos/resources/bioyond/bottle_carriers.py | 620 ------------------ unilabos/resources/bioyond/bottles copy.py | 24 + unilabos/resources/bioyond/bottles.py | 255 ------- unilabos/resources/graphio.py | 2 + 12 files changed, 508 insertions(+), 1172 deletions(-) create mode 100644 test/resources/YB_materials_info.json create mode 100644 unilabos/devices/electrolysis_water_platfrom/electrolysis_water_platfrom.py create mode 100644 unilabos/resources/bioyond/bottle_carriers copy.py delete mode 100644 unilabos/resources/bioyond/bottle_carriers.py create mode 100644 unilabos/resources/bioyond/bottles copy.py delete mode 100644 unilabos/resources/bioyond/bottles.py diff --git a/test/resources/YB_materials_info.json b/test/resources/YB_materials_info.json new file mode 100644 index 0000000..61d2de8 --- /dev/null +++ b/test/resources/YB_materials_info.json @@ -0,0 +1,12 @@ +[ + {'id': '3a1d2656-69bf-1ccf-fc38-85a0431ee498', 'typeName': '加样头(大)', 'code': '0005-00291', 'barCode': '', 'name': 'test', 'quantity': 1.0, 'lockQuantity': 0.0, 'unit': '个', 'status': 1, 'isUse': False, 'locations': [ + {'id': '3a19da56-1379-20c8-5886-f7c4fbcb5733', 'whid': '3a19da56-1378-613b-29f2-871e1a287aa5', 'whName': '粉末加样头堆栈', 'code': '0005-0003', 'x': 3, 'y': 1, 'z': 1, 'quantity': 0 + } + ], 'detail': [] + }, + {'id': '3a1d2657-d16f-a575-3506-5c029ff6810d', 'typeName': '加样头(大)', 'code': '0005-00293', 'barCode': '', 'name': 'dsfdsfd', 'quantity': 1.0, 'lockQuantity': 0.0, 'unit': '个', 'status': 1, 'isUse': False, 'locations': [ + {'id': '3a19da56-1379-e77d-0e65-7463b238a3b9', 'whid': '3a19da56-1378-613b-29f2-871e1a287aa5', 'whName': '粉末加样头堆栈', 'code': '0005-0005', 'x': 5, 'y': 1, 'z': 1, 'quantity': 0 + } + ], 'detail': [] + } +] \ No newline at end of file diff --git a/unilabos/devices/electrolysis_water_platfrom/electrolysis_water_platfrom.py b/unilabos/devices/electrolysis_water_platfrom/electrolysis_water_platfrom.py new file mode 100644 index 0000000..cfcfdee --- /dev/null +++ b/unilabos/devices/electrolysis_water_platfrom/electrolysis_water_platfrom.py @@ -0,0 +1,296 @@ +# -*- coding: utf-8 -*- +import serial +import time +import csv +import threading +import os +from collections import deque +from typing import Dict, Any, Optional +from pylabrobot.resources import Deck + +from unilabos.devices.workstation.workstation_base import WorkstationBase + + +class ElectrolysisWaterPlatform(WorkstationBase): + """ + 电解水平台工作站 + 基于 WorkstationBase 的电解水实验平台,支持串口通信和数据采集 + """ + + def __init__( + self, + deck: Deck, + port: str = "COM10", + baudrate: int = 115200, + csv_path: Optional[str] = None, + timeout: float = 0.2, + **kwargs + ): + super().__init__(deck, **kwargs) + + # ========== 配置 ========== + self.port = port + self.baudrate = baudrate + # 如果没有指定路径,默认保存在代码文件所在目录 + if csv_path is None: + current_dir = os.path.dirname(os.path.abspath(__file__)) + self.csv_path = os.path.join(current_dir, "stm32_data.csv") + else: + self.csv_path = csv_path + self.ser_timeout = timeout + self.chunk_read = 128 + + # 串口对象 + self.ser: Optional[serial.Serial] = None + self.stop_flag = False + + # 线程对象 + self.rx_thread: Optional[threading.Thread] = None + self.tx_thread: Optional[threading.Thread] = None + + # ==== 接收(下位机->上位机):固定 1+13+1 = 15 字节 ==== + self.RX_HEAD = 0x3E + self.RX_TAIL = 0x3E + self.RX_FRAME_LEN = 1 + 13 + 1 # 15 + + # ==== 发送(上位机->下位机):固定 1+9+1 = 11 字节 ==== + self.TX_HEAD = 0x3E + self.TX_TAIL = 0xE3 # 协议图中标注 E3 作为帧尾 + self.TX_FRAME_LEN = 1 + 9 + 1 # 11 + + def open_serial(self, port: Optional[str] = None, baudrate: Optional[int] = None, timeout: Optional[float] = None) -> Optional[serial.Serial]: + """打开串口""" + port = port or self.port + baudrate = baudrate or self.baudrate + timeout = timeout or self.ser_timeout + try: + ser = serial.Serial(port, baudrate, timeout=timeout) + print(f"[OK] 串口 {port} 已打开,波特率 {baudrate}") + ser.reset_input_buffer() + ser.reset_output_buffer() + self.ser = ser + return ser + except serial.SerialException as e: + print(f"[ERR] 无法打开串口 {port}: {e}") + return None + + def close_serial(self): + """关闭串口""" + if self.ser and self.ser.is_open: + self.ser.close() + print("[INFO] 串口已关闭") + + @staticmethod + def u16_be(h: int, l: int) -> int: + """将两个字节组合成16位无符号整数(大端序)""" + return ((h & 0xFF) << 8) | (l & 0xFF) + + @staticmethod + def split_u16_be(val: int) -> tuple: + """返回 (高字节, 低字节),输入会夹到 0..65535""" + v = int(max(0, min(65535, int(val)))) + return (v >> 8) & 0xFF, v & 0xFF + + # ================== 接收:固定15字节 ================== + def parse_rx_payload(self, dat13: bytes) -> Optional[Dict[str, Any]]: + """解析 13 字节数据区(下位机发送到上位机)""" + if len(dat13) != 13: + return None + current_mA = self.u16_be(dat13[0], dat13[1]) + voltage_mV = self.u16_be(dat13[2], dat13[3]) + temperature_raw = self.u16_be(dat13[4], dat13[5]) + tds_ppm = self.u16_be(dat13[6], dat13[7]) + gas_sccm = self.u16_be(dat13[8], dat13[9]) + liquid_mL = self.u16_be(dat13[10], dat13[11]) + ph_raw = dat13[12] & 0xFF + + return { + "Current_mA": current_mA, + "Voltage_mV": voltage_mV, + "Temperature_C": round(temperature_raw / 100.0, 2), + "TDS_ppm": tds_ppm, + "GasFlow_sccm": gas_sccm, + "LiquidFlow_mL": liquid_mL, + "pH": round(ph_raw / 10.0, 2) + } + + def try_parse_rx_frame(self, frame15: bytes) -> Optional[Dict[str, Any]]: + """尝试解析接收帧""" + if len(frame15) != self.RX_FRAME_LEN: + return None + if frame15[0] != self.RX_HEAD or frame15[-1] != self.RX_TAIL: + return None + return self.parse_rx_payload(frame15[1:-1]) + + def rx_thread_fn(self): + """接收线程函数""" + headers = ["Timestamp", "Current_mA", "Voltage_mV", + "Temperature_C", "TDS_ppm", "GasFlow_sccm", "LiquidFlow_mL", "pH"] + + new_file = not os.path.exists(self.csv_path) + f = open(self.csv_path, mode='a', newline='', encoding='utf-8') + writer = csv.writer(f) + if new_file: + writer.writerow(headers) + f.flush() + + buf = deque(maxlen=8192) + print(f"[RX] 开始接收(帧长 {self.RX_FRAME_LEN} 字节);写入:{self.csv_path}") + + try: + while not self.stop_flag and self.ser and self.ser.is_open: + chunk = self.ser.read(self.chunk_read) + if chunk: + buf.extend(chunk) + while True: + # 找帧头 + try: + start = next(i for i, b in enumerate(buf) if b == self.RX_HEAD) + except StopIteration: + buf.clear() + break + if start > 0: + for _ in range(start): + buf.popleft() + if len(buf) < self.RX_FRAME_LEN: + break + candidate = bytes([buf[i] for i in range(self.RX_FRAME_LEN)]) + if candidate[-1] == self.RX_TAIL: + parsed = self.try_parse_rx_frame(candidate) + for _ in range(self.RX_FRAME_LEN): + buf.popleft() + if parsed: + ts = time.strftime("%Y-%m-%d %H:%M:%S") + row = [ts, + parsed["Current_mA"], parsed["Voltage_mV"], + parsed["Temperature_C"], parsed["TDS_ppm"], + parsed["GasFlow_sccm"], parsed["LiquidFlow_mL"], + parsed["pH"]] + writer.writerow(row) + f.flush() + # 若不想打印可注释下一行 + # print(f"[{ts}] I={parsed['Current_mA']} mA, V={parsed['Voltage_mV']} mV, " + # f"T={parsed['Temperature_C']} °C, TDS={parsed['TDS_ppm']}, " + # f"Gas={parsed['GasFlow_sccm']} sccm, Liq={parsed['LiquidFlow_mL']} mL, pH={parsed['pH']}") + else: + # 头不变,尾不对,丢1字节继续对齐 + buf.popleft() + else: + time.sleep(0.01) + finally: + f.close() + print("[RX] 接收线程退出,CSV 已关闭") + + # ================== 发送:固定11字节 ================== + def build_tx_frame(self, mode: int, current_ma: int, voltage_mv: int, temp_c: float, ki: float, pump_percent: float) -> bytes: + """ + 发送帧:HEAD + [mode, I_hi, I_lo, V_hi, V_lo, T_hi, T_lo, Ki_byte, Pump_byte] + TAIL + - mode: 0=恒压, 1=恒流 + - current_ma: mA (0..65535) + - voltage_mv: mV (0..65535) + - temp_c: ℃,将 *100 后拆分为高/低字节 + - ki: 0.0..20.0 -> byte = round(ki * 10) 夹到 0..200 + - pump_percent: 0..100 -> byte = round(pump * 2) 夹到 0..200 + """ + mode_b = 1 if int(mode) == 1 else 0 + + i_hi, i_lo = self.split_u16_be(current_ma) + v_hi, v_lo = self.split_u16_be(voltage_mv) + + t100 = int(round(float(temp_c) * 100.0)) + t_hi, t_lo = self.split_u16_be(t100) + + ki_b = int(max(0, min(200, round(float(ki) * 10)))) + pump_b = int(max(0, min(200, round(float(pump_percent) * 2)))) + + return bytes(( + self.TX_HEAD, + mode_b, + i_hi, i_lo, + v_hi, v_lo, + t_hi, t_lo, + ki_b, + pump_b, + self.TX_TAIL + )) + + def tx_thread_fn(self): + """ + 发送线程函数 + 用户输入 6 个用逗号分隔的数值: + mode,current_mA,voltage_mV,set_temp_C,Ki,pump_percent + 例如: 0,1000,500,0,0,50 + """ + print("\n输入 6 个值(用英文逗号分隔),顺序为:") + print("mode,current_mA,voltage_mV,set_temp_C,Ki,pump_percent") + print("示例恒压:0,500,1000,25,0,100 (stop 结束)\n") + print("示例恒流:1,1000,500,25,0,100 (stop 结束)\n") + print("示例恒流:1,2000,500,25,0,100 (stop 结束)\n") + # 1,2000,500,25,0,100 + + while not self.stop_flag and self.ser and self.ser.is_open: + try: + line = input(">>> ").strip() + except EOFError: + self.stop_flag = True + break + + if not line: + continue + if line.lower() == "stop": + self.stop_flag = True + print("[SYS] 停止程序") + break + + try: + parts = [p.strip() for p in line.split(",")] + if len(parts) != 6: + raise ValueError("需要 6 个逗号分隔的数值") + mode = int(parts[0]) + i_ma = int(float(parts[1])) + v_mv = int(float(parts[2])) + t_c = float(parts[3]) + ki = float(parts[4]) + pump = float(parts[5]) + + frame = self.build_tx_frame(mode, i_ma, v_mv, t_c, ki, pump) + self.ser.write(frame) + print("[TX]", " ".join(f"{b:02X}" for b in frame)) + except Exception as e: + print("[TX] 输入/打包失败:", e) + print("格式:mode,current_mA,voltage_mV,set_temp_C,Ki,pump_percent") + continue + + def start(self): + """启动电解水平台""" + self.ser = self.open_serial() + if self.ser: + try: + self.rx_thread = threading.Thread(target=self.rx_thread_fn, daemon=True) + self.tx_thread = threading.Thread(target=self.tx_thread_fn, daemon=True) + self.rx_thread.start() + self.tx_thread.start() + print("[INFO] 电解水平台已启动") + self.tx_thread.join() # 等待用户输入线程结束(输入 stop) + finally: + self.close_serial() + + def stop(self): + """停止电解水平台""" + self.stop_flag = True + if self.rx_thread and self.rx_thread.is_alive(): + self.rx_thread.join(timeout=2.0) + if self.tx_thread and self.tx_thread.is_alive(): + self.tx_thread.join(timeout=2.0) + self.close_serial() + print("[INFO] 电解水平台已停止") + + +# ================== 主入口 ================== +if __name__ == "__main__": + # 创建一个简单的 Deck 用于测试 + from pylabrobot.resources import Deck + + deck = Deck() + platform = ElectrolysisWaterPlatform(deck) + platform.start() diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index 7888039..c60a03e 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -10,7 +10,6 @@ from datetime import datetime, timedelta import re import threading import json - from urllib3 import response from unilabos.devices.workstation.workstation_base import WorkstationBase from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation, BioyondResourceSynchronizer @@ -19,6 +18,8 @@ from unilabos.devices.workstation.bioyond_studio.config import ( ) from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService from unilabos.utils.log import logger +from unilabos.registry.registry import lab_registry + def _iso_local_now_ms() -> str: # 文档要求:到毫秒 + Z,例如 2025-08-15T05:43:22.814Z @@ -967,22 +968,23 @@ class BioyondCellWorkstation(BioyondWorkstation): if __name__ == "__main__": + lab_registry.setup() ws = BioyondCellWorkstation() - logger.info(ws.scheduler_stop()) + # logger.info(ws.scheduler_stop()) + logger.info(ws.scheduler_start()) - - results = ws.create_materials(SOLID_LIQUID_MAPPINGS) - for r in results: - logger.info(r) + # results = ws.create_materials(SOLID_LIQUID_MAPPINGS) + # for r in results: + # logger.info(r) # 从CSV文件读取物料列表并批量创建入库 - result = ws.create_and_inbound_materials() + # result = ws.create_and_inbound_materials() # 继续后续流程 - # logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 + logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 # # 使用正斜杠或 Path 对象来指定文件路径 - # excel_path = Path("unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025092701.xlsx") - # logger.info(ws.create_orders(excel_path)) - # logger.info(ws.transfer_3_to_2_to_1()) + excel_path = Path("unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025092701.xlsx") + logger.info(ws.create_orders(excel_path)) + logger.info(ws.transfer_3_to_2_to_1()) # logger.info(ws.transfer_1_to_2()) # logger.info(ws.scheduler_start()) diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 3bf6250..81a9736 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -5,13 +5,10 @@ import os # ==================== API 基础配置 ==================== - - -# ==================== 完整的 Bioyond 配置 ==================== # BioyondCellWorkstation 默认配置(包含所有必需参数) API_CONFIG = { # API 连接配置 - "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.11.219:44388"), + "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.10.169:44388"), "api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"), "timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")), @@ -19,11 +16,9 @@ API_CONFIG = { "report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"), # HTTP 服务配置 - "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "0.0.0.0"), # HTTP服务监听地址(0.0.0.0 表示监听所有网络接口) + "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.21.32.91"), # HTTP服务监听地址,监听计算机飞连ip地址 "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), - "report_ip": os.getenv("BIOYOND_REPORT_IP", "172.21.32.172"), # 报送给 Bioyond 的本机IP地址(留空则自动检测) - # 调试模式 - "debug_mode": False, + "debug_mode": False,# 调试模式 } # 库位映射配置 @@ -57,29 +52,10 @@ WAREHOUSE_MAPPING = { # 物料类型配置 MATERIAL_TYPE_MAPPINGS = { - "烧杯": ("BIOYOND_PolymerStation_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"), - "试剂瓶": ("BIOYOND_PolymerStation_1BottleCarrier", ""), - "样品板": ("BIOYOND_PolymerStation_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"), - "分装板": ("BIOYOND_PolymerStation_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), - "样品瓶": ("BIOYOND_PolymerStation_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"), - "90%分装小瓶": ("BIOYOND_PolymerStation_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"), - "10%分装小瓶": ("BIOYOND_PolymerStation_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"), - "20ml分液瓶": ("BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), - "100ml液体": ("BIOYOND_PolymerStation_100ml_Liquid_Bottle", "d37166b3-ecaa-481e-bd84-3032b795ba07"), - "液": ("BIOYOND_PolymerStation_Liquid_Bottle", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), - "高粘液": ("BIOYOND_PolymerStation_High_Viscosity_Liquid_Bottle", "abe8df30-563d-43d2-85e0-cabec59ddc16"), - "加样头(大)": ("BIOYOND_PolymerStation_Large_Dispense_Head", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), - "5ml分液瓶板": ("BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"), - "5ml分液瓶": ("BIOYOND_PolymerStation_5ml_Dispensing_Vial", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"), - "20ml分液瓶板": ("BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier", "3a192fa4-47db-3449-162a-eaf8aba57e27"), - "配液瓶(小)板": ("BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier", "3a190c8b-3284-af78-d29f-9a69463ad047"), - "配液瓶(小)": ("BIOYOND_PolymerStation_Small_Solution_Bottle", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"), - "配液瓶(大)板": ("BIOYOND_PolymerStation_4x_LargeSolutionBottleCarrier", "53e50377-32dc-4781-b3c0-5ce45bc7dc27"), - "配液瓶(大)": ("BIOYOND_PolymerStation_Large_Solution_Bottle", "19c52ad1-51c5-494f-8854-576f4ca9c6ca"), - "加样头(大)板": ("BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"), - "适配器块": ("BIOYOND_PolymerStation_AdapterBlock", "efc3bb32-d504-4890-91c0-b64ed3ac80cf"), - "枪头盒": ("BIOYOND_PolymerStation_TipBox", "3a192c2e-20f3-a44a-0334-c8301839d0b3"), - "枪头": ("BIOYOND_PolymerStation_Pipette_Tip", "b6196971-1050-46da-9927-333e8dea062d"), + + "加样头(大)": ("YB_jia_yang_tou_da", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + "加样头(大)板": ("YB_jia_yang_tou_da_1X1_carrier", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"), + # YB信息 } SOLID_LIQUID_MAPPINGS = { diff --git a/unilabos/devices/workstation/bioyond_studio/station.py b/unilabos/devices/workstation/bioyond_studio/station.py index 795b740..3ce7353 100644 --- a/unilabos/devices/workstation/bioyond_studio/station.py +++ b/unilabos/devices/workstation/bioyond_studio/station.py @@ -74,6 +74,7 @@ class BioyondResourceSynchronizer(ResourceSynchronizer): type_mapping=self.workstation.bioyond_config["material_type_mappings"], deck=self.workstation.deck ) + print("unilab_resources:",unilab_resources) logger.info(f"从Bioyond同步了 {len(unilab_resources)} 个资源") return True diff --git a/unilabos/registry/resources/bioyond/bottle_carriers.yaml b/unilabos/registry/resources/bioyond/bottle_carriers.yaml index 5889bcc..ac01cc3 100644 --- a/unilabos/registry/resources/bioyond/bottle_carriers.yaml +++ b/unilabos/registry/resources/bioyond/bottle_carriers.yaml @@ -1,130 +1,10 @@ -1BottleCarrier: - category: - - bottle_carriers - class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_1BottleCarrier - type: pylabrobot - description: 1BottleCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -1FlaskCarrier: - category: - - bottle_carriers - class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_1FlaskCarrier - type: pylabrobot - description: 1FlaskCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -6StockCarrier: - category: - - bottle_carriers - class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6StockCarrier - type: pylabrobot - description: 6StockCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -6VialCarrier: - category: - - bottle_carriers - class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6VialCarrier - type: pylabrobot - description: 6VialCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -6x5ml_DispensingVialCarrier: +YB_jia_yang_tou_da_1X1_carrier: category: - yb3 class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier + module: unilabos.resources.bioyond.bottle_carriers:YB_jia_yang_tou_da_1X1_carrier type: pylabrobot - description: 6x5ml_DispensingVialCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -6x20ml_DispensingVialCarrier: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier - type: pylabrobot - description: 6x20ml_DispensingVialCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -6x_SmallSolutionBottleCarrier: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier - type: pylabrobot - description: 6x_SmallSolutionBottleCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -4x_LargeSolutionBottleCarrier: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_4x_LargeSolutionBottleCarrier - type: pylabrobot - description: 4x_LargeSolutionBottleCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -6x_LargeDispenseHeadCarrier: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier - type: pylabrobot - description: 6x_LargeDispenseHeadCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -AdapterBlock: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_AdapterBlock - type: pylabrobot - description: AdapterBlock - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -TipBox: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_TipBox - type: pylabrobot - description: TipBox + description: YB_jia_yang_tou_da_1X1_carrier handles: [] icon: '' init_param_schema: {} diff --git a/unilabos/registry/resources/bioyond/bottles.yaml b/unilabos/registry/resources/bioyond/bottles.yaml index 8d71f28..556b5f8 100644 --- a/unilabos/registry/resources/bioyond/bottles.yaml +++ b/unilabos/registry/resources/bioyond/bottles.yaml @@ -1,138 +1,8 @@ -Liquid_Vial: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Liquid_Vial - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -Reagent_Bottle: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Reagent_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -Solid_Stock: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Solid_Stock - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -Solid_Vial: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Solid_Vial - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -Solution_Beaker: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Solution_Beaker - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -100ml_Liquid_Bottle: +YB_jia_yang_tou_da: category: - yb3 class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_100ml_Liquid_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -Liquid_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Liquid_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -High_Viscosity_Liquid_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_High_Viscosity_Liquid_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -Large_Dispense_Head: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Large_Dispense_Head - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -5ml_Dispensing_Vial: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_5ml_Dispensing_Vial - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -20ml_Dispensing_Vial: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_20ml_Dispensing_Vial - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -Small_Solution_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Small_Solution_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -Large_Solution_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Large_Solution_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -Pipette_Tip: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Pipette_Tip + module: unilabos.resources.bioyond.bottles:YB_jia_yang_tou_da type: pylabrobot handles: [] icon: '' diff --git a/unilabos/resources/bioyond/bottle_carriers copy.py b/unilabos/resources/bioyond/bottle_carriers copy.py new file mode 100644 index 0000000..a3b0044 --- /dev/null +++ b/unilabos/resources/bioyond/bottle_carriers copy.py @@ -0,0 +1,148 @@ +from pylabrobot.resources import create_homogeneous_resources, Coordinate, ResourceHolder, create_ordered_items_2d + +from unilabos.resources.itemized_carrier import Bottle, BottleCarrier +from unilabos.resources.bioyond.bottles import ( + YB_jia_yang_tou_da, +) +# 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial + + + + + +def YB_jia_yang_tou_da_1X1_carrier(name: str) -> BottleCarrier: + """加样头(大)板 - 1x1布局,1个位置""" + + # 载架尺寸 (mm) + carrier_size_x = 127.8 + carrier_size_y = 85.5 + carrier_size_z = 95.0 + + # 瓶位尺寸 + bottle_diameter = 35.0 + bottle_spacing_x = 42.0 # X方向间距 + bottle_spacing_y = 35.0 # Y方向间距 + + # 计算起始位置 (居中排列) + start_x = (carrier_size_x - (1 - 1) * bottle_spacing_x - bottle_diameter) / 2 + start_y = (carrier_size_y - (1 - 1) * bottle_spacing_y - bottle_diameter) / 2 + + sites = create_ordered_items_2d( + klass=ResourceHolder, + num_items_x=1, + num_items_y=1, + dx=start_x, + dy=start_y, + dz=5.0, + item_dx=bottle_spacing_x, + item_dy=bottle_spacing_y, + size_x=bottle_diameter, + size_y=bottle_diameter, + size_z=carrier_size_z, + ) + for k, v in sites.items(): + v.name = f"{name}_{v.name}" + + carrier = BottleCarrier( + name=name, + size_x=carrier_size_x, + size_y=carrier_size_y, + size_z=carrier_size_z, + sites=sites, + model="YB_1X1_jia_yang_tou_da_carrier", + ) + carrier.num_items_x = 1 + carrier.num_items_y = 1 + carrier.num_items_z = 1 + carrier[0] = YB_jia_yang_tou_da(f"{name}_head_1") + return carrier + + +def BIOYOND_PolymerStation_AdapterBlock(name: str) -> BottleCarrier: + """适配器块 - 单个中央位置""" + + # 载架尺寸 (mm) + carrier_size_x = 127.8 + carrier_size_y = 85.5 + carrier_size_z = 30.0 + + # 适配器尺寸 + adapter_diameter = 80.0 + + # 计算中央位置 + center_x = (carrier_size_x - adapter_diameter) / 2 + center_y = (carrier_size_y - adapter_diameter) / 2 + center_z = 0.0 + + carrier = BottleCarrier( + name=name, + size_x=carrier_size_x, + size_y=carrier_size_y, + size_z=carrier_size_z, + sites=create_homogeneous_resources( + klass=ResourceHolder, + locations=[Coordinate(center_x, center_y, center_z)], + resource_size_x=adapter_diameter, + resource_size_y=adapter_diameter, + name_prefix=name, + ), + model="AdapterBlock", + ) + carrier.num_items_x = 1 + carrier.num_items_y = 1 + carrier.num_items_z = 1 + # 适配器块本身不包含瓶子,只是一个支撑结构 + return carrier + + +def BIOYOND_PolymerStation_TipBox(name: str) -> BottleCarrier: + """枪头盒 - 8x12布局,96个位置""" + + # 载架尺寸 (mm) + carrier_size_x = 127.8 + carrier_size_y = 85.5 + carrier_size_z = 55.0 + + # 枪头尺寸 + tip_diameter = 10.0 + tip_spacing_x = 9.0 # X方向间距 + tip_spacing_y = 9.0 # Y方向间距 + + # 计算起始位置 (居中排列) + start_x = (carrier_size_x - (12 - 1) * tip_spacing_x - tip_diameter) / 2 + start_y = (carrier_size_y - (8 - 1) * tip_spacing_y - tip_diameter) / 2 + + sites = create_ordered_items_2d( + klass=ResourceHolder, + num_items_x=12, + num_items_y=8, + dx=start_x, + dy=start_y, + dz=5.0, + item_dx=tip_spacing_x, + item_dy=tip_spacing_y, + size_x=tip_diameter, + size_y=tip_diameter, + size_z=carrier_size_z, + ) + for k, v in sites.items(): + v.name = f"{name}_{v.name}" + + carrier = BottleCarrier( + name=name, + size_x=carrier_size_x, + size_y=carrier_size_y, + size_z=carrier_size_z, + sites=sites, + model="TipBox", + ) + carrier.num_items_x = 12 + carrier.num_items_y = 8 + carrier.num_items_z = 1 + # 创建96个枪头 + for i in range(96): + row = chr(65 + i // 12) # A-H + col = (i % 12) + 1 # 1-12 + carrier[i] = BIOYOND_PolymerStation_Pipette_Tip(f"{name}_tip_{row}{col}") + return carrier + diff --git a/unilabos/resources/bioyond/bottle_carriers.py b/unilabos/resources/bioyond/bottle_carriers.py deleted file mode 100644 index ab0b656..0000000 --- a/unilabos/resources/bioyond/bottle_carriers.py +++ /dev/null @@ -1,620 +0,0 @@ -from pylabrobot.resources import create_homogeneous_resources, Coordinate, ResourceHolder, create_ordered_items_2d - -from unilabos.resources.itemized_carrier import Bottle, BottleCarrier -from unilabos.resources.bioyond.bottles import ( - BIOYOND_PolymerStation_Solid_Stock, - BIOYOND_PolymerStation_Solid_Vial, - BIOYOND_PolymerStation_Liquid_Vial, - BIOYOND_PolymerStation_Solution_Beaker, - BIOYOND_PolymerStation_Reagent_Bottle, - BIOYOND_PolymerStation_5ml_Dispensing_Vial, - BIOYOND_PolymerStation_20ml_Dispensing_Vial, - BIOYOND_PolymerStation_Small_Solution_Bottle, - BIOYOND_PolymerStation_Large_Solution_Bottle, - BIOYOND_PolymerStation_Large_Dispense_Head, - BIOYOND_PolymerStation_Pipette_Tip -) -# 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial - - -def BIOYOND_Electrolyte_6VialCarrier(name: str) -> BottleCarrier: - """6瓶载架 - 2x3布局""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 50.0 - - # 瓶位尺寸 - bottle_diameter = 30.0 - bottle_spacing_x = 42.0 # X方向间距 - bottle_spacing_y = 35.0 # Y方向间距 - - # 计算起始位置 (居中排列) - start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2 - start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2 - - sites = create_ordered_items_2d( - klass=ResourceHolder, - num_items_x=3, - num_items_y=2, - dx=start_x, - dy=start_y, - dz=5.0, - item_dx=bottle_spacing_x, - item_dy=bottle_spacing_y, - - size_x=bottle_diameter, - size_y=bottle_diameter, - size_z=carrier_size_z, - ) - for k, v in sites.items(): - v.name = f"{name}_{v.name}" - - carrier = BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=sites, - model="Electrolyte_6VialCarrier", - ) - carrier.num_items_x = 3 - carrier.num_items_y = 2 - carrier.num_items_z = 1 - for i in range(6): - carrier[i] = BIOYOND_PolymerStation_Solid_Vial(f"{name}_vial_{i+1}") - return carrier - - -def BIOYOND_Electrolyte_1BottleCarrier(name: str) -> BottleCarrier: - """1瓶载架 - 单个中央位置""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 100.0 - - # 烧杯尺寸 - beaker_diameter = 80.0 - - # 计算中央位置 - center_x = (carrier_size_x - beaker_diameter) / 2 - center_y = (carrier_size_y - beaker_diameter) / 2 - center_z = 5.0 - - carrier = BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=create_homogeneous_resources( - klass=ResourceHolder, - locations=[Coordinate(center_x, center_y, center_z)], - resource_size_x=beaker_diameter, - resource_size_y=beaker_diameter, - name_prefix=name, - ), - model="Electrolyte_1BottleCarrier", - ) - carrier.num_items_x = 1 - carrier.num_items_y = 1 - carrier.num_items_z = 1 - carrier[0] = BIOYOND_PolymerStation_Solution_Beaker(f"{name}_beaker_1") - return carrier - - -def BIOYOND_PolymerStation_6StockCarrier(name: str) -> BottleCarrier: - """6瓶载架 - 2x3布局""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 50.0 - - # 瓶位尺寸 - bottle_diameter = 20.0 - bottle_spacing_x = 42.0 # X方向间距 - bottle_spacing_y = 35.0 # Y方向间距 - - # 计算起始位置 (居中排列) - start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2 - start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2 - - sites = create_ordered_items_2d( - klass=ResourceHolder, - num_items_x=3, - num_items_y=2, - dx=start_x, - dy=start_y, - dz=5.0, - item_dx=bottle_spacing_x, - item_dy=bottle_spacing_y, - - size_x=bottle_diameter, - size_y=bottle_diameter, - size_z=carrier_size_z, - ) - for k, v in sites.items(): - v.name = f"{name}_{v.name}" - - carrier = BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=sites, - model="6StockCarrier", - ) - carrier.num_items_x = 3 - carrier.num_items_y = 2 - carrier.num_items_z = 1 - ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] # 自定义顺序 - for i in range(6): - carrier[i] = BIOYOND_PolymerStation_Solid_Stock(f"{name}_vial_{ordering[i]}") - return carrier - - -def BIOYOND_PolymerStation_6VialCarrier(name: str) -> BottleCarrier: - """6瓶载架 - 2x3布局""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 50.0 - - # 瓶位尺寸 - bottle_diameter = 30.0 - bottle_spacing_x = 42.0 # X方向间距 - bottle_spacing_y = 35.0 # Y方向间距 - - # 计算起始位置 (居中排列) - start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2 - start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2 - - sites = create_ordered_items_2d( - klass=ResourceHolder, - num_items_x=3, - num_items_y=2, - dx=start_x, - dy=start_y, - dz=5.0, - item_dx=bottle_spacing_x, - item_dy=bottle_spacing_y, - - size_x=bottle_diameter, - size_y=bottle_diameter, - size_z=carrier_size_z, - ) - for k, v in sites.items(): - v.name = f"{name}_{v.name}" - - carrier = BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=sites, - model="6VialCarrier", - ) - carrier.num_items_x = 3 - carrier.num_items_y = 2 - carrier.num_items_z = 1 - ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] # 自定义顺序 - for i in range(3): - carrier[i] = BIOYOND_PolymerStation_Solid_Vial(f"{name}_solidvial_{ordering[i]}") - for i in range(3, 6): - carrier[i] = BIOYOND_PolymerStation_Liquid_Vial(f"{name}_liquidvial_{ordering[i]}") - return carrier - - -def BIOYOND_PolymerStation_1BottleCarrier(name: str) -> BottleCarrier: - """1瓶载架 - 单个中央位置""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 20.0 - - # 烧杯尺寸 - beaker_diameter = 60.0 - - # 计算中央位置 - center_x = (carrier_size_x - beaker_diameter) / 2 - center_y = (carrier_size_y - beaker_diameter) / 2 - center_z = 5.0 - - carrier = BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=create_homogeneous_resources( - klass=ResourceHolder, - locations=[Coordinate(center_x, center_y, center_z)], - resource_size_x=beaker_diameter, - resource_size_y=beaker_diameter, - name_prefix=name, - ), - model="1BottleCarrier", - ) - carrier.num_items_x = 1 - carrier.num_items_y = 1 - carrier.num_items_z = 1 - carrier[0] = BIOYOND_PolymerStation_Reagent_Bottle(f"{name}_flask_1") - return carrier - - -def BIOYOND_PolymerStation_1FlaskCarrier(name: str) -> BottleCarrier: - """1瓶载架 - 单个中央位置""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 20.0 - - # 烧杯尺寸 - beaker_diameter = 70.0 - - # 计算中央位置 - center_x = (carrier_size_x - beaker_diameter) / 2 - center_y = (carrier_size_y - beaker_diameter) / 2 - center_z = 5.0 - - carrier = BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=create_homogeneous_resources( - klass=ResourceHolder, - locations=[Coordinate(center_x, center_y, center_z)], - resource_size_x=beaker_diameter, - resource_size_y=beaker_diameter, - name_prefix=name, - ), - model="1FlaskCarrier", - ) - carrier.num_items_x = 1 - carrier.num_items_y = 1 - carrier.num_items_z = 1 - carrier[0] = BIOYOND_PolymerStation_Reagent_Bottle(f"{name}_bottle_1") - return carrier - - -def BIOYOND_PolymerStation_6x5ml_DispensingVialCarrier(name: str) -> BottleCarrier: - """5ml分液瓶板 - 4x2布局,8个位置""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 50.0 - - # 瓶位尺寸 - bottle_diameter = 15.0 - bottle_spacing_x = 42.0 # X方向间距 - bottle_spacing_y = 35.0 # Y方向间距 - - # 计算起始位置 (居中排列) - start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2 - start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2 - - sites = create_ordered_items_2d( - klass=ResourceHolder, - num_items_x=4, - num_items_y=2, - dx=start_x, - dy=start_y, - dz=5.0, - item_dx=bottle_spacing_x, - item_dy=bottle_spacing_y, - size_x=bottle_diameter, - size_y=bottle_diameter, - size_z=carrier_size_z, - ) - for k, v in sites.items(): - v.name = f"{name}_{v.name}" - - carrier = BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=sites, - model="6x5ml_DispensingVialCarrier", - ) - carrier.num_items_x = 4 - carrier.num_items_y = 2 - carrier.num_items_z = 1 - ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"] - for i in range(8): - carrier[i] = BIOYOND_PolymerStation_5ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}") - return carrier - - -def BIOYOND_PolymerStation_6x20ml_DispensingVialCarrier(name: str) -> BottleCarrier: - """20ml分液瓶板 - 4x2布局,8个位置""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 70.0 - - # 瓶位尺寸 - bottle_diameter = 20.0 - bottle_spacing_x = 42.0 # X方向间距 - bottle_spacing_y = 35.0 # Y方向间距 - - # 计算起始位置 (居中排列) - start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2 - start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2 - - sites = create_ordered_items_2d( - klass=ResourceHolder, - num_items_x=4, - num_items_y=2, - dx=start_x, - dy=start_y, - dz=5.0, - item_dx=bottle_spacing_x, - item_dy=bottle_spacing_y, - size_x=bottle_diameter, - size_y=bottle_diameter, - size_z=carrier_size_z, - ) - for k, v in sites.items(): - v.name = f"{name}_{v.name}" - - carrier = BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=sites, - model="6x20ml_DispensingVialCarrier", - ) - carrier.num_items_x = 4 - carrier.num_items_y = 2 - carrier.num_items_z = 1 - ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"] - for i in range(8): - carrier[i] = BIOYOND_PolymerStation_20ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}") - return carrier - - -def BIOYOND_PolymerStation_6x_SmallSolutionBottleCarrier(name: str) -> BottleCarrier: - """配液瓶(小)板 - 4x2布局,8个位置""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 65.0 - - # 瓶位尺寸 - bottle_diameter = 35.0 - bottle_spacing_x = 42.0 # X方向间距 - bottle_spacing_y = 35.0 # Y方向间距 - - # 计算起始位置 (居中排列) - start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2 - start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2 - - sites = create_ordered_items_2d( - klass=ResourceHolder, - num_items_x=4, - num_items_y=2, - dx=start_x, - dy=start_y, - dz=5.0, - item_dx=bottle_spacing_x, - item_dy=bottle_spacing_y, - size_x=bottle_diameter, - size_y=bottle_diameter, - size_z=carrier_size_z, - ) - for k, v in sites.items(): - v.name = f"{name}_{v.name}" - - carrier = BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=sites, - model="6x_SmallSolutionBottleCarrier", - ) - carrier.num_items_x = 4 - carrier.num_items_y = 2 - carrier.num_items_z = 1 - ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"] - for i in range(8): - carrier[i] = BIOYOND_PolymerStation_Small_Solution_Bottle(f"{name}_bottle_{ordering[i]}") - return carrier - - -def BIOYOND_PolymerStation_4x_LargeSolutionBottleCarrier(name: str) -> BottleCarrier: - """配液瓶(大)板 - 2x2布局,4个位置""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 95.0 - - # 瓶位尺寸 - bottle_diameter = 55.0 - bottle_spacing_x = 60.0 # X方向间距 - bottle_spacing_y = 60.0 # Y方向间距 - - # 计算起始位置 (居中排列) - start_x = (carrier_size_x - (2 - 1) * bottle_spacing_x - bottle_diameter) / 2 - start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2 - - sites = create_ordered_items_2d( - klass=ResourceHolder, - num_items_x=2, - num_items_y=2, - dx=start_x, - dy=start_y, - dz=5.0, - item_dx=bottle_spacing_x, - item_dy=bottle_spacing_y, - size_x=bottle_diameter, - size_y=bottle_diameter, - size_z=carrier_size_z, - ) - for k, v in sites.items(): - v.name = f"{name}_{v.name}" - - carrier = BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=sites, - model="4x_LargeSolutionBottleCarrier", - ) - carrier.num_items_x = 2 - carrier.num_items_y = 2 - carrier.num_items_z = 1 - ordering = ["A1", "A2", "B1", "B2"] - for i in range(4): - carrier[i] = BIOYOND_PolymerStation_Large_Solution_Bottle(f"{name}_bottle_{ordering[i]}") - return carrier - - -def BIOYOND_PolymerStation_6x_LargeDispenseHeadCarrier(name: str) -> BottleCarrier: - """加样头(大)板 - 1x1布局,1个位置""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 95.0 - - # 瓶位尺寸 - bottle_diameter = 35.0 - bottle_spacing_x = 42.0 # X方向间距 - bottle_spacing_y = 35.0 # Y方向间距 - - # 计算起始位置 (居中排列) - start_x = (carrier_size_x - (1 - 1) * bottle_spacing_x - bottle_diameter) / 2 - start_y = (carrier_size_y - (1 - 1) * bottle_spacing_y - bottle_diameter) / 2 - - sites = create_ordered_items_2d( - klass=ResourceHolder, - num_items_x=1, - num_items_y=1, - dx=start_x, - dy=start_y, - dz=5.0, - item_dx=bottle_spacing_x, - item_dy=bottle_spacing_y, - size_x=bottle_diameter, - size_y=bottle_diameter, - size_z=carrier_size_z, - ) - for k, v in sites.items(): - v.name = f"{name}_{v.name}" - - carrier = BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=sites, - model="6x_LargeDispenseHeadCarrier", - ) - carrier.num_items_x = 1 - carrier.num_items_y = 1 - carrier.num_items_z = 1 - carrier[0] = BIOYOND_PolymerStation_Large_Dispense_Head(f"{name}_head_1") - return carrier - - -def BIOYOND_PolymerStation_AdapterBlock(name: str) -> BottleCarrier: - """适配器块 - 单个中央位置""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 30.0 - - # 适配器尺寸 - adapter_diameter = 80.0 - - # 计算中央位置 - center_x = (carrier_size_x - adapter_diameter) / 2 - center_y = (carrier_size_y - adapter_diameter) / 2 - center_z = 0.0 - - carrier = BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=create_homogeneous_resources( - klass=ResourceHolder, - locations=[Coordinate(center_x, center_y, center_z)], - resource_size_x=adapter_diameter, - resource_size_y=adapter_diameter, - name_prefix=name, - ), - model="AdapterBlock", - ) - carrier.num_items_x = 1 - carrier.num_items_y = 1 - carrier.num_items_z = 1 - # 适配器块本身不包含瓶子,只是一个支撑结构 - return carrier - - -def BIOYOND_PolymerStation_TipBox(name: str) -> BottleCarrier: - """枪头盒 - 8x12布局,96个位置""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 55.0 - - # 枪头尺寸 - tip_diameter = 10.0 - tip_spacing_x = 9.0 # X方向间距 - tip_spacing_y = 9.0 # Y方向间距 - - # 计算起始位置 (居中排列) - start_x = (carrier_size_x - (12 - 1) * tip_spacing_x - tip_diameter) / 2 - start_y = (carrier_size_y - (8 - 1) * tip_spacing_y - tip_diameter) / 2 - - sites = create_ordered_items_2d( - klass=ResourceHolder, - num_items_x=12, - num_items_y=8, - dx=start_x, - dy=start_y, - dz=5.0, - item_dx=tip_spacing_x, - item_dy=tip_spacing_y, - size_x=tip_diameter, - size_y=tip_diameter, - size_z=carrier_size_z, - ) - for k, v in sites.items(): - v.name = f"{name}_{v.name}" - - carrier = BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=sites, - model="TipBox", - ) - carrier.num_items_x = 12 - carrier.num_items_y = 8 - carrier.num_items_z = 1 - # 创建96个枪头 - for i in range(96): - row = chr(65 + i // 12) # A-H - col = (i % 12) + 1 # 1-12 - carrier[i] = BIOYOND_PolymerStation_Pipette_Tip(f"{name}_tip_{row}{col}") - return carrier - diff --git a/unilabos/resources/bioyond/bottles copy.py b/unilabos/resources/bioyond/bottles copy.py new file mode 100644 index 0000000..2504c37 --- /dev/null +++ b/unilabos/resources/bioyond/bottles copy.py @@ -0,0 +1,24 @@ +from unilabos.resources.itemized_carrier import Bottle, BottleCarrier +# 工厂函数 + + +def YB_jia_yang_tou_da( + name: str, + diameter: float = 35.0, + height: float = 90.0, + max_volume: float = 50000.0, # 50mL + code: str = None, + barcode: str = None, +) -> Bottle: + """创建加样头(大)""" + return Bottle( + name=name, + diameter=diameter, + height=height, + max_volume=max_volume, + barcode=barcode, + code=code, + model="YB_jia_yang_tou_da", + ) + + diff --git a/unilabos/resources/bioyond/bottles.py b/unilabos/resources/bioyond/bottles.py deleted file mode 100644 index 40cb9ef..0000000 --- a/unilabos/resources/bioyond/bottles.py +++ /dev/null @@ -1,255 +0,0 @@ -from unilabos.resources.itemized_carrier import Bottle, BottleCarrier -# 工厂函数 - - -def BIOYOND_PolymerStation_Solid_Stock( - name: str, - diameter: float = 20.0, - height: float = 100.0, - max_volume: float = 30000.0, # 30mL - barcode: str = None, -) -> Bottle: - """创建粉末瓶""" - return Bottle( - name=name, - diameter=diameter,# 未知 - height=height, - max_volume=max_volume, - barcode=barcode, - model="Solid_Stock", - ) - - -def BIOYOND_PolymerStation_Solid_Vial( - name: str, - diameter: float = 25.0, - height: float = 60.0, - max_volume: float = 30000.0, # 30mL - barcode: str = None, -) -> Bottle: - """创建粉末瓶""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="Solid_Vial", - ) - - -def BIOYOND_PolymerStation_Liquid_Vial( - name: str, - diameter: float = 25.0, - height: float = 60.0, - max_volume: float = 30000.0, # 30mL - barcode: str = None, -) -> Bottle: - """创建滴定液瓶""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="Liquid_Vial", - ) - - -def BIOYOND_PolymerStation_Solution_Beaker( - name: str, - diameter: float = 60.0, - height: float = 70.0, - max_volume: float = 200000.0, # 200mL - barcode: str = None, -) -> Bottle: - """创建溶液烧杯""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="Solution_Beaker", - ) - - -def BIOYOND_PolymerStation_Reagent_Bottle( - name: str, - diameter: float = 70.0, - height: float = 120.0, - max_volume: float = 500000.0, # 500mL - barcode: str = None, -) -> Bottle: - """创建试剂瓶""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="Reagent_Bottle", - ) - - -def BIOYOND_PolymerStation_100ml_Liquid_Bottle( - name: str, - diameter: float = 50.0, - height: float = 80.0, - max_volume: float = 100000.0, # 100mL - barcode: str = None, -) -> Bottle: - """创建100ml液体瓶""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="100ml_Liquid_Bottle", - ) - - -def BIOYOND_PolymerStation_Liquid_Bottle( - name: str, - diameter: float = 40.0, - height: float = 70.0, - max_volume: float = 50000.0, # 50mL - barcode: str = None, -) -> Bottle: - """创建液体瓶""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="Liquid_Bottle", - ) - - -def BIOYOND_PolymerStation_High_Viscosity_Liquid_Bottle( - name: str, - diameter: float = 45.0, - height: float = 75.0, - max_volume: float = 60000.0, # 60mL - barcode: str = None, -) -> Bottle: - """创建高粘液瓶""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="High_Viscosity_Liquid_Bottle", - ) - - -def BIOYOND_PolymerStation_Large_Dispense_Head( - name: str, - diameter: float = 35.0, - height: float = 90.0, - max_volume: float = 50000.0, # 50mL - barcode: str = None, -) -> Bottle: - """创建加样头(大)""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="Large_Dispense_Head", - ) - - -def BIOYOND_PolymerStation_5ml_Dispensing_Vial( - name: str, - diameter: float = 15.0, - height: float = 45.0, - max_volume: float = 5000.0, # 5mL - barcode: str = None, -) -> Bottle: - """创建5ml分液瓶""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="5ml_Dispensing_Vial", - ) - - -def BIOYOND_PolymerStation_20ml_Dispensing_Vial( - name: str, - diameter: float = 20.0, - height: float = 65.0, - max_volume: float = 20000.0, # 20mL - barcode: str = None, -) -> Bottle: - """创建20ml分液瓶""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="20ml_Dispensing_Vial", - ) - - -def BIOYOND_PolymerStation_Small_Solution_Bottle( - name: str, - diameter: float = 35.0, - height: float = 60.0, - max_volume: float = 40000.0, # 40mL - barcode: str = None, -) -> Bottle: - """创建配液瓶(小)""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="Small_Solution_Bottle", - ) - - -def BIOYOND_PolymerStation_Large_Solution_Bottle( - name: str, - diameter: float = 55.0, - height: float = 90.0, - max_volume: float = 150000.0, # 150mL - barcode: str = None, -) -> Bottle: - """创建配液瓶(大)""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="Large_Solution_Bottle", - ) - - -def BIOYOND_PolymerStation_Pipette_Tip( - name: str, - diameter: float = 10.0, - height: float = 50.0, - max_volume: float = 1000.0, # 1mL - barcode: str = None, -) -> Bottle: - """创建枪头""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="Pipette_Tip", - ) - diff --git a/unilabos/resources/graphio.py b/unilabos/resources/graphio.py index bca92c9..92fcf1e 100644 --- a/unilabos/resources/graphio.py +++ b/unilabos/resources/graphio.py @@ -636,6 +636,8 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[st plr_material: ResourcePLR = initialize_resource( {"name": material["name"], "class": className}, resource_type=ResourcePLR ) + print("plr_material:",plr_material) + print("code:",material.get("code", "")) plr_material.code = material.get("code", "") and material.get("barCode", "") or "" plr_material.unilabos_uuid = str(uuid.uuid4()) From 3231d606465292417f25b49143f1265ba832a5d1 Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Mon, 27 Oct 2025 20:08:19 +0800 Subject: [PATCH 024/104] 1027by_Xinyu --- test/resources/YB_materials_info.json | 52 +++++- test/resources/test_resourcetreeset.py | 24 +-- .../bioyond_cell/bioyond_cell_workstation.py | 10 +- .../bioyond_cell/样品导入模板.xlsx | Bin 22007 -> 9686 bytes .../workstation/bioyond_studio/config.py | 4 +- .../workstation/bioyond_studio/station.py | 2 +- .../resources/bioyond/YB_bottle_carriers.yaml | 25 +++ .../resources/bioyond/bottle_carriers.yaml | 12 -- .../registry/resources/bioyond/bottles.yaml | 151 ----------------- ...ttle_carriers.py => YB_bottle_carriers.py} | 79 ++------- unilabos/resources/bioyond/YB_bottles.py | 38 +++++ unilabos/resources/bioyond/YB_warehouses.py | 160 ++++++++++++++++++ .../resources/bioyond/bottle_carriers copy.py | 148 ---------------- unilabos/resources/bioyond/bottles copy.py | 24 --- 14 files changed, 307 insertions(+), 422 deletions(-) create mode 100644 unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml delete mode 100644 unilabos/registry/resources/bioyond/bottle_carriers.yaml delete mode 100644 unilabos/registry/resources/bioyond/bottles.yaml rename unilabos/resources/bioyond/{bottle_carriers.py => YB_bottle_carriers.py} (89%) create mode 100644 unilabos/resources/bioyond/YB_bottles.py create mode 100644 unilabos/resources/bioyond/YB_warehouses.py delete mode 100644 unilabos/resources/bioyond/bottle_carriers copy.py delete mode 100644 unilabos/resources/bioyond/bottles copy.py diff --git a/test/resources/YB_materials_info.json b/test/resources/YB_materials_info.json index 61d2de8..d5bc381 100644 --- a/test/resources/YB_materials_info.json +++ b/test/resources/YB_materials_info.json @@ -1,12 +1,52 @@ [ - {'id': '3a1d2656-69bf-1ccf-fc38-85a0431ee498', 'typeName': '加样头(大)', 'code': '0005-00291', 'barCode': '', 'name': 'test', 'quantity': 1.0, 'lockQuantity': 0.0, 'unit': '个', 'status': 1, 'isUse': False, 'locations': [ - {'id': '3a19da56-1379-20c8-5886-f7c4fbcb5733', 'whid': '3a19da56-1378-613b-29f2-871e1a287aa5', 'whName': '粉末加样头堆栈', 'code': '0005-0003', 'x': 3, 'y': 1, 'z': 1, 'quantity': 0 + { + "id": "3a1d377b-299d-d0f2-ced9-48257f60dfad", + "typeName": "加样头(大)", + "code": "0005-00145", + "barCode": "", + "name": "LiDFOB", + "quantity": 9999.0, + "lockQuantity": 0.0, + "unit": "个", + "status": 1, + "isUse": false, + "locations": [ + { + "id": "3a19da56-1379-ff7c-1745-07e200b44ce2", + "whid": "3a19da56-1378-613b-29f2-871e1a287aa5", + "whName": "粉末加样头堆栈", + "code": "0005-0001", + "x": 1, + "y": 1, + "z": 1, + "quantity": 0 } - ], 'detail': [] + ], + "detail": [] }, - {'id': '3a1d2657-d16f-a575-3506-5c029ff6810d', 'typeName': '加样头(大)', 'code': '0005-00293', 'barCode': '', 'name': 'dsfdsfd', 'quantity': 1.0, 'lockQuantity': 0.0, 'unit': '个', 'status': 1, 'isUse': False, 'locations': [ - {'id': '3a19da56-1379-e77d-0e65-7463b238a3b9', 'whid': '3a19da56-1378-613b-29f2-871e1a287aa5', 'whName': '粉末加样头堆栈', 'code': '0005-0005', 'x': 5, 'y': 1, 'z': 1, 'quantity': 0 + { + "id": "3a1d377b-6a81-6a7e-147c-f89f6463656d", + "typeName": "液", + "code": "0006-00141", + "barCode": "", + "name": "EMC", + "quantity": 99999.0, + "lockQuantity": 0.0, + "unit": "g", + "status": 1, + "isUse": false, + "locations": [ + { + "id": "3a1baa20-a7b1-c665-8b9c-d8099d07d2f6", + "whid": "3a1baa20-a7b0-5c19-8844-5de8924d4e78", + "whName": "4号手套箱内部堆栈", + "code": "0015-0001", + "x": 1, + "y": 1, + "z": 1, + "quantity": 0 } - ], 'detail': [] + ], + "detail": [] } ] \ No newline at end of file diff --git a/test/resources/test_resourcetreeset.py b/test/resources/test_resourcetreeset.py index ff5cfd0..b7602ed 100644 --- a/test/resources/test_resourcetreeset.py +++ b/test/resources/test_resourcetreeset.py @@ -1,3 +1,4 @@ +from ast import If import pytest import json import os @@ -13,13 +14,8 @@ lab_registry.setup() type_mapping = { - "烧杯": ("YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"), - "试剂瓶": ("YB_1BottleCarrier", ""), - "样品板": ("YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"), - "分装板": ("YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), - "样品瓶": ("YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"), - "90%分装小瓶": ("YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"), - "10%分装小瓶": ("YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"), + "加样头(大)": ("YB_jia_yang_tou_da_1X1_carrier", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + "液": ("YB_1BottleCarrier", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), } @@ -57,12 +53,20 @@ def bioyond_materials_liquidhandling_2() -> list[dict]: "bioyond_materials_reaction", "bioyond_materials_liquidhandling_1", ]) -def test_resourcetreeset_from_plr(materials_fixture, request) -> list[dict]: - materials = request.getfixturevalue(materials_fixture) +def test_resourcetreeset_from_plr() -> list[dict]: + # 直接加载 bioyond_materials_reaction.json 文件 + current_dir = os.path.dirname(os.path.abspath(__file__)) + json_path = os.path.join(current_dir, "YB_materials_info.json") + with open(json_path, "r", encoding="utf-8") as f: + materials = json.load(f) deck = BIOYOND_PolymerReactionStation_Deck("test_deck") output = resource_bioyond_to_plr(materials, type_mapping=type_mapping, deck=deck) - print(deck.summary()) + print(output) + # print(deck.summary()) r = ResourceTreeSet.from_plr_resources([deck]) print(r.dump()) # json.dump(deck.serialize(), open("test.json", "w", encoding="utf-8"), indent=4) + +if __name__ == "__main__": + test_resourcetreeset_from_plr() diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index c60a03e..ff29574 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -971,7 +971,7 @@ if __name__ == "__main__": lab_registry.setup() ws = BioyondCellWorkstation() # logger.info(ws.scheduler_stop()) - logger.info(ws.scheduler_start()) + # logger.info(ws.scheduler_start()) # results = ws.create_materials(SOLID_LIQUID_MAPPINGS) # for r in results: @@ -980,11 +980,11 @@ if __name__ == "__main__": # result = ws.create_and_inbound_materials() # 继续后续流程 - logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 + # logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 # # 使用正斜杠或 Path 对象来指定文件路径 - excel_path = Path("unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025092701.xlsx") - logger.info(ws.create_orders(excel_path)) - logger.info(ws.transfer_3_to_2_to_1()) + # excel_path = Path("unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025092701.xlsx") + # logger.info(ws.create_orders(excel_path)) + # logger.info(ws.transfer_3_to_2_to_1()) # logger.info(ws.transfer_1_to_2()) # logger.info(ws.scheduler_start()) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/样品导入模板.xlsx b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/样品导入模板.xlsx index a89f1bc27fa7eee453958723c88bd563c122cbe7..e6addeb9855082c3ba401dbff45cc4aecf81e8ca 100644 GIT binary patch literal 9686 zcmeHtg;yNe_I2X~cXtbJfe_pY4#9$Z<23Hx1a}V_BuH=#?(PH&?j*RogkMi)-ZwLu z`Tm0Us#jO7UUm1W>bm=!eeOA>EC&ON1Aqr00ssI?fbmhLg+3Gj5DN`0`2zB%%^c9=vWLa81Wrf{ih~?l~@l;`rFL`mEj#^ZMY(IlT`Ufj_(idHU#vA#ySJ{lXFGj##D(%*(@R_n z0~VbukpN2Y;gmZ(tq)FCbi#hj<+_#4QaRfi_NTtiSF5=Z^oyKKPfv zUK+2U)Xk0(d@TJCGH^Y!9E%|)=O!xMOsVGUFSCSR7nMgtyxjhr1VfD|5azXCtM9|m z;<7;0-T>v*23KhW77jo4XScH8lsg9(1V%c?WGRQz^&U*unai20)Yq~e^scQjj3o_4 zIdX%m)KXKY5>*)EtZ#{)Vds+s;|Zn)X!j{-tr*{wL(Pb(9+m}H*7IcVC5)&0eN8Ud zMHLR^l|PtF#T{@mHlHu|9k}^kz}a7*L_& z!n)xcf89@;ed(uN%XK=O>CwaUth;33WDp!dUbF&HsDC<%3`6xmAH+e#Aufatc{1)c zY;Jar*2Z>r*1zf2N3~_eMRuIF54CQg-sERfFGVE6W#xEi<|b~M!`_kf+cJ~z*N`qC zIW#_>dvQQ}CDhU%o$y)NvztQp;8p+aL#9)k6B^ogTDPN5lk(xPaQs4!MHHOo#%b9l z!&S{8B({|U94TNr3sG*q1HT{6>Vp9njmgi;&*{#VJ z$xi{taGg?!-xfLCiB-O;US!Ralxuz3$t=y~*0?~yyCIiC7SWESaIg3ofMzR_5(Ukhijni`omcS$p9F*n$0uZSQgtWntSY zR^$S^WWM@z5o{Px5!T#94h}p`gY#~lqjhCS5Uc~g@XmM|0}?mfBs7mv4^3aHP=A5F zRWCMISGOg+=-32H#`0iAA3J1;(_sLHvQ_+Wx9g_w}QqCln8VDJWo}65w0zS2CQ}ljQSvw z3?3_^U_=9vcjY~feWBlL+8op0a(&&J)hvv7y|ip(XTL;z7>)4b1~cQ$8+n>?g{tY0 zAVp>To=PBJ_jwZS$!6O2I2YN&_V&W7OWh+lsWoxt7 zTrOu9`{Prqs}oDM!XR)Y3GU*HX_=M8clCEshKe?N9i}{Y3c+t5+n*qe{wL)Fgc4KE zAyjXI(4Gi@2nC`1@6zyB;{Qh`pdeWr#Bu(+w^9`axgK_mR^%Tc>~86_*Q= zVGxZE%gDg5;jpm|_V!B-kG{9|GRgp2W8;e`g)2q2D9y_M)Acf_zz$Y@-OFv&twEpJpf;JrYV_jIwh|V z&srYL6gPwn<9l$cniIy@Q=8LIz%w`!U2jZRe&os~@jBi`@?~v@f194__Rc zZ4LI%tU_k`pOj(Mk(A3x8lT`u#Z0pl7da(lAeZ+{TplSlJt$Kk$WD`T)& zP@spRwqY1;$^Jk-o{1pqy;iU_L`1)XCqNnlvJ1A}$7_Q}gnibBUyGJANO+zLKkD=K zrJ;R6wc@#dJ7~;%(?8{qu1qtT=33s#awj^rS&ray@kjqKTsc&_0}3=8^!M!*z|Cte z7S>M7BfQ}P>QWE}M}lmyEpom@gf}10>}hIO6OZKw z&c$tS{U83Q1o#M(9*_7&lR{3F@u$!+MF&lZlg%(p{9T@JyN=K+-E~iR2N3b=476q9s^35CZFcc$?TAd zHDf~=EWOGaD$NSk?Zl>6pRn`{A$tO;8%JUtWIGTYUuNkjwezWn^`u0}y)08f`NB(mXf9^6;^+1d481E{KZvZaRe8Q-@FmXMvc!Sr822{0s zL>E@t)U-qS#Sg(7=?Uo6D7(1kDJy#VW8bgWZ+w@IS9~9|TYYqzFZa&QZ#-o_9JQCf zWjo&OzVy6Yw2djeKlTeg-*)y@cxt)ZsQ7xnY&k7J^XBd=?c-xr`+}{|RrBbs@IH;x zfajn|Jv+QTdEBblv>OfN-?8T4yO8eI=`%1Z%kM*^_gGUyR@Wd#1-w8{pxk~fo zaxF;>21`t9qdO;hJxJY`Kf5dB4wmHQ?$esqB(skpciMUq!h(rf7>zHhbWNSByrCe>?w zXM^`n?dXkIuPxtj%3E8AHRtzNSk*chDvht7;np7&_Z}4u`JO{P5vmBPEObH9r<6nI zkm$-ey5=$J6ddft;U(uMw*?@o*rw=ol{<0zkh~_E?BcX&j1(>L03p46t~%gQOUWV- zm>fY^oAy1~-^-pe!uyMcXrzMyoC>IvhMAA)S;Q<7nH;|Zp#9=ZaNU?*B`eNM=?2UV zcRbgk$fo(GRO3v02r+hV7t=7im(1UDVe|P%=Wjxr33X9cL*C|Fp6IbUIC6xzp{u1M zj~9(rJ3Az>!fyT|$oZtjHd99>>n#nxWG=DJ7cZ~@x zL4UYBXa}4G1(7-vbOpe5ph%;&C_`ZamE>^sv!Z$w1%O)+8GRwP@{taJ(PbTot!zgK z&d}*TP2ID93D|Cv3}_gxMt}_D9b3*p&_1Y^q_(KDP2N@CzolHR+KRf^YJ?dod?xz| z0Wcs45IKT6shX}Ni)B2FO}SNjx+JUt3Ja;o-FAZC1ZdhR+-^nPRwE3Ynlj|&N^~pi zZj}+%AZWjO*{>=Yo}9ihs1s<0Ui<3cd;f%A0FrEeq`0a;6yqZZj-LOsA#4#ZNVX7g z+Z}*ZYi}d@7WR`_BSR+i7CHi>iqKwwV>Tm(oxT&_?Y*<_SC<+ntY8>iOQZP~zp@5@ zR1Xn7V?rrqwCU4SPjtgcM75Ggk|H^QZXd?gdf5HXE&y#csa`#sfzJ9K&ZWLHM1CRF zRxL7r#}YW@#cdKFIp%Kf-mffB^p`9!Dxj_at1PEGmQ!Zx5&SI5wVV24LsWC5o)^@G zt60OFUC>G$okYv<*^)pt-7g@g2bM~K;sQt1r5b3LmSO2ePJhTE?F>EB?`O*T(~otd zmyy6C7(oWWRD^-#S`n4l&@(E;Jvgq{Zl7FjT<^T@XFDRA_|8Xr-NveUwv-Pn$BSv7 z0}}$^X^+WX*w<2Bi(Dm>NZeLe@GYq?X?>r8(-}16)ABc7Og+WhYhQ&`Q=oDt9W54AzT=_YHx(<>G#j??ZfQ^X+tF8|TZ+KK zlh@usqMmakkAr#7Pv?mx28Dm#^REk1cJxr|Lx_s3kp5OD`l%w$7C>7d+t2+^0Xfi; zvm@rgXv5#}qqxe_tfuXMNrjNiOl!N*Q<;RrtI|BpYI5W#s`A-|Dc&RiT5>8k1-kW8 zxfV4R(E72x&o=xe+!;O}&D)ZE$=f7YA4vo+@>P`Yql8B@)*%ey_Y%@WH5$t8y2u!V zVq{;KL2z#Ot#V9I@kr*FW&+e@hVrKNwvUh3VTQ7y3(aJNPh#cM`;LDT4FC_+{B5gkIr|rcse4M$CFe%`m0Y`oS1l;e9=HjA>O8pPE znU59Aw5%I$ke>1hQ{OHgx;oXx_HuGlc+J1>te8$FCXPT`@n>%14YaG};mZUf*ook^ z3j`kPY3YF)++k!1Mz72!7IIqkJ6qqO@HVdpRRFy6H989ELxVY+F0jP2K6z9nZy@Mi z0I~y6n9?Y@#|AM%FnUQd&|rH#p<1Nux1)S;hEiX9ve1Z&I?W&njc}OuB(9cxUv2mN zfz#^wV{2!dlq(y)SoqA`L(xRPQ5xgKTSJ2&D##R>a?%2;mh%RjoBVgFmx;2aE>tGqvN=L4M0`1i? z;vL0Q&$TxYs5|fz9N*6#t&KR?x+RLwWWeT|+$)NaaMTly;Y7kbWgOZByr=qUg~@IPkeUCHX|2=>8Y;O6Y*yDv%SgXEi1+@Of$MQ zjm0xj76tTt#Yv2T=;rEyA^F@nHKG&f0ZA!F2VbkZTLKBgYL^e0g&ZRE?^qg(CKedu z*FrV%d1IK$ONdt$#4ND%$i8Ndr_G4)>LFs|B{*DmNoBFWC(WM$_)J za;hTC#`s=lh?u~tTfqBDVNV~Uh9EjZjMip4D=mUI5l-R>LFq@80|Apon43w+0o?NgvR&j9+lhR045EM>Q zYzz)&vy8JpybX;jdjq*m@nu-iI~e4Ii92}3ueEifO!c4fT*^EPfc96sW**=Ot4~Vi zbcE*XbgQz2BbiCFp$JVLK%SvY1KiA0JJ}>Ff~Rq{FN;ta6wHzfFq9-mNRiHMpP^aR zx^j@VpP_m|kG0Al`db8GYrCWNg;1NF8^roNu71ndHku@Y7p)UFp`o{V&WQQWMqG>Y ziyKmnXVs-n$LGM$`;1A z?9tv`kk368A*Md+=kQDmtp+TFubl_tl8LGrOsb<&N#jdu_Z%HC1O+n+?fH3sm1y40oa)P$ z{x7MqV6|l$wup&z#Yo@7eOhLb+vuGNlS8WEOgxC6BPqHi=(w&DGZkD&O>%i*UN`#| zTdwNBMcx&DUF+M6RJSp{lEH&)_uM>ILrxd>>OmyAfKxLV^fr4Wl;=NunpPZ7i zcNVWRk~MoJ@QgjTS`9ZL0;Rp3v4umb4@(QPoM!YSn8-V=-8+t`dHups=946VQXx2H zww$ibZip7QC!+{A$zGL-B{V2c|Hdqz;Zvj+O#?Yx=K%gYnI%ejiCY2;(ThvZ?IhXK zP@q#^>T<0nL71asRjh0RFG8`xD;ip>uDEFBJU2z{LqTD1k~X3GB6i26x?WBEN@4m< zn9>jo7KJb>L*yd`UwG>mB?+Rtv=#QZLK%g1@w3?~W%;A_uo8MmbYE3`5`&t-NOoms z)81k{?*i$%e6y0nnWC5z(Kzw>qG-Ubcy7Y&k3TM#cde)hR-G5Fv~&*2QVnB0&A0i9 zNK*sTw>aJhCJ)71z}U`I5u;=tQ3{vw!~7ul-nzywnI)E@Sqy}#w_q}`)xgMq7ksqS zHRD%?pJvlVe~^cK_w)4*f?pQPzD;u&-V#QHF`FS#!Dv9z^^H)cKrg}r>71W~#5HaOz8UrOEPL4g_@s|po4-|P;>bxU z_st0(8cYQNYz_}eTon%)s&PeVx5Y~$#qmw5BZrs|4N=g-D9x?k2KupwVYlUvtm;aX z4Ui?ln=qmc$_uhLAx{|(Z2y`VPL5<)7C~~|Qy2gM=MU;z7&!t>Rh=CzLFPX_AR|FZ zuA7~p?-;zAvvpJrQY~~|HllaO5TGxruLhP^($x)02>XvQ{0QhY9kIj=38i4%FrBii zsXfpk$V5=s>oxHkPp?-xYm13nb{$&4J1bJKhu^7sHGwgf0FL6}Gz5D1QlWAUG3!a zht|3Y1&dR9h=MjyhERUcP#fNoaD={Z1L;l6UbvR8H(`rF&}B+!36jx?evEHJ9G=kC zZEw!mTA3F|ooM@*m&(BeYO@jiZ&e5M!vpCi}0~MP% z>Ge{lmyC~wOLVXtj9q;Pr@~=tg20GS6LZl_oWU~DYsd1?H(sO1Y2J3%ex%umA@5E+e=Lr0xG5o(0BZTrcaRv~2^*dQE|5Z@+V7IQd;KB^WzQE1W@%mCPoFn!(Z< zN-!nOC++0x7r4=%1`rRX&x(Y3o>;umGr;ZQ{KDt%SO$lWb3+p!Y2KA=K@J8IcvT^c z`rwh$g)h!pl5I>KR&H-(9s|C=8>tHJphYJ^@0eDUaF&wgpgip4rWQd$LA7A-G)a5& zaEQ8A`{ZV*#+soWw9=P8=D;?ulF>8hb795ls+ZzY!yz@IbZXY>Nv5QbMA(GoU!3zH z_`Wh)9fUh@-?l(MMIN8Tcw$f%Yb@V?G5?__=m6(f&NEX`U{+3To2FfcLFRlhO+^YZ z@4O!R30`e8?Sp}u&%X*LsYl&(0)$tOkh2(n<(84X{r{*1;nW{nM*O%e69-P;HTam_ z)b53PoZO&9k8p!BC}+k85_khGIcfTHT>L(Cl%+Rgc*BUE*y4DjjE`7k7Bjhh&p1BW zPGQ4rK2KF(MEv5OQsv7AeD>8T}POvHBZEsjnwf0{X zozxdJe&se0AZL?>PByM!F8hMCn|wCCvzEuk&YNLf33ihdb*7$p+qouI0Cv;im>lX7 zA{-W*M+Ez!^>7sN)#h+g%D!38hK>@V`J$_8lg`|zjA@k-sm@5S9b36q_u0})M+)U^ zd;REk_v-Sgb?sw)O~FCAt91>_{U|hUNkpCKuxJJ0i%ebC0m{z;k%nu5h1tN9-{szr|ChkASe|euqS?=!!{@&~V z5Ae5R3gnW1>HPl+{I&o2C$t5UhyKzH{T2N8PST%H0HEU8Pw@Yzuk@>(U)u|RTG~MV ze>d@uX2Y*mel0KkX@wr^UyDt@8u&E>|I>gJ;ZFm9=HtIYf6ZY2go={<4*fNs`PIVT xmGVzK06Qxi literal 22007 zcmeHP|8FeURd;AWD5xqCAOR9$v=aPM+3Vf4{cK}9vUk_-y}akddUjruAXM(o%p-~OAQnwY@9UxmVb zxBBVOcBAT^AMSK~e`jic;7(!5(nD}%?Al@E_}+4*HZ@%_Y|r#9$LlRu9@}1@oUa&B zEIdoNzGp924(zD%(8|XjSh``mA_k$T?*x$nTD)kv(vRa{v09DHzC94plpok0P`Z9N z5HZ|_y=oxL9nrI^_37!kDj?80Dl0^%an)L`gjcOv#n?Uwpyrcx``xZ%+AZH44s0(j zs6w_s>f3e5I=`BOvNRI0^b>K6$n1|!)Th`g=J|BuDcxJV39eBeTl zX4X*m<4iDfq19twY}m1|L@YFlK~j?9s1j#e*(AExqP0`QpXeZJx}mWvT<~4Tv;4hndmsGS6;TX?<;t}DhaOzbgxt79HYc|(|0QyX zfVESXb%wo8R%va0X5msR(=Qp^c$BZ*Za3StOrK;BrU@-oSn@h&*5_+$sm>gex*XNr zoSvPYq5L`hjS3kepbR5Gc{nGyc^>A9KJI^ma0ihp*5e`am9*_H6-KLiTxJpPx?HH5Yh8=3Q7t*Z1NwUmJ*9 zez*<{$cQo8GhztBU15S3H^rb6Isz*Yi?+y7RFN5t6k%OPWQI-v5%=Lhz=+WLK79AD zK79B4#)t2|@!r#KzW4N7?|tD5?>+t3N*58lBD`K+@PpU>@ZVo~%J{eM-}&IhKPu*j z(55RoeEqq*fBQl)Ba9%;c=Xa6$8WxI^v7R$|2r=hu&;)qlV{&{26kjTV(%F@`~kF0 z8ipdbJK@&jVpd+7CwD-VZxz8ga97S-Jp9_iZlFo39;Iwi4vvu_iao38L; zn1XnAY#`0wvF(D^J>fXSt{aXS`jOv_jVByqO*n-n*>*a_k+jeiX8`?jP}FAVYGRv> zo5q^&7PY!%?@~?}5JFB?)Nsy**tYEwGhPi(LuW5(-DIA&=0~Z?e3cn&Lnl*I5`kJ=JW_8?I1`Qh6S)L+dlE% ztpm3!Y#C)Bq?1TJaJ+G0+jSsrs~SEE+slwXR8~V!r>?Cgp~;{~ zYRj68CYI0TVDhYKP?%X0W;O=Qhrr5n@M;UIhRK+4ZFq^5J_D!Sf>uqhFSv`6R;z~e zg=MQQBE>mt)@0wME;Q0UY@YIwvsp8<8W`rzNBL8bBz(;7;+TE^g#`(_9m7wj^ zZ4wFnVQhz6eQVF?xWn*$h#aoXj>t6*|@Ve1xNfvH6`!H|IeC&=>Xm!G%V}^6JG9y!oZQT zT=B!W??V&{`i{9CLf)6L1H1*^&;l8Z4+|;sH?(*0SB*KPLk??r5R7j)JtK4=oy7gn zwl^UxN$)#BSuIIDN~N19k{*?V8wCn>$KJKwZH$3)7=tTDA6#8VlOzmCKJI_?J{dus z9?ntng4MBpQCC#Lxf+W9% zg`}!;LO4wV&gEJau|`^4pH0C*FOIet3bXl40Plee{2$~$Cp2AKx-llyZT|+uRs)tF zl>H3w>Pd`h;6WH5)l~=PJ*rSS6RJ|CO(v&kwKQ2@@GK~+ml+$8ou{{`CX4sSn=G*u7;n_^V1+Xj@aVNA<1Zu~<>}I8*>dn>|9St5Pu@Q0v__ePmGIBH|FjkguP!Kd&u(A|=Rt@@^Tv|1K*AETZ zhsi@Su97Q`CM*>sFRQz-aN!=PVkxH_Qx&KIir%1VkU#L&0pmdi07nrPtSmtmf~mf@ z4mX(G@ET3HT=8IYi)@18JAoVW)#Gy?WDUIL)xQt4AS zs6Vew>Aq*R#(p=cOay^@@W^n`v0(uj_x&V&D{F;%a*ZKP$v`DLA09zrSenWdcZQA& z)2xDKfKF>)8%C!1Sae)lPDry_z$lhVX4@HYxuUN(Y|9xA7Ba3)XV+ILZMmYa71xaH zdmR*55c-!7X8!#6S0*O#@0V2&D%@vu{&co-~3LdovR z;ma={e)X-RXP-NK^&7{pfA{dtmyYh-UaIb*(-?|ZBwd-Nr22} zKwxid&LWo>5K84hD3$vNrIr9$$bdl7$^n^YKq!?1p;Yc8lv)C$hL0O+{K91($^wJJ z$gJ5dM%L9SKPe@X{;6fzVGS<|+ zs5m3HnY30;;pkXfV;I9bG9tYyFWbq)^nMYNzSW`^Z4c0 zVXkubhc6ty^Fqc+`(J04T3G^+s+;4QgM1Twxqw zB&y;nXL$MVj-J;OA8Hky=4vz>G2l%`)^06fw-}j8w@b+sjm~v7nv&QUY%=l)nz7Pt zCau9zG&{mjZS=Znz{G{g^@?lP-$JA zbgMR#)?{iMo%iZA@3FYjWaJSvRGN|7Ou7tB7KK-F`$2Bl zWSL-L7GyldkFteKJPQ^w@hr%gj-UO@qnEx}l(#MybJB-#+J8Di*J*@i-q{>aZjC3m zOUV?Cj?i@)V|9m1?`$$M1KVO`Cf#OaElttr2wkT!mU(BBk+nV*p&6M;w;5SWQ#3k4 z*J+I9`GS#I<6DfZt4qc`Rj$bdO+!a$xXh0%t}p2< zroE)IbUbdbkja|gWb_d@RGiti&7?J2(pj(;kiIN}%X~ntvHF^f&d4oBX0~lJGKZr+ z_g2daP7gkuv8{F(`XMYhQ8T%@FqhCOW_hWtnQPxBDY)<&l*3bxuJF z9RB@0I9+Dnq%r5u%(y!mTqW(B8m@giJMMl4i7fIuN`J29Md^JsTdgt(P8V@wc(J8Hxd z1I$q~I4Yiv;FC>;nwajY?rav+{^{$#_u!L|Kk)Ctyno>czqc%628*afSFO^?q6wch zoiPff6OGZhqa4jLGnK(Gbmgf|mRYrvQ*5fWsah4bZQ+4Xi<0Q^sJwur9m~rn%Fk4C5wYRr7wbzg^U=~-aKE82XZW9%gj)x~#nYgi!9vSo(|9tDI zU;5dJ3H-w&gMPaS`{v+anopz4M!$1!T_JUxKAV#Ru}pM%<6|B)qh-n)ytY zSy@W*^FR=(~!Mm;_t1T!8?XMuaL-A%z#nb?~I4Qn_8vni@ot_CCzGrsR;H z0s#>oObrAatTzXk3&z(l zfO(SpNvWo|37tW}Oaxz@!*OU>UMvydhlm7{lECs}iJsFfl)&<0342g1u|J8FVwt^C z8Ke};+zN`@ax1XQ8Y4+X?H?l(mly!b%aBP@Q8i;^0*hNMKTMEXYz5da31gddXa<>zd|R3i7U{M1dZhaX|6mHY?P z2YR>@a|QgP7p>sG45>gnB^Tve)wSs7e?y9911*0*CfCB>X=*LsdPr)?_ZAoC?^d+% zRx^W;9$U(V`I`bQ{P!*L-hd18r#&rr>GJ5aAD8BHc`f~ot2ya(FmiD|1J&ZjwZi%6 V=Cz;x8Dx3@{`(v_MRyDD{u^YnfQ0}6 diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 4804da1..f184a28 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -135,8 +135,8 @@ WAREHOUSE_MAPPING = { # 物料类型配置 MATERIAL_TYPE_MAPPINGS = { - "加样头(大)": ("YB_jia_yang_tou_da", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), - "加样头(大)板": ("YB_jia_yang_tou_da_1X1_carrier", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"), + "加样头(大)": ("YB_jia_yang_tou_da_1X1_carrier", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + "液": ("YB_1BottleCarrier", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), # YB信息 } diff --git a/unilabos/devices/workstation/bioyond_studio/station.py b/unilabos/devices/workstation/bioyond_studio/station.py index 3ce7353..3e3e0b3 100644 --- a/unilabos/devices/workstation/bioyond_studio/station.py +++ b/unilabos/devices/workstation/bioyond_studio/station.py @@ -63,7 +63,7 @@ class BioyondResourceSynchronizer(ResourceSynchronizer): logger.error("Bioyond API客户端未初始化") return False - bioyond_data = self.bioyond_api_client.stock_material('{"typeMode": 2, "includeDetail": true}') + bioyond_data = self.bioyond_api_client.stock_material('{"typeMode": 1, "includeDetail": true}') if not bioyond_data: logger.warning("从Bioyond获取的物料数据为空") return False diff --git a/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml b/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml new file mode 100644 index 0000000..83320c5 --- /dev/null +++ b/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml @@ -0,0 +1,25 @@ +YB_jia_yang_tou_da_1X1_carrier: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_jia_yang_tou_da_1X1_carrier + type: pylabrobot + description: YB_jia_yang_tou_da_1X1_carrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +YB_1BottleCarrier: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_1BottleCarrier + type: pylabrobot + description: YB_1BottleCarrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 diff --git a/unilabos/registry/resources/bioyond/bottle_carriers.yaml b/unilabos/registry/resources/bioyond/bottle_carriers.yaml deleted file mode 100644 index ac01cc3..0000000 --- a/unilabos/registry/resources/bioyond/bottle_carriers.yaml +++ /dev/null @@ -1,12 +0,0 @@ -YB_jia_yang_tou_da_1X1_carrier: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottle_carriers:YB_jia_yang_tou_da_1X1_carrier - type: pylabrobot - description: YB_jia_yang_tou_da_1X1_carrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 diff --git a/unilabos/registry/resources/bioyond/bottles.yaml b/unilabos/registry/resources/bioyond/bottles.yaml deleted file mode 100644 index b886510..0000000 --- a/unilabos/registry/resources/bioyond/bottles.yaml +++ /dev/null @@ -1,151 +0,0 @@ -YB_jia_yang_tou_da: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_jia_yang_tou_da - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 - -YB_Liquid_Vial: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:YB_Liquid_Vial - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_Reagent_Bottle: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:YB_Reagent_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_Solid_Stock: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:YB_Solid_Stock - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_Solid_Vial: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:YB_Solid_Vial - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_Solution_Beaker: - category: - - bottles - class: - module: unilabos.resources.bioyond.bottles:YB_Solution_Beaker - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_100ml_Liquid_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_100ml_Liquid_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_Liquid_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_Liquid_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_High_Viscosity_Liquid_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_High_Viscosity_Liquid_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_Large_Dispense_Head: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_Large_Dispense_Head - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_5ml_Dispensing_Vial: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_5ml_Dispensing_Vial - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_20ml_Dispensing_Vial: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_20ml_Dispensing_Vial - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_Small_Solution_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_Small_Solution_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_Large_Solution_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_Large_Solution_Bottle - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 -YB_Pipette_Tip: - category: - - yb3 - class: - module: unilabos.resources.bioyond.bottles:YB_Pipette_Tip - type: pylabrobot - handles: [] - icon: '' - init_param_schema: {} - version: 1.0.0 diff --git a/unilabos/resources/bioyond/bottle_carriers.py b/unilabos/resources/bioyond/YB_bottle_carriers.py similarity index 89% rename from unilabos/resources/bioyond/bottle_carriers.py rename to unilabos/resources/bioyond/YB_bottle_carriers.py index 76e3a93..a4a3601 100644 --- a/unilabos/resources/bioyond/bottle_carriers.py +++ b/unilabos/resources/bioyond/YB_bottle_carriers.py @@ -1,18 +1,9 @@ from pylabrobot.resources import create_homogeneous_resources, Coordinate, ResourceHolder, create_ordered_items_2d from unilabos.resources.itemized_carrier import Bottle, BottleCarrier -from unilabos.resources.bioyond.bottles import ( - YB_Solid_Stock, - YB_Solid_Vial, - YB_Liquid_Vial, - YB_Solution_Beaker, - YB_Reagent_Bottle, - YB_5ml_Dispensing_Vial, - YB_20ml_Dispensing_Vial, - YB_Small_Solution_Bottle, - YB_Large_Solution_Bottle, - YB_Large_Dispense_Head, - YB_Pipette_Tip +from unilabos.resources.bioyond.YB_bottles import ( + YB_jia_yang_tou_da, + YB_ye_Bottle ) # 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial @@ -207,10 +198,9 @@ def YB_6VialCarrier(name: str) -> BottleCarrier: carrier[i] = YB_Liquid_Vial(f"{name}_liquidvial_{ordering[i]}") return carrier - +"""1瓶载架 - 单个中央位置""" def YB_1BottleCarrier(name: str) -> BottleCarrier: - """1瓶载架 - 单个中央位置""" - + # 载架尺寸 (mm) carrier_size_x = 127.8 carrier_size_y = 85.5 @@ -241,49 +231,13 @@ def YB_1BottleCarrier(name: str) -> BottleCarrier: carrier.num_items_x = 1 carrier.num_items_y = 1 carrier.num_items_z = 1 - carrier[0] = YB_Reagent_Bottle(f"{name}_flask_1") - return carrier - - -def YB_1FlaskCarrier(name: str) -> BottleCarrier: - """1瓶载架 - 单个中央位置""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 20.0 - - # 烧杯尺寸 - beaker_diameter = 70.0 - - # 计算中央位置 - center_x = (carrier_size_x - beaker_diameter) / 2 - center_y = (carrier_size_y - beaker_diameter) / 2 - center_z = 5.0 - - carrier = BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=create_homogeneous_resources( - klass=ResourceHolder, - locations=[Coordinate(center_x, center_y, center_z)], - resource_size_x=beaker_diameter, - resource_size_y=beaker_diameter, - name_prefix=name, - ), - model="1FlaskCarrier", - ) - carrier.num_items_x = 1 - carrier.num_items_y = 1 - carrier.num_items_z = 1 - carrier[0] = YB_Reagent_Bottle(f"{name}_bottle_1") + carrier[0] = YB_ye_Bottle(f"{name}_flask_1") return carrier +"""5ml分液瓶板 - 4x2布局,8个位置""" def YB_6x5ml_DispensingVialCarrier(name: str) -> BottleCarrier: - """5ml分液瓶板 - 4x2布局,8个位置""" + # 载架尺寸 (mm) carrier_size_x = 127.8 @@ -331,9 +285,9 @@ def YB_6x5ml_DispensingVialCarrier(name: str) -> BottleCarrier: carrier[i] = YB_5ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}") return carrier - +"""20ml分液瓶板 - 4x2布局,8个位置""" def YB_6x20ml_DispensingVialCarrier(name: str) -> BottleCarrier: - """20ml分液瓶板 - 4x2布局,8个位置""" + # 载架尺寸 (mm) carrier_size_x = 127.8 @@ -381,9 +335,9 @@ def YB_6x20ml_DispensingVialCarrier(name: str) -> BottleCarrier: carrier[i] = YB_20ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}") return carrier - +"""配液瓶(小)板 - 4x2布局,8个位置""" def YB_6x_SmallSolutionBottleCarrier(name: str) -> BottleCarrier: - """配液瓶(小)板 - 4x2布局,8个位置""" + # 载架尺寸 (mm) carrier_size_x = 127.8 @@ -481,10 +435,9 @@ def YB_4x_LargeSolutionBottleCarrier(name: str) -> BottleCarrier: carrier[i] = YB_Large_Solution_Bottle(f"{name}_bottle_{ordering[i]}") return carrier - -def YB_6x_LargeDispenseHeadCarrier(name: str) -> BottleCarrier: - """加样头(大)板 - 1x1布局,1个位置""" - +"""加样头(大)板 - 1x1布局,1个位置""" +def YB_jia_yang_tou_da_1X1_carrier(name: str) -> BottleCarrier: + # 载架尺寸 (mm) carrier_size_x = 127.8 carrier_size_y = 85.5 @@ -526,7 +479,7 @@ def YB_6x_LargeDispenseHeadCarrier(name: str) -> BottleCarrier: carrier.num_items_x = 1 carrier.num_items_y = 1 carrier.num_items_z = 1 - carrier[0] = YB_Large_Dispense_Head(f"{name}_head_1") + carrier[0] = YB_jia_yang_tou_da(f"{name}_head_1") return carrier diff --git a/unilabos/resources/bioyond/YB_bottles.py b/unilabos/resources/bioyond/YB_bottles.py new file mode 100644 index 0000000..f38dc36 --- /dev/null +++ b/unilabos/resources/bioyond/YB_bottles.py @@ -0,0 +1,38 @@ +from unilabos.resources.itemized_carrier import Bottle, BottleCarrier +# 工厂函数 + +"""加样头(大)""" +def YB_jia_yang_tou_da( + name: str, + diameter: float = 20.0, + height: float = 100.0, + max_volume: float = 30000.0, # 30mL + barcode: str = None, +) -> Bottle: + """创建粉末瓶""" + return Bottle( + name=name, + diameter=diameter,# 未知 + height=height, + max_volume=max_volume, + barcode=barcode, + model="Solid_Stock", + ) + +"""液1x1""" +def YB_ye_Bottle( + name: str, + diameter: float = 40.0, + height: float = 70.0, + max_volume: float = 50000.0, # 50mL + barcode: str = None, +) -> Bottle: + """创建液体瓶""" + return Bottle( + name=name, + diameter=diameter, + height=height, + max_volume=max_volume, + barcode=barcode, + model="Liquid_Bottle", + ) diff --git a/unilabos/resources/bioyond/YB_warehouses.py b/unilabos/resources/bioyond/YB_warehouses.py new file mode 100644 index 0000000..c546759 --- /dev/null +++ b/unilabos/resources/bioyond/YB_warehouses.py @@ -0,0 +1,160 @@ +from unilabos.resources.warehouse import WareHouse, warehouse_factory + + +def bioyond_warehouse_1x4x4(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=1, + num_items_y=4, + num_items_z=4, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) + + +def bioyond_warehouse_1x4x2(name: str) -> WareHouse: + """创建BioYond 4x1x2仓库""" + return warehouse_factory( + name=name, + num_items_x=1, + num_items_y=4, + num_items_z=2, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + removed_positions=None + ) + # 定义benyond的堆栈 +def bioyond_warehouse_1x2x2(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=2, + num_items_y=2, + num_items_z=1, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="YB_warehouse", + ) +def bioyond_warehouse_10x1x1(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=10, + num_items_y=1, + num_items_z=1, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) +def bioyond_warehouse_1x3x3(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=1, + num_items_y=3, + num_items_z=3, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) +def bioyond_warehouse_2x1x3(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=2, + num_items_y=1, + num_items_z=3, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) + +def bioyond_warehouse_3x3x1(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=3, + num_items_y=3, + num_items_z=1, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) +def bioyond_warehouse_5x1x1(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=5, + num_items_y=1, + num_items_z=1, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) +def bioyond_warehouse_3x3x1_2(name: str) -> WareHouse: + """创建BioYond 4x1x4仓库""" + return warehouse_factory( + name=name, + num_items_x=3, + num_items_y=3, + num_items_z=1, + dx=12.0, + dy=12.0, + dz=12.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) + +def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse: + """创建BioYond开关盖加液模块台面""" + return warehouse_factory( + name=name, + num_items_x=2, + num_items_y=5, + num_items_z=1, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + removed_positions=None + ) \ No newline at end of file diff --git a/unilabos/resources/bioyond/bottle_carriers copy.py b/unilabos/resources/bioyond/bottle_carriers copy.py deleted file mode 100644 index a3b0044..0000000 --- a/unilabos/resources/bioyond/bottle_carriers copy.py +++ /dev/null @@ -1,148 +0,0 @@ -from pylabrobot.resources import create_homogeneous_resources, Coordinate, ResourceHolder, create_ordered_items_2d - -from unilabos.resources.itemized_carrier import Bottle, BottleCarrier -from unilabos.resources.bioyond.bottles import ( - YB_jia_yang_tou_da, -) -# 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial - - - - - -def YB_jia_yang_tou_da_1X1_carrier(name: str) -> BottleCarrier: - """加样头(大)板 - 1x1布局,1个位置""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 95.0 - - # 瓶位尺寸 - bottle_diameter = 35.0 - bottle_spacing_x = 42.0 # X方向间距 - bottle_spacing_y = 35.0 # Y方向间距 - - # 计算起始位置 (居中排列) - start_x = (carrier_size_x - (1 - 1) * bottle_spacing_x - bottle_diameter) / 2 - start_y = (carrier_size_y - (1 - 1) * bottle_spacing_y - bottle_diameter) / 2 - - sites = create_ordered_items_2d( - klass=ResourceHolder, - num_items_x=1, - num_items_y=1, - dx=start_x, - dy=start_y, - dz=5.0, - item_dx=bottle_spacing_x, - item_dy=bottle_spacing_y, - size_x=bottle_diameter, - size_y=bottle_diameter, - size_z=carrier_size_z, - ) - for k, v in sites.items(): - v.name = f"{name}_{v.name}" - - carrier = BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=sites, - model="YB_1X1_jia_yang_tou_da_carrier", - ) - carrier.num_items_x = 1 - carrier.num_items_y = 1 - carrier.num_items_z = 1 - carrier[0] = YB_jia_yang_tou_da(f"{name}_head_1") - return carrier - - -def BIOYOND_PolymerStation_AdapterBlock(name: str) -> BottleCarrier: - """适配器块 - 单个中央位置""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 30.0 - - # 适配器尺寸 - adapter_diameter = 80.0 - - # 计算中央位置 - center_x = (carrier_size_x - adapter_diameter) / 2 - center_y = (carrier_size_y - adapter_diameter) / 2 - center_z = 0.0 - - carrier = BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=create_homogeneous_resources( - klass=ResourceHolder, - locations=[Coordinate(center_x, center_y, center_z)], - resource_size_x=adapter_diameter, - resource_size_y=adapter_diameter, - name_prefix=name, - ), - model="AdapterBlock", - ) - carrier.num_items_x = 1 - carrier.num_items_y = 1 - carrier.num_items_z = 1 - # 适配器块本身不包含瓶子,只是一个支撑结构 - return carrier - - -def BIOYOND_PolymerStation_TipBox(name: str) -> BottleCarrier: - """枪头盒 - 8x12布局,96个位置""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 55.0 - - # 枪头尺寸 - tip_diameter = 10.0 - tip_spacing_x = 9.0 # X方向间距 - tip_spacing_y = 9.0 # Y方向间距 - - # 计算起始位置 (居中排列) - start_x = (carrier_size_x - (12 - 1) * tip_spacing_x - tip_diameter) / 2 - start_y = (carrier_size_y - (8 - 1) * tip_spacing_y - tip_diameter) / 2 - - sites = create_ordered_items_2d( - klass=ResourceHolder, - num_items_x=12, - num_items_y=8, - dx=start_x, - dy=start_y, - dz=5.0, - item_dx=tip_spacing_x, - item_dy=tip_spacing_y, - size_x=tip_diameter, - size_y=tip_diameter, - size_z=carrier_size_z, - ) - for k, v in sites.items(): - v.name = f"{name}_{v.name}" - - carrier = BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=sites, - model="TipBox", - ) - carrier.num_items_x = 12 - carrier.num_items_y = 8 - carrier.num_items_z = 1 - # 创建96个枪头 - for i in range(96): - row = chr(65 + i // 12) # A-H - col = (i % 12) + 1 # 1-12 - carrier[i] = BIOYOND_PolymerStation_Pipette_Tip(f"{name}_tip_{row}{col}") - return carrier - diff --git a/unilabos/resources/bioyond/bottles copy.py b/unilabos/resources/bioyond/bottles copy.py deleted file mode 100644 index 2504c37..0000000 --- a/unilabos/resources/bioyond/bottles copy.py +++ /dev/null @@ -1,24 +0,0 @@ -from unilabos.resources.itemized_carrier import Bottle, BottleCarrier -# 工厂函数 - - -def YB_jia_yang_tou_da( - name: str, - diameter: float = 35.0, - height: float = 90.0, - max_volume: float = 50000.0, # 50mL - code: str = None, - barcode: str = None, -) -> Bottle: - """创建加样头(大)""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - code=code, - model="YB_jia_yang_tou_da", - ) - - From ff25e814de7301c33bde397df1e71806cfcf157d Mon Sep 17 00:00:00 2001 From: calvincao Date: Mon, 27 Oct 2025 22:08:02 +0800 Subject: [PATCH 025/104] feat: add new glove box internal stack configuration with site UUIDs --- .../workstation/bioyond_studio/config.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index f184a28..c6f8b11 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -130,6 +130,23 @@ WAREHOUSE_MAPPING = { "J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9" } }, + "4号手套箱内部堆栈": { + "uuid": "", + "site_uuids": { + "A01": "3a1baa20-a7b1-c665-8b9c-d8099d07d2f6", + "A02": "3a1baa20-a7b1-93a7-c988-f9c8ad6c58c9", + "A03": "3a1baa20-a7b1-00ee-f751-da9b20b6c464", + "A04": "3a1baa20-a7b1-4712-c37b-0b5b658ef7b9", + "B01": "3a1baa20-a7b1-9847-fc9c-96d604cd1a8e", + "B02": "3a1baa20-a7b1-4ae9-e604-0601db06249c", + "B03": "3a1baa20-a7b1-8329-ea75-81ca559d9ce1", + "B04": "3a1baa20-a7b1-89c5-d96f-36e98a8f7268", + "C01": "3a1baa20-a7b1-32ec-39e6-8044733839d6", + "C02": "3a1baa20-a7b1-b573-e426-4c86040348b2", + "C03": "3a1baa20-a7b1-cca7-781e-0522b729bf5d", + "C04": "3a1baa20-a7b1-7c98-5fd9-5855355ae4b3" + } + } } # 物料类型配置 From 2d58576937ea519d81a11b8828a481d084a2fccb Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Mon, 27 Oct 2025 22:23:09 +0800 Subject: [PATCH 026/104] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0YB=E7=93=B6?= =?UTF-8?q?=E5=AD=90=E5=92=8C=E8=BD=BD=E6=9E=B6=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在YB_bottles.py中添加8种瓶子类型(100ml液体、高粘液、5ml分液瓶、20ml分液瓶、配液瓶小、配液瓶大、枪头等) - 在YB_bottle_carriers.py中添加12个载架函数(包括新增的高粘液载架和100ml液体载架) - 更新config.py的MATERIAL_TYPE_MAPPINGS配置,添加16种物料类型映射 - 创建YB_bottle_carriers.yaml注册文件,包含所有载架和瓶子函数 - 创建YB_bottle.yaml注册文件,包含独立的瓶子函数配置 - 移除不存在的瓶子函数引用(YB_Solid_Vial等4个函数) --- .../workstation/bioyond_studio/config.py | 20 +- .../registry/resources/bioyond/YB_bottle.yaml | 65 ++++++ .../resources/bioyond/YB_bottle_carriers.yaml | 189 +++++++++++++++++- .../resources/bioyond/YB_bottle_carriers.py | 120 +++++++++-- unilabos/resources/bioyond/YB_bottles.py | 127 +++++++++++- 5 files changed, 493 insertions(+), 28 deletions(-) create mode 100644 unilabos/registry/resources/bioyond/YB_bottle.yaml diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index f184a28..f6602b2 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -129,15 +129,27 @@ WAREHOUSE_MAPPING = { "J02": "3a19deae-2c7a-1d45-b4f8-6f866530e205", "J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9" } - }, + } } # 物料类型配置 MATERIAL_TYPE_MAPPINGS = { - - "加样头(大)": ("YB_jia_yang_tou_da_1X1_carrier", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + "100ml液体": ("YB_1Bottle100mlCarrier", "d37166b3-ecaa-481e-bd84-3032b795ba07"), "液": ("YB_1BottleCarrier", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), - # YB信息 + "高粘液": ("YB_1GaoNianYeBottleCarrier", "abe8df30-563d-43d2-85e0-cabec59ddc16"), + "加样头(大)": ("YB_jia_yang_tou_da_1X1_carrier", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + "加样头(大)板": ("YB_jia_yang_tou_da_1X1_carrier", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"), + "5ml分液瓶板": ("YB_6x5ml_DispensingVialCarrier", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"), + "5ml分液瓶": ("YB_6x5ml_DispensingVialCarrier", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"), + "20ml分液瓶板": ("YB_6x20ml_DispensingVialCarrier", "3a192fa4-47db-3449-162a-eaf8aba57e27"), + "20ml分液瓶": ("YB_6x20ml_DispensingVialCarrier", "3a192c2b-19e8-f0a3-035e-041ca8ca1035"), + "配液瓶(小)板": ("YB_6x_SmallSolutionBottleCarrier", "3a190c8b-3284-af78-d29f-9a69463ad047"), + "配液瓶(小)": ("YB_6x_SmallSolutionBottleCarrier", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"), + "配液瓶(大)板": ("YB_4x_LargeSolutionBottleCarrier", "53e50377-32dc-4781-b3c0-5ce45bc7dc27"), + "配液瓶(大)": ("YB_4x_LargeSolutionBottleCarrier", "19c52ad1-51c5-494f-8854-576f4ca9c6ca"), + "适配器块": ("YB_AdapterBlock", "efc3bb32-d504-4890-91c0-b64ed3ac80cf"), + "枪头盒": ("YB_TipBox", "3a192c2e-20f3-a44a-0334-c8301839d0b3"), + "枪头": ("YB_TipBox", "b6196971-1050-46da-9927-333e8dea062d"), } SOLID_LIQUID_MAPPINGS = { diff --git a/unilabos/registry/resources/bioyond/YB_bottle.yaml b/unilabos/registry/resources/bioyond/YB_bottle.yaml new file mode 100644 index 0000000..51ea6e4 --- /dev/null +++ b/unilabos/registry/resources/bioyond/YB_bottle.yaml @@ -0,0 +1,65 @@ +YB_fen_ye_5ml_Bottle: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottles:YB_fen_ye_5ml_Bottle + type: pylabrobot + description: YB_fen_ye_5ml_Bottle + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +YB_fen_ye_20ml_Bottle: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottles:YB_fen_ye_20ml_Bottle + type: pylabrobot + description: YB_fen_ye_20ml_Bottle + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +YB_pei_ye_xiao_Bottle: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottles:YB_pei_ye_xiao_Bottle + type: pylabrobot + description: YB_pei_ye_xiao_Bottle + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +YB_pei_ye_da_Bottle: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottles:YB_pei_ye_da_Bottle + type: pylabrobot + description: YB_pei_ye_da_Bottle + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +YB_Pipette_Tip: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottles:YB_Pipette_Tip + type: pylabrobot + description: YB_Pipette_Tip + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + diff --git a/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml b/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml index 83320c5..1c5b029 100644 --- a/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml +++ b/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml @@ -1,10 +1,24 @@ -YB_jia_yang_tou_da_1X1_carrier: + +YB_6StockCarrier: category: - yb3 class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_jia_yang_tou_da_1X1_carrier + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6StockCarrier type: pylabrobot - description: YB_jia_yang_tou_da_1X1_carrier + description: YB_6StockCarrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +YB_6VialCarrier: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6VialCarrier + type: pylabrobot + description: YB_6VialCarrier handles: [] icon: '' init_param_schema: {} @@ -23,3 +37,172 @@ YB_1BottleCarrier: init_param_schema: {} registry_type: resource version: 1.0.0 + +YB_1GaoNianYeBottleCarrier: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_1GaoNianYeBottleCarrier + type: pylabrobot + description: YB_1GaoNianYeBottleCarrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +YB_1Bottle100mlCarrier: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_1Bottle100mlCarrier + type: pylabrobot + description: YB_1Bottle100mlCarrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +YB_6x5ml_DispensingVialCarrier: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6x5ml_DispensingVialCarrier + type: pylabrobot + description: YB_6x5ml_DispensingVialCarrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +YB_6x20ml_DispensingVialCarrier: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6x20ml_DispensingVialCarrier + type: pylabrobot + description: YB_6x20ml_DispensingVialCarrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +YB_6x_SmallSolutionBottleCarrier: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6x_SmallSolutionBottleCarrier + type: pylabrobot + description: YB_6x_SmallSolutionBottleCarrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +YB_4x_LargeSolutionBottleCarrier: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_4x_LargeSolutionBottleCarrier + type: pylabrobot + description: YB_4x_LargeSolutionBottleCarrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +YB_jia_yang_tou_da_1X1_carrier: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_jia_yang_tou_da_1X1_carrier + type: pylabrobot + description: YB_jia_yang_tou_da_1X1_carrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +YB_AdapterBlock: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_AdapterBlock + type: pylabrobot + description: YB_AdapterBlock + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +YB_TipBox: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_TipBox + type: pylabrobot + description: YB_TipBox + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +YB_ye_Bottle: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottles:YB_ye_Bottle + type: pylabrobot + description: YB_ye_Bottle + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +YB_ye_100ml_Bottle: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottles:YB_ye_100ml_Bottle + type: pylabrobot + description: YB_ye_100ml_Bottle + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +YB_gao_nian_ye_Bottle: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottles:YB_gao_nian_ye_Bottle + type: pylabrobot + description: YB_gao_nian_ye_Bottle + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +YB_jia_yang_tou_da: + category: + - yb3 + class: + module: unilabos.resources.bioyond.YB_bottles:YB_jia_yang_tou_da + type: pylabrobot + description: YB_jia_yang_tou_da + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 diff --git a/unilabos/resources/bioyond/YB_bottle_carriers.py b/unilabos/resources/bioyond/YB_bottle_carriers.py index a4a3601..01d64c5 100644 --- a/unilabos/resources/bioyond/YB_bottle_carriers.py +++ b/unilabos/resources/bioyond/YB_bottle_carriers.py @@ -3,7 +3,14 @@ from pylabrobot.resources import create_homogeneous_resources, Coordinate, Resou from unilabos.resources.itemized_carrier import Bottle, BottleCarrier from unilabos.resources.bioyond.YB_bottles import ( YB_jia_yang_tou_da, - YB_ye_Bottle + YB_ye_Bottle, + YB_ye_100ml_Bottle, + YB_gao_nian_ye_Bottle, + YB_fen_ye_5ml_Bottle, + YB_fen_ye_20ml_Bottle, + YB_pei_ye_xiao_Bottle, + YB_pei_ye_da_Bottle, + YB_Pipette_Tip, ) # 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial @@ -53,8 +60,8 @@ def BIOYOND_Electrolyte_6VialCarrier(name: str) -> BottleCarrier: carrier.num_items_x = 3 carrier.num_items_y = 2 carrier.num_items_z = 1 - for i in range(6): - carrier[i] = YB_Solid_Vial(f"{name}_vial_{i+1}") + # for i in range(6): + # carrier[i] = YB_Solid_Vial(f"{name}_vial_{i+1}") return carrier @@ -91,7 +98,7 @@ def BIOYOND_Electrolyte_1BottleCarrier(name: str) -> BottleCarrier: carrier.num_items_x = 1 carrier.num_items_y = 1 carrier.num_items_z = 1 - carrier[0] = YB_Solution_Beaker(f"{name}_beaker_1") + # carrier[0] = YB_Solution_Beaker(f"{name}_beaker_1") return carrier @@ -141,8 +148,8 @@ def YB_6StockCarrier(name: str) -> BottleCarrier: carrier.num_items_y = 2 carrier.num_items_z = 1 ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] # 自定义顺序 - for i in range(6): - carrier[i] = YB_Solid_Stock(f"{name}_vial_{ordering[i]}") + # for i in range(6): + # carrier[i] = YB_Solid_Stock(f"{name}_vial_{ordering[i]}") return carrier @@ -192,13 +199,13 @@ def YB_6VialCarrier(name: str) -> BottleCarrier: carrier.num_items_y = 2 carrier.num_items_z = 1 ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] # 自定义顺序 - for i in range(3): - carrier[i] = YB_Solid_Vial(f"{name}_solidvial_{ordering[i]}") - for i in range(3, 6): - carrier[i] = YB_Liquid_Vial(f"{name}_liquidvial_{ordering[i]}") + # for i in range(3): + # carrier[i] = YB_Solid_Vial(f"{name}_solidvial_{ordering[i]}") + # for i in range(3, 6): + # carrier[i] = YB_Liquid_Vial(f"{name}_liquidvial_{ordering[i]}") return carrier -"""1瓶载架 - 单个中央位置""" +# 1瓶载架 - 单个中央位置 def YB_1BottleCarrier(name: str) -> BottleCarrier: # 载架尺寸 (mm) @@ -235,7 +242,80 @@ def YB_1BottleCarrier(name: str) -> BottleCarrier: return carrier -"""5ml分液瓶板 - 4x2布局,8个位置""" +# 高粘液瓶载架 - 单个中央位置 +def YB_1GaoNianYeBottleCarrier(name: str) -> BottleCarrier: + + # 载架尺寸 (mm) + carrier_size_x = 127.8 + carrier_size_y = 85.5 + carrier_size_z = 20.0 + + # 烧杯尺寸 + beaker_diameter = 60.0 + + # 计算中央位置 + center_x = (carrier_size_x - beaker_diameter) / 2 + center_y = (carrier_size_y - beaker_diameter) / 2 + center_z = 5.0 + + carrier = BottleCarrier( + name=name, + size_x=carrier_size_x, + size_y=carrier_size_y, + size_z=carrier_size_z, + sites=create_homogeneous_resources( + klass=ResourceHolder, + locations=[Coordinate(center_x, center_y, center_z)], + resource_size_x=beaker_diameter, + resource_size_y=beaker_diameter, + name_prefix=name, + ), + model="1GaoNianYeBottleCarrier", + ) + carrier.num_items_x = 1 + carrier.num_items_y = 1 + carrier.num_items_z = 1 + carrier[0] = YB_gao_nian_ye_Bottle(f"{name}_flask_1") + return carrier + + +# 100ml液体瓶载架 - 单个中央位置 +def YB_1Bottle100mlCarrier(name: str) -> BottleCarrier: + + # 载架尺寸 (mm) + carrier_size_x = 127.8 + carrier_size_y = 85.5 + carrier_size_z = 20.0 + + # 烧杯尺寸 + beaker_diameter = 60.0 + + # 计算中央位置 + center_x = (carrier_size_x - beaker_diameter) / 2 + center_y = (carrier_size_y - beaker_diameter) / 2 + center_z = 5.0 + + carrier = BottleCarrier( + name=name, + size_x=carrier_size_x, + size_y=carrier_size_y, + size_z=carrier_size_z, + sites=create_homogeneous_resources( + klass=ResourceHolder, + locations=[Coordinate(center_x, center_y, center_z)], + resource_size_x=beaker_diameter, + resource_size_y=beaker_diameter, + name_prefix=name, + ), + model="1Bottle100mlCarrier", + ) + carrier.num_items_x = 1 + carrier.num_items_y = 1 + carrier.num_items_z = 1 + carrier[0] = YB_ye_100ml_Bottle(f"{name}_flask_1") + return carrier + +# 5ml分液瓶板 - 4x2布局,8个位置 def YB_6x5ml_DispensingVialCarrier(name: str) -> BottleCarrier: @@ -282,10 +362,10 @@ def YB_6x5ml_DispensingVialCarrier(name: str) -> BottleCarrier: carrier.num_items_z = 1 ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"] for i in range(8): - carrier[i] = YB_5ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}") + carrier[i] = YB_fen_ye_5ml_Bottle(f"{name}_vial_{ordering[i]}") return carrier -"""20ml分液瓶板 - 4x2布局,8个位置""" +# 20ml分液瓶板 - 4x2布局,8个位置 def YB_6x20ml_DispensingVialCarrier(name: str) -> BottleCarrier: @@ -332,10 +412,10 @@ def YB_6x20ml_DispensingVialCarrier(name: str) -> BottleCarrier: carrier.num_items_z = 1 ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"] for i in range(8): - carrier[i] = YB_20ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}") + carrier[i] = YB_fen_ye_20ml_Bottle(f"{name}_vial_{ordering[i]}") return carrier -"""配液瓶(小)板 - 4x2布局,8个位置""" +# 配液瓶(小)板 - 4x2布局,8个位置 def YB_6x_SmallSolutionBottleCarrier(name: str) -> BottleCarrier: @@ -382,12 +462,12 @@ def YB_6x_SmallSolutionBottleCarrier(name: str) -> BottleCarrier: carrier.num_items_z = 1 ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"] for i in range(8): - carrier[i] = YB_Small_Solution_Bottle(f"{name}_bottle_{ordering[i]}") + carrier[i] = YB_pei_ye_xiao_Bottle(f"{name}_bottle_{ordering[i]}") return carrier +# 配液瓶(大)板 - 2x2布局,4个位置 def YB_4x_LargeSolutionBottleCarrier(name: str) -> BottleCarrier: - """配液瓶(大)板 - 2x2布局,4个位置""" # 载架尺寸 (mm) carrier_size_x = 127.8 @@ -432,10 +512,10 @@ def YB_4x_LargeSolutionBottleCarrier(name: str) -> BottleCarrier: carrier.num_items_z = 1 ordering = ["A1", "A2", "B1", "B2"] for i in range(4): - carrier[i] = YB_Large_Solution_Bottle(f"{name}_bottle_{ordering[i]}") + carrier[i] = YB_pei_ye_da_Bottle(f"{name}_bottle_{ordering[i]}") return carrier -"""加样头(大)板 - 1x1布局,1个位置""" +# 加样头(大)板 - 1x1布局,1个位置 def YB_jia_yang_tou_da_1X1_carrier(name: str) -> BottleCarrier: # 载架尺寸 (mm) diff --git a/unilabos/resources/bioyond/YB_bottles.py b/unilabos/resources/bioyond/YB_bottles.py index f38dc36..8ebdb41 100644 --- a/unilabos/resources/bioyond/YB_bottles.py +++ b/unilabos/resources/bioyond/YB_bottles.py @@ -1,6 +1,5 @@ from unilabos.resources.itemized_carrier import Bottle, BottleCarrier # 工厂函数 - """加样头(大)""" def YB_jia_yang_tou_da( name: str, @@ -35,4 +34,130 @@ def YB_ye_Bottle( max_volume=max_volume, barcode=barcode, model="Liquid_Bottle", + ) + +"""100ml液体""" +def YB_ye_100ml_Bottle( + name: str, + diameter: float = 50.0, + height: float = 90.0, + max_volume: float = 100000.0, # 100mL + barcode: str = None, +) -> Bottle: + """创建100ml液体瓶""" + return Bottle( + name=name, + diameter=diameter, + height=height, + max_volume=max_volume, + barcode=barcode, + model="Liquid_Bottle_100ml", + ) + +"""高粘液""" +def YB_gao_nian_ye_Bottle( + name: str, + diameter: float = 40.0, + height: float = 70.0, + max_volume: float = 50000.0, # 50mL + barcode: str = None, +) -> Bottle: + """创建高粘液瓶""" + return Bottle( + name=name, + diameter=diameter, + height=height, + max_volume=max_volume, + barcode=barcode, + model="High_Viscosity_Liquid", + ) + +"""5ml分液瓶""" +def YB_fen_ye_5ml_Bottle( + name: str, + diameter: float = 20.0, + height: float = 50.0, + max_volume: float = 5000.0, # 5mL + barcode: str = None, +) -> Bottle: + """创建5ml分液瓶""" + return Bottle( + name=name, + diameter=diameter, + height=height, + max_volume=max_volume, + barcode=barcode, + model="Separation_Bottle_5ml", + ) + +"""20ml分液瓶""" +def YB_fen_ye_20ml_Bottle( + name: str, + diameter: float = 30.0, + height: float = 65.0, + max_volume: float = 20000.0, # 20mL + barcode: str = None, +) -> Bottle: + """创建20ml分液瓶""" + return Bottle( + name=name, + diameter=diameter, + height=height, + max_volume=max_volume, + barcode=barcode, + model="Separation_Bottle_20ml", + ) + +"""配液瓶(小)""" +def YB_pei_ye_xiao_Bottle( + name: str, + diameter: float = 35.0, + height: float = 60.0, + max_volume: float = 30000.0, # 30mL + barcode: str = None, +) -> Bottle: + """创建配液瓶(小)""" + return Bottle( + name=name, + diameter=diameter, + height=height, + max_volume=max_volume, + barcode=barcode, + model="Mixing_Bottle_Small", + ) + +"""配液瓶(大)""" +def YB_pei_ye_da_Bottle( + name: str, + diameter: float = 55.0, + height: float = 100.0, + max_volume: float = 150000.0, # 150mL + barcode: str = None, +) -> Bottle: + """创建配液瓶(大)""" + return Bottle( + name=name, + diameter=diameter, + height=height, + max_volume=max_volume, + barcode=barcode, + model="Mixing_Bottle_Large", + ) + +"""枪头""" +def YB_Pipette_Tip( + name: str, + diameter: float = 10.0, + height: float = 50.0, + max_volume: float = 1000.0, # 1mL + barcode: str = None, +) -> Bottle: + """创建枪头""" + return Bottle( + name=name, + diameter=diameter, + height=height, + max_volume=max_volume, + barcode=barcode, + model="Pipette_Tip", ) From c35edcece15df8d19a75c40b7b2cc094b481c38a Mon Sep 17 00:00:00 2001 From: calvincao Date: Tue, 28 Oct 2025 11:42:14 +0800 Subject: [PATCH 027/104] =?UTF-8?q?=E9=87=8D=E6=9E=84=20coin=5Fcell=5Fasse?= =?UTF-8?q?mbly=20=E7=9B=AE=E5=BD=95=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- button_battery_station_resources_unilab.json | 2521 +++++++++++++++++ .../workstation/bioyond_studio/config.py | 2 +- .../{coin_cell_assembly => }/__init__.py | 0 .../button_battery_station.py | 0 .../coin_cell_assembly.py | 2 +- .../coin_cell_assembly_a.csv | 18 - .../new_cellconfig3c.json | 2 +- unilabos/registry/devices/laiyu_liquid.yaml | 11 +- unilabos/registry/devices/liquid_handler.yaml | 33 +- 9 files changed, 2552 insertions(+), 37 deletions(-) create mode 100644 button_battery_station_resources_unilab.json rename unilabos/devices/workstation/coin_cell_assembly/{coin_cell_assembly => }/__init__.py (100%) rename unilabos/devices/workstation/coin_cell_assembly/{coin_cell_assembly => }/button_battery_station.py (100%) rename unilabos/devices/workstation/coin_cell_assembly/{coin_cell_assembly => }/coin_cell_assembly.py (99%) rename unilabos/devices/workstation/coin_cell_assembly/{coin_cell_assembly => }/coin_cell_assembly_a.csv (70%) rename unilabos/devices/workstation/coin_cell_assembly/{coin_cell_assembly => }/new_cellconfig3c.json (99%) diff --git a/button_battery_station_resources_unilab.json b/button_battery_station_resources_unilab.json new file mode 100644 index 0000000..2d55750 --- /dev/null +++ b/button_battery_station_resources_unilab.json @@ -0,0 +1,2521 @@ +{ + "nodes": [ + { + "id": "coin_cell_deck", + "name": "coin_cell_deck", + "sample_id": null, + "children": [ + "liaopan1", + "liaopan2", + "\u7535\u6c60\u6599\u76d8" + ], + "parent": null, + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "CoincellDeck", + "size_x": 1000, + "size_y": 1000, + "size_z": 900, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "coin_cell_deck", + "barcode": null + }, + "data": {} + }, + { + "id": "liaopan1", + "name": "liaopan1", + "sample_id": null, + "children": [ + "liaopan1_materialhole_0_0", + "liaopan1_materialhole_0_1", + "liaopan1_materialhole_0_2", + "liaopan1_materialhole_0_3", + "liaopan1_materialhole_1_0", + "liaopan1_materialhole_1_1", + "liaopan1_materialhole_1_2", + "liaopan1_materialhole_1_3", + "liaopan1_materialhole_2_0", + "liaopan1_materialhole_2_1", + "liaopan1_materialhole_2_2", + "liaopan1_materialhole_2_3", + "liaopan1_materialhole_3_0", + "liaopan1_materialhole_3_1", + "liaopan1_materialhole_3_2", + "liaopan1_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120.8, + "size_y": 120.5, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan1_materialhole_0_0", + "B1": "liaopan1_materialhole_0_1", + "C1": "liaopan1_materialhole_0_2", + "D1": "liaopan1_materialhole_0_3", + "A2": "liaopan1_materialhole_1_0", + "B2": "liaopan1_materialhole_1_1", + "C2": "liaopan1_materialhole_1_2", + "D2": "liaopan1_materialhole_1_3", + "A3": "liaopan1_materialhole_2_0", + "B3": "liaopan1_materialhole_2_1", + "C3": "liaopan1_materialhole_2_2", + "D3": "liaopan1_materialhole_2_3", + "A4": "liaopan1_materialhole_3_0", + "B4": "liaopan1_materialhole_3_1", + "C4": "liaopan1_materialhole_3_2", + "D4": "liaopan1_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan1_materialhole_0_0", + "name": "liaopan1_materialhole_0_0", + "sample_id": null, + "children": [ + "jipian1_0" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_0", + "name": "jipian1_0", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_1", + "name": "liaopan1_materialhole_0_1", + "sample_id": null, + "children": [ + "jipian1_1" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_1", + "name": "jipian1_1", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_2", + "name": "liaopan1_materialhole_0_2", + "sample_id": null, + "children": [ + "jipian1_2" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_2", + "name": "jipian1_2", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_0_3", + "name": "liaopan1_materialhole_0_3", + "sample_id": null, + "children": [ + "jipian1_3" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_3", + "name": "jipian1_3", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_0_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_0", + "name": "liaopan1_materialhole_1_0", + "sample_id": null, + "children": [ + "jipian1_4" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_4", + "name": "jipian1_4", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_1", + "name": "liaopan1_materialhole_1_1", + "sample_id": null, + "children": [ + "jipian1_5" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_5", + "name": "jipian1_5", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_2", + "name": "liaopan1_materialhole_1_2", + "sample_id": null, + "children": [ + "jipian1_6" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_6", + "name": "jipian1_6", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_1_3", + "name": "liaopan1_materialhole_1_3", + "sample_id": null, + "children": [ + "jipian1_7" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_7", + "name": "jipian1_7", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_1_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_0", + "name": "liaopan1_materialhole_2_0", + "sample_id": null, + "children": [ + "jipian1_8" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_8", + "name": "jipian1_8", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_1", + "name": "liaopan1_materialhole_2_1", + "sample_id": null, + "children": [ + "jipian1_9" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_9", + "name": "jipian1_9", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_2", + "name": "liaopan1_materialhole_2_2", + "sample_id": null, + "children": [ + "jipian1_10" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_10", + "name": "jipian1_10", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_2_3", + "name": "liaopan1_materialhole_2_3", + "sample_id": null, + "children": [ + "jipian1_11" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_11", + "name": "jipian1_11", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_2_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_0", + "name": "liaopan1_materialhole_3_0", + "sample_id": null, + "children": [ + "jipian1_12" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_12", + "name": "jipian1_12", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_0", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_1", + "name": "liaopan1_materialhole_3_1", + "sample_id": null, + "children": [ + "jipian1_13" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_13", + "name": "jipian1_13", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_1", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_2", + "name": "liaopan1_materialhole_3_2", + "sample_id": null, + "children": [ + "jipian1_14" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_14", + "name": "jipian1_14", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_2", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan1_materialhole_3_3", + "name": "liaopan1_materialhole_3_3", + "sample_id": null, + "children": [ + "jipian1_15" + ], + "parent": "liaopan1", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "jipian1_15", + "name": "jipian1_15", + "sample_id": null, + "children": [], + "parent": "liaopan1_materialhole_3_3", + "type": "container", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, + { + "id": "liaopan2", + "name": "liaopan2", + "sample_id": null, + "children": [ + "liaopan2_materialhole_0_0", + "liaopan2_materialhole_0_1", + "liaopan2_materialhole_0_2", + "liaopan2_materialhole_0_3", + "liaopan2_materialhole_1_0", + "liaopan2_materialhole_1_1", + "liaopan2_materialhole_1_2", + "liaopan2_materialhole_1_3", + "liaopan2_materialhole_2_0", + "liaopan2_materialhole_2_1", + "liaopan2_materialhole_2_2", + "liaopan2_materialhole_2_3", + "liaopan2_materialhole_3_0", + "liaopan2_materialhole_3_1", + "liaopan2_materialhole_3_2", + "liaopan2_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 500, + "y": 0, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120.8, + "size_y": 120.5, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "liaopan2_materialhole_0_0", + "B1": "liaopan2_materialhole_0_1", + "C1": "liaopan2_materialhole_0_2", + "D1": "liaopan2_materialhole_0_3", + "A2": "liaopan2_materialhole_1_0", + "B2": "liaopan2_materialhole_1_1", + "C2": "liaopan2_materialhole_1_2", + "D2": "liaopan2_materialhole_1_3", + "A3": "liaopan2_materialhole_2_0", + "B3": "liaopan2_materialhole_2_1", + "C3": "liaopan2_materialhole_2_2", + "D3": "liaopan2_materialhole_2_3", + "A4": "liaopan2_materialhole_3_0", + "B4": "liaopan2_materialhole_3_1", + "C4": "liaopan2_materialhole_3_2", + "D4": "liaopan2_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "liaopan2_materialhole_0_0", + "name": "liaopan2_materialhole_0_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_1", + "name": "liaopan2_materialhole_0_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_2", + "name": "liaopan2_materialhole_0_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_0_3", + "name": "liaopan2_materialhole_0_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_0", + "name": "liaopan2_materialhole_1_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_1", + "name": "liaopan2_materialhole_1_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_2", + "name": "liaopan2_materialhole_1_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_1_3", + "name": "liaopan2_materialhole_1_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_0", + "name": "liaopan2_materialhole_2_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_1", + "name": "liaopan2_materialhole_2_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_2", + "name": "liaopan2_materialhole_2_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_2_3", + "name": "liaopan2_materialhole_2_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_0", + "name": "liaopan2_materialhole_3_0", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 84.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_1", + "name": "liaopan2_materialhole_3_1", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 60.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_2", + "name": "liaopan2_materialhole_3_2", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 36.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "liaopan2_materialhole_3_3", + "name": "liaopan2_materialhole_3_3", + "sample_id": null, + "children": [], + "parent": "liaopan2", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 12.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8", + "name": "\u7535\u6c60\u6599\u76d8", + "sample_id": null, + "children": [ + "\u7535\u6c60\u6599\u76d8_materialhole_0_0", + "\u7535\u6c60\u6599\u76d8_materialhole_0_1", + "\u7535\u6c60\u6599\u76d8_materialhole_0_2", + "\u7535\u6c60\u6599\u76d8_materialhole_0_3", + "\u7535\u6c60\u6599\u76d8_materialhole_1_0", + "\u7535\u6c60\u6599\u76d8_materialhole_1_1", + "\u7535\u6c60\u6599\u76d8_materialhole_1_2", + "\u7535\u6c60\u6599\u76d8_materialhole_1_3", + "\u7535\u6c60\u6599\u76d8_materialhole_2_0", + "\u7535\u6c60\u6599\u76d8_materialhole_2_1", + "\u7535\u6c60\u6599\u76d8_materialhole_2_2", + "\u7535\u6c60\u6599\u76d8_materialhole_2_3", + "\u7535\u6c60\u6599\u76d8_materialhole_3_0", + "\u7535\u6c60\u6599\u76d8_materialhole_3_1", + "\u7535\u6c60\u6599\u76d8_materialhole_3_2", + "\u7535\u6c60\u6599\u76d8_materialhole_3_3" + ], + "parent": "coin_cell_deck", + "type": "container", + "class": "", + "position": { + "x": 100, + "y": 100, + "z": 0 + }, + "config": { + "type": "MaterialPlate", + "size_x": 120.8, + "size_y": 160.5, + "size_z": 10.0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_plate", + "model": null, + "barcode": null, + "ordering": { + "A1": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", + "B1": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", + "C1": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", + "D1": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", + "A2": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", + "B2": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", + "C2": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", + "D2": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", + "A3": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", + "B3": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", + "C3": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", + "D3": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", + "A4": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", + "B4": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", + "C4": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", + "D4": "\u7535\u6c60\u6599\u76d8_materialhole_3_3" + } + }, + "data": {} + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 104.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 80.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 56.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 12.4, + "y": 32.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 104.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 80.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 56.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 36.4, + "y": 32.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 104.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 80.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 56.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 60.4, + "y": 32.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 104.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 80.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 56.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + }, + { + "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_3", + "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_3", + "sample_id": null, + "children": [], + "parent": "\u7535\u6c60\u6599\u76d8", + "type": "container", + "class": "", + "position": { + "x": 84.4, + "y": 32.25, + "z": 10.0 + }, + "config": { + "type": "MaterialHole", + "size_x": 16, + "size_y": 16, + "size_z": 16, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "material_hole", + "model": null, + "barcode": null + }, + "data": { + "diameter": 20, + "depth": 10, + "max_sheets": 1, + "info": null + } + } + ], + "links": [] +} \ No newline at end of file diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 566d30d..1141dfa 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -16,7 +16,7 @@ API_CONFIG = { "report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"), # HTTP 服务配置 - "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.21.32.91"), # HTTP服务监听地址,监听计算机飞连ip地址 + "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.21.33.161"), # HTTP服务监听地址,监听计算机飞连ip地址 "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), "debug_mode": False,# 调试模式 } diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/__init__.py b/unilabos/devices/workstation/coin_cell_assembly/__init__.py similarity index 100% rename from unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/__init__.py rename to unilabos/devices/workstation/coin_cell_assembly/__init__.py diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/button_battery_station.py b/unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py similarity index 100% rename from unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/button_battery_station.py rename to unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py similarity index 99% rename from unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/coin_cell_assembly.py rename to unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index 750a34f..a746eb8 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -22,7 +22,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): def __init__( self, station_resource: CoincellDeck, - address: str = "192.168.1.20", + address: str = "172.21.32.20", port: str = "502", debug_mode: bool = True, *args, diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/coin_cell_assembly_a.csv b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv similarity index 70% rename from unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/coin_cell_assembly_a.csv rename to unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv index 836fb71..ebfb998 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/coin_cell_assembly_a.csv +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv @@ -43,21 +43,3 @@ REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,10000, UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,8730, UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,8530, REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,10018,ASSEMBLY_TYPE7or8 -COIL_ALUMINUM_FOIL,BOOL,,ʹ,,coil,8340, -REG_MSG_NE_PLATE_MATRIX,INT16,,Ƭλ,,hold_register,440, -REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,Ĥλ,,hold_register,450, -REG_MSG_TIP_BOX_MATRIX,INT16,,Һǹͷλ,,hold_register,480, -REG_MSG_NE_PLATE_NUM,INT16,,Ƭ,,hold_register,443, -REG_MSG_SEPARATOR_PLATE_NUM,INT16,,Ĥ,,hold_register,453, -REG_MSG_PRESS_MODE,BOOL,,ѹģʽfalse:ѹģʽTrue:ģʽ,,coil,8360,ѹģʽ -,,,,,,, -,BOOL,,Ӿλfalse:ʹãtrue:ԣ,,coil,8300,Ӿλ -,BOOL,,죨false:ʹãtrue:ԣ,,coil,8310,Ӿ -,BOOL,,_֣false:ʹãtrue:ԣ,,coil,8320, -,BOOL,,_Ҳ֣false:ʹãtrue:ԣ,,coil,8420,Ҳ -,BOOL,,ռ֪false:ʹãtrue:ԣ,,coil,8350,ռ֪ -,BOOL,,Һģʽfalse:εҺtrue:εҺ,,coil,8370,Һģʽ -,BOOL,,Ƭأfalse:ʹãtrue:ԣ,,coil,8380,Ƭ -,BOOL,,Ƭװʽfalse:װtrue:װ,,coil,8390,װ -,BOOL,,ѹࣨfalse:ʹãtrue:ԣ,,coil,8400,ѹ -,BOOL,,̷̰ʽfalse:ˮƽ̣true:ѵ̣,,coil,8410,Ƭ̷ʽ diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/new_cellconfig3c.json b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json similarity index 99% rename from unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/new_cellconfig3c.json rename to unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json index 630faa5..8efce63 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly/new_cellconfig3c.json +++ b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json @@ -25,7 +25,7 @@ } }, - "address": "192.168.1.20", + "address": "172.21.32.20", "port": 502 }, "data": {} diff --git a/unilabos/registry/devices/laiyu_liquid.yaml b/unilabos/registry/devices/laiyu_liquid.yaml index 64c0c18..98201a7 100644 --- a/unilabos/registry/devices/laiyu_liquid.yaml +++ b/unilabos/registry/devices/laiyu_liquid.yaml @@ -1361,7 +1361,8 @@ laiyu_liquid: mix_liquid_height: 0.0 mix_rate: 0 mix_stage: '' - mix_times: 0 + mix_times: + - 0 mix_vol: 0 none_keys: - '' @@ -1491,9 +1492,11 @@ laiyu_liquid: mix_stage: type: string mix_times: - maximum: 2147483647 - minimum: -2147483648 - type: integer + items: + maximum: 2147483647 + minimum: -2147483648 + type: integer + type: array mix_vol: maximum: 2147483647 minimum: -2147483648 diff --git a/unilabos/registry/devices/liquid_handler.yaml b/unilabos/registry/devices/liquid_handler.yaml index 99c9233..b21ccd7 100644 --- a/unilabos/registry/devices/liquid_handler.yaml +++ b/unilabos/registry/devices/liquid_handler.yaml @@ -3994,7 +3994,8 @@ liquid_handler: mix_liquid_height: 0.0 mix_rate: 0 mix_stage: '' - mix_times: 0 + mix_times: + - 0 mix_vol: 0 none_keys: - '' @@ -4150,9 +4151,11 @@ liquid_handler: mix_stage: type: string mix_times: - maximum: 2147483647 - minimum: -2147483648 - type: integer + items: + maximum: 2147483647 + minimum: -2147483648 + type: integer + type: array mix_vol: maximum: 2147483647 minimum: -2147483648 @@ -5012,7 +5015,8 @@ liquid_handler.biomek: mix_liquid_height: 0.0 mix_rate: 0 mix_stage: '' - mix_times: 0 + mix_times: + - 0 mix_vol: 0 none_keys: - '' @@ -5155,9 +5159,11 @@ liquid_handler.biomek: mix_stage: type: string mix_times: - maximum: 2147483647 - minimum: -2147483648 - type: integer + items: + maximum: 2147483647 + minimum: -2147483648 + type: integer + type: array mix_vol: maximum: 2147483647 minimum: -2147483648 @@ -7801,7 +7807,8 @@ liquid_handler.prcxi: mix_liquid_height: 0.0 mix_rate: 0 mix_stage: '' - mix_times: 0 + mix_times: + - 0 mix_vol: 0 none_keys: - '' @@ -7930,9 +7937,11 @@ liquid_handler.prcxi: mix_stage: type: string mix_times: - maximum: 2147483647 - minimum: -2147483648 - type: integer + items: + maximum: 2147483647 + minimum: -2147483648 + type: integer + type: array mix_vol: maximum: 2147483647 minimum: -2147483648 From a130c03ebdd45f6f406dd5bdf0ba30dbd3ddee5c Mon Sep 17 00:00:00 2001 From: calvincao Date: Wed, 29 Oct 2025 10:44:30 +0800 Subject: [PATCH 028/104] =?UTF-8?q?feat(workstation):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=97=A7=E7=89=88bioyond=E8=AE=BE=E5=A4=87=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=B9=B6=E4=BC=98=E5=8C=96=E6=89=A3=E7=94=B5=E7=BB=84=E8=A3=85?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E7=AB=99-=20=E5=88=A0=E9=99=A4bioyond.yaml?= =?UTF-8?q?=E5=92=8Cbioyond=5Fdispensing=5Fstation.yaml=E6=97=A7=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6-=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=89=A3=E7=94=B5=E7=BB=84=E8=A3=85=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E7=AB=99=E9=85=8D=E7=BD=AE=EF=BC=8C=E7=A7=BB=E9=99=A4=E4=B8=8D?= =?UTF-8?q?=E5=BF=85=E8=A6=81=E7=9A=84=E5=AD=90=E8=B5=84=E6=BA=90=E5=BC=95?= =?UTF-8?q?=E7=94=A8-=20=E6=9B=B4=E6=96=B0Modbus=E9=80=9A=E4=BF=A1?= =?UTF-8?q?=E5=9C=B0=E5=9D=80=E5=92=8C=E7=AB=AF=E5=8F=A3=E9=85=8D=E7=BD=AE?= =?UTF-8?q?-=20=E7=AE=80=E5=8C=96CoinCellAssemblyWorkstation=E7=B1=BB?= =?UTF-8?q?=E7=9A=84=E5=88=9D=E5=A7=8B=E5=8C=96=E5=8F=82=E6=95=B0-=20?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=86=97=E4=BD=99=E7=9A=84deck=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E5=88=9B=E5=BB=BA=E9=80=BB=E8=BE=91=20-=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=8F=8D=E5=BA=94=E7=AB=99=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=B8=ADdrip=5Fback=E5=91=BD=E4=BB=A4=E7=9A=84?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=20-=20=E6=B7=BB=E5=8A=A0=E6=96=B0=E7=9A=84Mo?= =?UTF-8?q?dbus=E5=AF=84=E5=AD=98=E5=99=A8=E5=92=8C=E7=BA=BF=E5=9C=88?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=20-=20=E7=A7=BB=E9=99=A4workstation=5Fbase.p?= =?UTF-8?q?y=E5=9F=BA=E7=B1=BB=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bioyond_yihua_YB.json | 23 +- .../coin_cell_assembly/coin_cell_assembly.py | 35 +- .../coin_cell_assembly_a.csv | 18 + .../coin_cell_assembly/workstation_base.py | 489 ------------------ unilabos/registry/devices/bioyond.yaml | 255 --------- .../devices/bioyond_dispensing_station.yaml | 404 --------------- .../devices/reaction_station_bioyond.yaml | 84 +-- 7 files changed, 88 insertions(+), 1220 deletions(-) delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/workstation_base.py delete mode 100644 unilabos/registry/devices/bioyond.yaml delete mode 100644 unilabos/registry/devices/bioyond_dispensing_station.yaml diff --git a/bioyond_yihua_YB.json b/bioyond_yihua_YB.json index 1cd7f00..c38179d 100644 --- a/bioyond_yihua_YB.json +++ b/bioyond_yihua_YB.json @@ -17,29 +17,16 @@ { "id": "BatteryStation", "name": "扣电组装工作站", - "children": [ - "coin_cell_deck" - ], + "children": [], "parent": null, "type": "device", "class": "bettery_station_registry", - "position": { - "x": 600, - "y": 400, - "z": 0 - }, "config": { - "debug_mode": true, - "_comment": "protocol_type接外部工站固定写法字段,一般为空,deck写法也固定", + "debug_mode": false, + "protocol_type": [], - "deck": { - "data": { - "_resource_child_name": "coin_cell_deck", - "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" - } - }, - - "address": "192.168.1.20", + "deck": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck", + "address": "172.21.32.20", "port": 502 }, "data": {} diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index a746eb8..51d668b 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -8,34 +8,34 @@ from typing import Any, Dict, Optional from pylabrobot.resources import Resource as PLRResource from unilabos_msgs.msg import Resource from unilabos.device_comms.modbus_plc.client import ModbusTcpClient -from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import MaterialHole, MaterialPlate from unilabos.devices.workstation.workstation_base import WorkstationBase from unilabos.device_comms.modbus_plc.client import TCPClient, ModbusNode, PLCWorkflow, ModbusWorkflow, WorkflowAction, BaseClient from unilabos.device_comms.modbus_plc.modbus import DeviceType, Base as ModbusNodeBase, DataType, WorderOrder from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import * from unilabos.ros.nodes.base_device_node import ROS2DeviceNode, BaseROS2DeviceNode from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode +from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import CoincellDeck #构建物料系统 class CoinCellAssemblyWorkstation(WorkstationBase): def __init__( self, - station_resource: CoincellDeck, + deck: Deck=None, address: str = "172.21.32.20", port: str = "502", - debug_mode: bool = True, + debug_mode: bool = False, *args, **kwargs, ): super().__init__( #桌子 - station_resource=station_resource, + deck=deck, *args, **kwargs, ) self.debug_mode = debug_mode - self.station_resource = station_resource + self.deck = deck """ 连接初始化 """ modbus_client = TCPClient(addr=address, port=port) print("modbus_client", modbus_client) @@ -75,7 +75,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): self._ros_node = ros_node #self.deck = create_a_coin_cell_deck() ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ - "resources": [self.station_resource] + "resources": [self.deck] }) # 批量操作在这里写 @@ -85,7 +85,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): async def fill_plate(self): - plate_1: MaterialPlate = self.station_resource.children[0].children[0] + plate_1: MaterialPlate = self.deck.children[0].children[0] #plate_1 return await self._ros_node.update_resource(plate_1) @@ -342,7 +342,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): def modify_deck_name(self, resource_name: str): # figure_res = self._ros_node.resource_tracker.figure_resource({"name": resource_name}) # print(f"!!! figure_res: {type(figure_res)}") - self.station_resource.children[1] + self.deck.children[1] return @property @@ -1105,8 +1105,17 @@ class CoinCellAssemblyWorkstation(WorkstationBase): if __name__ == "__main__": - from pylabrobot.resources import Resource - Coin_Cell = CoinCellAssemblyWorkstation(Resource("1", 1, 1, 1), debug_mode=True) + # modbus_client = TCPClient(addr="172.21.32.20", port="502") + # # modbus_client.client.connect() + # nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_a.csv')) + # client = modbus_client.register_node_list(nodes) + # # print("modbus_client", modbus_client) + # while True: + # time.sleep(1) + # cmd_feedback, read_err = modbus_client.use_node('COIL_SYS_AUTO_CMD').read(1) + # print("modbus_client", cmd_feedback) + Coin_Cell = CoinCellAssemblyWorkstation() + print(Coin_Cell.deck) #Coin_Cell.func_pack_device_init() #Coin_Cell.func_pack_device_auto() #Coin_Cell.func_pack_device_start() @@ -1121,7 +1130,7 @@ if __name__ == "__main__": #print("success") #创建一个物料台面 - deck = create_a_coin_cell_deck() + # deck = create_a_coin_cell_deck() #deck = create_a_full_coin_cell_deck() @@ -1169,4 +1178,6 @@ if __name__ == "__main__": #print(resources) http_client.remote_addr = "https://uni-lab.test.bohrium.com/api/v1" - http_client.resource_add(resources) \ No newline at end of file + http_client.resource_add(resources) + + \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv index ebfb998..836fb71 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv @@ -43,3 +43,21 @@ REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,10000, UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,8730, UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,8530, REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,10018,ASSEMBLY_TYPE7or8 +COIL_ALUMINUM_FOIL,BOOL,,ʹ,,coil,8340, +REG_MSG_NE_PLATE_MATRIX,INT16,,Ƭλ,,hold_register,440, +REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,Ĥλ,,hold_register,450, +REG_MSG_TIP_BOX_MATRIX,INT16,,Һǹͷλ,,hold_register,480, +REG_MSG_NE_PLATE_NUM,INT16,,Ƭ,,hold_register,443, +REG_MSG_SEPARATOR_PLATE_NUM,INT16,,Ĥ,,hold_register,453, +REG_MSG_PRESS_MODE,BOOL,,ѹģʽfalse:ѹģʽTrue:ģʽ,,coil,8360,ѹģʽ +,,,,,,, +,BOOL,,Ӿλfalse:ʹãtrue:ԣ,,coil,8300,Ӿλ +,BOOL,,죨false:ʹãtrue:ԣ,,coil,8310,Ӿ +,BOOL,,_֣false:ʹãtrue:ԣ,,coil,8320, +,BOOL,,_Ҳ֣false:ʹãtrue:ԣ,,coil,8420,Ҳ +,BOOL,,ռ֪false:ʹãtrue:ԣ,,coil,8350,ռ֪ +,BOOL,,Һģʽfalse:εҺtrue:εҺ,,coil,8370,Һģʽ +,BOOL,,Ƭأfalse:ʹãtrue:ԣ,,coil,8380,Ƭ +,BOOL,,Ƭװʽfalse:װtrue:װ,,coil,8390,װ +,BOOL,,ѹࣨfalse:ʹãtrue:ԣ,,coil,8400,ѹ +,BOOL,,̷̰ʽfalse:ˮƽ̣true:ѵ̣,,coil,8410,Ƭ̷ʽ diff --git a/unilabos/devices/workstation/coin_cell_assembly/workstation_base.py b/unilabos/devices/workstation/coin_cell_assembly/workstation_base.py deleted file mode 100644 index 65b34a9..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/workstation_base.py +++ /dev/null @@ -1,489 +0,0 @@ -""" -工作站基类 -Workstation Base Class - 简化版 - -基于PLR Deck的简化工作站架构 -专注于核心物料系统和工作流管理 -""" - -import collections -import time -from typing import Dict, Any, List, Optional, Union -from abc import ABC, abstractmethod -from dataclasses import dataclass -from enum import Enum -from pylabrobot.resources import Deck, Plate, Resource as PLRResource - -from pylabrobot.resources.coordinate import Coordinate -from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode - -from unilabos.utils.log import logger - - -class WorkflowStatus(Enum): - """工作流状态""" - - IDLE = "idle" - INITIALIZING = "initializing" - RUNNING = "running" - PAUSED = "paused" - STOPPING = "stopping" - STOPPED = "stopped" - ERROR = "error" - COMPLETED = "completed" - - -@dataclass -class WorkflowInfo: - """工作流信息""" - - name: str - description: str - estimated_duration: float # 预估持续时间(秒) - required_materials: List[str] # 所需物料类型 - output_product: str # 输出产品类型 - parameters_schema: Dict[str, Any] # 参数架构 - - -class WorkStationContainer(Plate): - """ - WorkStation 专用 Container 类,继承自 Plate和TipRack - 注意这个物料必须通过plr_additional_res_reg.py注册到edge,才能正常序列化 - """ - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - category: str, - ordering: collections.OrderedDict, - model: Optional[str] = None, - ): - """ - 这里的初始化入参要和plr的保持一致 - """ - super().__init__(name, size_x, size_y, size_z, category=category, ordering=ordering, model=model) - self._unilabos_state = {} # 必须有此行,自己的类描述的是物料的 - - def load_state(self, state: Dict[str, Any]) -> None: - """从给定的状态加载工作台信息。""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - data = super().serialize_state() - data.update( - self._unilabos_state - ) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - - -def get_workstation_plate_resource(name: str) -> PLRResource: # 要给定一个返回plr的方法 - """ - 用于获取一些模板,例如返回一个带有特定信息/子物料的 Plate,这里需要到注册表注册,例如unilabos/registry/resources/organic/workstation.yaml - 可以直接运行该函数或者利用注册表补全机制,来检查是否资源出错 - :param name: 资源名称 - :return: Resource对象 - """ - plate = WorkStationContainer( - name, size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict() - ) - tip_rack = WorkStationContainer( - "tip_rack_inside_plate", - size_x=50, - size_y=50, - size_z=10, - category="tip_rack", - ordering=collections.OrderedDict(), - ) - plate.assign_child_resource(tip_rack, Coordinate.zero()) - return plate - - -class ResourceSynchronizer(ABC): - """资源同步器基类 - - 负责与外部物料系统的同步,并对 self.deck 做修改 - """ - - def __init__(self, workstation: "WorkstationBase"): - self.workstation = workstation - - @abstractmethod - async def sync_from_external(self) -> bool: - """从外部系统同步物料到本地deck""" - pass - - @abstractmethod - async def sync_to_external(self, plr_resource: PLRResource) -> bool: - """将本地物料同步到外部系统""" - pass - - @abstractmethod - async def handle_external_change(self, change_info: Dict[str, Any]) -> bool: - """处理外部系统的变更通知""" - pass - - -class WorkstationBase(ABC): - """工作站基类 - 简化版 - - 核心功能: - 1. 基于 PLR Deck 的物料系统,支持格式转换 - 2. 可选的资源同步器支持外部物料系统 - 3. 简化的工作流管理 - """ - - _ros_node: ROS2WorkstationNode - - @property - def _children(self) -> Dict[str, Any]: # 不要删除这个下划线,不然会自动导入注册表,后面改成装饰器识别 - return self._ros_node.children - - async def update_resource_example(self): - return await self._ros_node.update_resource([get_workstation_plate_resource("test")]) - - def __init__( - self, - station_resource: PLRResource, - *args, - **kwargs, # 必须有kwargs - ): - # 基本配置 - print(station_resource) - self.deck_config = station_resource - - # PLR 物料系统 - self.deck: Optional[Deck] = None - self.plr_resources: Dict[str, PLRResource] = {} - - # 资源同步器(可选) - # self.resource_synchronizer = ResourceSynchronizer(self) # 要在driver中自行初始化,只有workstation用 - - # 硬件接口 - self.hardware_interface: Union[Any, str] = None - - # 工作流状态 - self.current_workflow_status = WorkflowStatus.IDLE - self.current_workflow_info = None - self.workflow_start_time = None - self.workflow_parameters = {} - - # 支持的工作流(静态预定义) - self.supported_workflows: Dict[str, WorkflowInfo] = {} - - # 初始化物料系统 - self._initialize_material_system() - - # 注册支持的工作流 - # self._register_supported_workflows() - - # logger.info(f"工作站 {device_id} 初始化完成(简化版)") - - def _initialize_material_system(self): - """初始化物料系统 - 使用 graphio 转换""" - try: - from unilabos.resources.graphio import resource_ulab_to_plr - - # # 1. 合并 deck_config 和 children 创建完整的资源树 - # complete_resource_config = self._create_complete_resource_config() - - # # 2. 使用 graphio 转换为 PLR 资源 - # self.deck = resource_ulab_to_plr(complete_resource_config, plr_model=True) - - # # 3. 建立资源映射 - # self._build_resource_mappings(self.deck) - - # # 4. 如果有资源同步器,执行初始同步 - # if self.resource_synchronizer: - # # 这里可以异步执行,暂时跳过 - # pass - - # logger.info(f"工作站 {self.device_id} 物料系统初始化成功,创建了 {len(self.plr_resources)} 个资源") - pass - except Exception as e: - # logger.error(f"工作站 {self.device_id} 物料系统初始化失败: {e}") - raise - - def _create_complete_resource_config(self) -> Dict[str, Any]: - """创建完整的资源配置 - 合并 deck_config 和 children""" - # 创建主 deck 配置 - deck_resource = { - "id": f"{self.device_id}_deck", - "name": f"{self.device_id}_deck", - "type": "deck", - "position": {"x": 0, "y": 0, "z": 0}, - "config": { - "size_x": self.deck_config.get("size_x", 1000.0), - "size_y": self.deck_config.get("size_y", 1000.0), - "size_z": self.deck_config.get("size_z", 100.0), - **{k: v for k, v in self.deck_config.items() if k not in ["size_x", "size_y", "size_z"]}, - }, - "data": {}, - "children": [], - "parent": None, - } - - # 添加子资源 - if self._children: - children_list = [] - for child_id, child_config in self._children.items(): - child_resource = self._normalize_child_resource(child_id, child_config, deck_resource["id"]) - children_list.append(child_resource) - deck_resource["children"] = children_list - - return deck_resource - - def _normalize_child_resource(self, resource_id: str, config: Dict[str, Any], parent_id: str) -> Dict[str, Any]: - """标准化子资源配置""" - return { - "id": resource_id, - "name": config.get("name", resource_id), - "type": config.get("type", "container"), - "position": self._normalize_position(config.get("position", {})), - "config": config.get("config", {}), - "data": config.get("data", {}), - "children": [], # 简化版本:只支持一层子资源 - "parent": parent_id, - } - - def _normalize_position(self, position: Any) -> Dict[str, float]: - """标准化位置信息""" - if isinstance(position, dict): - return { - "x": float(position.get("x", 0)), - "y": float(position.get("y", 0)), - "z": float(position.get("z", 0)), - } - elif isinstance(position, (list, tuple)) and len(position) >= 2: - return { - "x": float(position[0]), - "y": float(position[1]), - "z": float(position[2]) if len(position) > 2 else 0.0, - } - else: - return {"x": 0.0, "y": 0.0, "z": 0.0} - - def _build_resource_mappings(self, deck: Deck): - """递归构建资源映射""" - - def add_resource_recursive(resource: PLRResource): - if hasattr(resource, "name"): - self.plr_resources[resource.name] = resource - - if hasattr(resource, "children"): - for child in resource.children: - add_resource_recursive(child) - - add_resource_recursive(deck) - - # ============ 硬件接口管理 ============ - - def set_hardware_interface(self, hardware_interface: Union[Any, str]): - """设置硬件接口""" - self.hardware_interface = hardware_interface - logger.info(f"工作站 {self.device_id} 硬件接口设置: {type(hardware_interface).__name__}") - - def set_workstation_node(self, workstation_node: "ROS2WorkstationNode"): - """设置协议节点引用(用于代理模式)""" - self._ros_node = workstation_node - logger.info(f"工作站 {self.device_id} 关联协议节点") - - # ============ 设备操作接口 ============ - - def call_device_method(self, method: str, *args, **kwargs) -> Any: - """调用设备方法的统一接口""" - # 1. 代理模式:通过协议节点转发 - if isinstance(self.hardware_interface, str) and self.hardware_interface.startswith("proxy:"): - if not self._ros_node: - raise RuntimeError("代理模式需要设置workstation_node") - - device_id = self.hardware_interface[6:] # 移除 "proxy:" 前缀 - return self._ros_node.call_device_method(device_id, method, *args, **kwargs) - - # 2. 直接模式:直接调用硬件接口方法 - elif self.hardware_interface and hasattr(self.hardware_interface, method): - return getattr(self.hardware_interface, method)(*args, **kwargs) - - else: - raise AttributeError(f"硬件接口不支持方法: {method}") - - def get_device_status(self) -> Dict[str, Any]: - """获取设备状态""" - try: - return self.call_device_method("get_status") - except AttributeError: - # 如果设备不支持get_status方法,返回基础状态 - return { - "status": "unknown", - "interface_type": type(self.hardware_interface).__name__, - "timestamp": time.time(), - } - - def is_device_available(self) -> bool: - """检查设备是否可用""" - try: - self.get_device_status() - return True - except: - return False - - # ============ 物料系统接口 ============ - - def get_deck(self) -> Deck: - """获取主 Deck""" - return self.deck - - def get_all_resources(self) -> Dict[str, PLRResource]: - """获取所有 PLR 资源""" - return self.plr_resources.copy() - - def find_resource_by_name(self, name: str) -> Optional[PLRResource]: - """按名称查找资源""" - return self.plr_resources.get(name) - - def find_resources_by_type(self, resource_type: type) -> List[PLRResource]: - """按类型查找资源""" - return [res for res in self.plr_resources.values() if isinstance(res, resource_type)] - - async def sync_with_external_system(self) -> bool: - """与外部物料系统同步""" - if not self.resource_synchronizer: - logger.info(f"工作站 {self.device_id} 没有配置资源同步器") - return True - - try: - success = await self.resource_synchronizer.sync_from_external() - if success: - logger.info(f"工作站 {self.device_id} 外部同步成功") - else: - logger.warning(f"工作站 {self.device_id} 外部同步失败") - return success - except Exception as e: - logger.error(f"工作站 {self.device_id} 外部同步异常: {e}") - return False - - # ============ 简化的工作流控制 ============ - - def execute_workflow(self, workflow_name: str, parameters: Dict[str, Any]) -> bool: - """执行工作流""" - try: - # 设置工作流状态 - self.current_workflow_status = WorkflowStatus.INITIALIZING - self.workflow_parameters = parameters - self.workflow_start_time = time.time() - - # 委托给子类实现 - success = self._execute_workflow_impl(workflow_name, parameters) - - if success: - self.current_workflow_status = WorkflowStatus.RUNNING - logger.info(f"工作站 {self.device_id} 工作流 {workflow_name} 启动成功") - else: - self.current_workflow_status = WorkflowStatus.ERROR - logger.error(f"工作站 {self.device_id} 工作流 {workflow_name} 启动失败") - - return success - - except Exception as e: - self.current_workflow_status = WorkflowStatus.ERROR - logger.error(f"工作站 {self.device_id} 执行工作流失败: {e}") - return False - - def stop_workflow(self, emergency: bool = False) -> bool: - """停止工作流""" - try: - if self.current_workflow_status in [WorkflowStatus.IDLE, WorkflowStatus.STOPPED]: - logger.warning(f"工作站 {self.device_id} 没有正在运行的工作流") - return True - - self.current_workflow_status = WorkflowStatus.STOPPING - - # 委托给子类实现 - success = self._stop_workflow_impl(emergency) - - if success: - self.current_workflow_status = WorkflowStatus.STOPPED - logger.info(f"工作站 {self.device_id} 工作流停止成功 (紧急: {emergency})") - else: - self.current_workflow_status = WorkflowStatus.ERROR - logger.error(f"工作站 {self.device_id} 工作流停止失败") - - return success - - except Exception as e: - self.current_workflow_status = WorkflowStatus.ERROR - logger.error(f"工作站 {self.device_id} 停止工作流失败: {e}") - return False - - # ============ 状态属性 ============ - - @property - def workflow_status(self) -> WorkflowStatus: - """获取当前工作流状态""" - return self.current_workflow_status - - @property - def is_busy(self) -> bool: - """检查工作站是否忙碌""" - return self.current_workflow_status in [ - WorkflowStatus.INITIALIZING, - WorkflowStatus.RUNNING, - WorkflowStatus.STOPPING, - ] - - @property - def workflow_runtime(self) -> float: - """获取工作流运行时间(秒)""" - if self.workflow_start_time is None: - return 0.0 - return time.time() - self.workflow_start_time - - # ============ 抽象方法 - 子类必须实现 ============ - - # @abstractmethod - # def _register_supported_workflows(self): - # """注册支持的工作流 - 子类必须实现""" - # pass - - # @abstractmethod - # def _execute_workflow_impl(self, workflow_name: str, parameters: Dict[str, Any]) -> bool: - # """执行工作流的具体实现 - 子类必须实现""" - # pass - - # @abstractmethod - # def _stop_workflow_impl(self, emergency: bool = False) -> bool: - # """停止工作流的具体实现 - 子类必须实现""" - # pass - -class WorkstationExample(WorkstationBase): - """工作站示例实现""" - - def _register_supported_workflows(self): - """注册支持的工作流""" - self.supported_workflows["example_workflow"] = WorkflowInfo( - name="example_workflow", - description="这是一个示例工作流", - estimated_duration=300.0, - required_materials=["sample_plate"], - output_product="processed_plate", - parameters_schema={"param1": "string", "param2": "integer"}, - ) - - def _execute_workflow_impl(self, workflow_name: str, parameters: Dict[str, Any]) -> bool: - """执行工作流的具体实现""" - if workflow_name not in self.supported_workflows: - logger.error(f"工作站 {self.device_id} 不支持工作流: {workflow_name}") - return False - - # 这里添加实际的工作流逻辑 - logger.info(f"工作站 {self.device_id} 正在执行工作流: {workflow_name} with parameters {parameters}") - return True - - def _stop_workflow_impl(self, emergency: bool = False) -> bool: - """停止工作流的具体实现""" - # 这里添加实际的停止逻辑 - logger.info(f"工作站 {self.device_id} 正在停止工作流 (紧急: {emergency})") - return True \ No newline at end of file diff --git a/unilabos/registry/devices/bioyond.yaml b/unilabos/registry/devices/bioyond.yaml deleted file mode 100644 index fe6f090..0000000 --- a/unilabos/registry/devices/bioyond.yaml +++ /dev/null @@ -1,255 +0,0 @@ -workstation.bioyond_dispensing_station: - category: - - workstation - - bioyond - class: - action_value_mappings: - create_90_10_vial_feeding_task: - feedback: {} - goal: - delay_time: delay_time - hold_m_name: hold_m_name - order_name: order_name - percent_10_1_assign_material_name: percent_10_1_assign_material_name - percent_10_1_liquid_material_name: percent_10_1_liquid_material_name - percent_10_1_target_weigh: percent_10_1_target_weigh - percent_10_1_volume: percent_10_1_volume - percent_10_2_assign_material_name: percent_10_2_assign_material_name - percent_10_2_liquid_material_name: percent_10_2_liquid_material_name - percent_10_2_target_weigh: percent_10_2_target_weigh - percent_10_2_volume: percent_10_2_volume - percent_10_3_assign_material_name: percent_10_3_assign_material_name - percent_10_3_liquid_material_name: percent_10_3_liquid_material_name - percent_10_3_target_weigh: percent_10_3_target_weigh - percent_10_3_volume: percent_10_3_volume - percent_90_1_assign_material_name: percent_90_1_assign_material_name - percent_90_1_target_weigh: percent_90_1_target_weigh - percent_90_2_assign_material_name: percent_90_2_assign_material_name - percent_90_2_target_weigh: percent_90_2_target_weigh - percent_90_3_assign_material_name: percent_90_3_assign_material_name - percent_90_3_target_weigh: percent_90_3_target_weigh - speed: speed - temperature: temperature - goal_default: - delay_time: '' - hold_m_name: '' - order_name: '' - percent_10_1_assign_material_name: '' - percent_10_1_liquid_material_name: '' - percent_10_1_target_weigh: '' - percent_10_1_volume: '' - percent_10_2_assign_material_name: '' - percent_10_2_liquid_material_name: '' - percent_10_2_target_weigh: '' - percent_10_2_volume: '' - percent_10_3_assign_material_name: '' - percent_10_3_liquid_material_name: '' - percent_10_3_target_weigh: '' - percent_10_3_volume: '' - percent_90_1_assign_material_name: '' - percent_90_1_target_weigh: '' - percent_90_2_assign_material_name: '' - percent_90_2_target_weigh: '' - percent_90_3_assign_material_name: '' - percent_90_3_target_weigh: '' - speed: '' - temperature: '' - handles: {} - result: - return_info: return_info - schema: - description: '' - properties: - feedback: - properties: {} - required: [] - title: DispenStationVialFeed_Feedback - type: object - goal: - properties: - delay_time: - type: string - hold_m_name: - type: string - order_name: - type: string - percent_10_1_assign_material_name: - type: string - percent_10_1_liquid_material_name: - type: string - percent_10_1_target_weigh: - type: string - percent_10_1_volume: - type: string - percent_10_2_assign_material_name: - type: string - percent_10_2_liquid_material_name: - type: string - percent_10_2_target_weigh: - type: string - percent_10_2_volume: - type: string - percent_10_3_assign_material_name: - type: string - percent_10_3_liquid_material_name: - type: string - percent_10_3_target_weigh: - type: string - percent_10_3_volume: - type: string - percent_90_1_assign_material_name: - type: string - percent_90_1_target_weigh: - type: string - percent_90_2_assign_material_name: - type: string - percent_90_2_target_weigh: - type: string - percent_90_3_assign_material_name: - type: string - percent_90_3_target_weigh: - type: string - speed: - type: string - temperature: - type: string - required: - - order_name - - percent_90_1_assign_material_name - - percent_90_1_target_weigh - - percent_90_2_assign_material_name - - percent_90_2_target_weigh - - percent_90_3_assign_material_name - - percent_90_3_target_weigh - - percent_10_1_assign_material_name - - percent_10_1_target_weigh - - percent_10_1_volume - - percent_10_1_liquid_material_name - - percent_10_2_assign_material_name - - percent_10_2_target_weigh - - percent_10_2_volume - - percent_10_2_liquid_material_name - - percent_10_3_assign_material_name - - percent_10_3_target_weigh - - percent_10_3_volume - - percent_10_3_liquid_material_name - - speed - - temperature - - delay_time - - hold_m_name - title: DispenStationVialFeed_Goal - type: object - result: - properties: - return_info: - type: string - required: - - return_info - title: DispenStationVialFeed_Result - type: object - required: - - goal - title: DispenStationVialFeed - type: object - type: DispenStationVialFeed - create_diamine_solution_task: - feedback: {} - goal: - delay_time: delay_time - hold_m_name: hold_m_name - liquid_material_name: liquid_material_name - material_name: material_name - order_name: order_name - speed: speed - target_weigh: target_weigh - temperature: temperature - volume: volume - goal_default: - delay_time: '' - hold_m_name: '' - liquid_material_name: '' - material_name: '' - order_name: '' - speed: '' - target_weigh: '' - temperature: '' - volume: '' - handles: {} - result: - return_info: return_info - schema: - description: '' - properties: - feedback: - properties: {} - required: [] - title: DispenStationSolnPrep_Feedback - type: object - goal: - properties: - delay_time: - type: string - hold_m_name: - type: string - liquid_material_name: - type: string - material_name: - type: string - order_name: - type: string - speed: - type: string - target_weigh: - type: string - temperature: - type: string - volume: - type: string - required: - - order_name - - material_name - - target_weigh - - volume - - liquid_material_name - - speed - - temperature - - delay_time - - hold_m_name - title: DispenStationSolnPrep_Goal - type: object - result: - properties: - return_info: - type: string - required: - - return_info - title: DispenStationSolnPrep_Result - type: object - required: - - goal - title: DispenStationSolnPrep - type: object - type: DispenStationSolnPrep - module: unilabos.devices.workstation.bioyond_studio.dispensing_station:BioyondDispensingStation - status_types: {} - type: python - config_info: [] - description: '' - handles: [] - icon: '' - init_param_schema: - config: - properties: - config: - type: string - deck: - type: string - required: - - config - - deck - type: object - data: - properties: {} - required: [] - type: object - version: 1.0.0 diff --git a/unilabos/registry/devices/bioyond_dispensing_station.yaml b/unilabos/registry/devices/bioyond_dispensing_station.yaml deleted file mode 100644 index 50a94be..0000000 --- a/unilabos/registry/devices/bioyond_dispensing_station.yaml +++ /dev/null @@ -1,404 +0,0 @@ -bioyond_dispensing_station: - category: - - workstation - - bioyond - - bioyond_dispensing_station - class: - action_value_mappings: - batch_create_90_10_vial_feeding_tasks: - feedback: {} - goal: - delay_time: delay_time - hold_m_name: hold_m_name - liquid_material_name: liquid_material_name - speed: speed - temperature: temperature - titration: titration - goal_default: - delay_time: '600' - hold_m_name: '' - liquid_material_name: NMP - speed: '400' - temperature: '40' - titration: '' - handles: - input: - - data_key: titration - data_source: handle - data_type: object - handler_key: titration - io_type: source - label: Titration Data From Calculation Node - result: - return_info: return_info - schema: - description: 批量创建90%10%小瓶投料任务。从计算节点接收titration数据,包含物料名称、主称固体质量、滴定固体质量和滴定溶剂体积。 - properties: - feedback: - properties: {} - required: [] - title: BatchCreate9010VialFeedingTasks_Feedback - type: object - goal: - properties: - delay_time: - default: '600' - description: 延迟时间(秒),默认600 - type: string - hold_m_name: - description: 库位名称,如"C01",必填参数 - type: string - liquid_material_name: - default: NMP - description: 10%物料的液体物料名称,默认为"NMP" - type: string - speed: - default: '400' - description: 搅拌速度,默认400 - type: string - temperature: - default: '40' - description: 温度(℃),默认40 - type: string - titration: - description: '滴定信息对象,包含: name(物料名称), main_portion(主称固体质量g), titration_portion(滴定固体质量g), - titration_solvent(滴定溶液体积mL)' - type: string - required: - - titration - - hold_m_name - title: BatchCreate9010VialFeedingTasks_Goal - type: object - result: - properties: - return_info: - type: string - required: - - return_info - title: BatchCreate9010VialFeedingTasks_Result - type: object - required: - - goal - title: BatchCreate9010VialFeedingTasks - type: object - type: UniLabJsonCommand - batch_create_diamine_solution_tasks: - feedback: {} - goal: - delay_time: delay_time - liquid_material_name: liquid_material_name - solutions: solutions - speed: speed - temperature: temperature - goal_default: - delay_time: '600' - liquid_material_name: NMP - solutions: '' - speed: '400' - temperature: '20' - handles: - input: - - data_key: solutions - data_source: handle - data_type: array - handler_key: solutions - io_type: source - label: Solution Data From Python - result: - return_info: return_info - schema: - description: 批量创建二胺溶液配置任务。自动为多个二胺样品创建溶液配置任务,每个任务包含固体物料称量、溶剂添加、搅拌混合等步骤。 - properties: - feedback: - properties: {} - required: [] - title: BatchCreateDiamineSolutionTasks_Feedback - type: object - goal: - properties: - delay_time: - default: '600' - description: 溶液配置完成后的延迟时间(秒),用于充分混合和溶解,默认600秒 - type: string - liquid_material_name: - default: NMP - description: 液体溶剂名称,用于溶解固体物料,默认为NMP(N-甲基吡咯烷酮) - type: string - solutions: - description: '溶液列表,JSON数组格式,每个元素包含: name(物料名称), order(序号), solid_mass(固体质量g), - solvent_volume(溶剂体积mL)。示例: [{"name": "MDA", "order": 0, "solid_mass": - 5.0, "solvent_volume": 20}, {"name": "MPDA", "order": 1, "solid_mass": - 4.5, "solvent_volume": 18}]' - type: string - speed: - default: '400' - description: 搅拌速度(rpm),用于混合溶液,默认400转/分钟 - type: string - temperature: - default: '20' - description: 配置温度(℃),溶液配置过程的目标温度,默认20℃(室温) - type: string - required: - - solutions - title: BatchCreateDiamineSolutionTasks_Goal - type: object - result: - properties: - return_info: - description: 批量任务创建结果汇总,JSON格式包含总数、成功数、失败数及每个任务的详细信息 - type: string - required: - - return_info - title: BatchCreateDiamineSolutionTasks_Result - type: object - required: - - goal - title: BatchCreateDiamineSolutionTasks - type: object - type: UniLabJsonCommand - create_90_10_vial_feeding_task: - feedback: {} - goal: - delay_time: delay_time - hold_m_name: hold_m_name - order_name: order_name - percent_10_1_assign_material_name: percent_10_1_assign_material_name - percent_10_1_liquid_material_name: percent_10_1_liquid_material_name - percent_10_1_target_weigh: percent_10_1_target_weigh - percent_10_1_volume: percent_10_1_volume - percent_10_2_assign_material_name: percent_10_2_assign_material_name - percent_10_2_liquid_material_name: percent_10_2_liquid_material_name - percent_10_2_target_weigh: percent_10_2_target_weigh - percent_10_2_volume: percent_10_2_volume - percent_10_3_assign_material_name: percent_10_3_assign_material_name - percent_10_3_liquid_material_name: percent_10_3_liquid_material_name - percent_10_3_target_weigh: percent_10_3_target_weigh - percent_10_3_volume: percent_10_3_volume - percent_90_1_assign_material_name: percent_90_1_assign_material_name - percent_90_1_target_weigh: percent_90_1_target_weigh - percent_90_2_assign_material_name: percent_90_2_assign_material_name - percent_90_2_target_weigh: percent_90_2_target_weigh - percent_90_3_assign_material_name: percent_90_3_assign_material_name - percent_90_3_target_weigh: percent_90_3_target_weigh - speed: speed - temperature: temperature - goal_default: - delay_time: '' - hold_m_name: '' - order_name: '' - percent_10_1_assign_material_name: '' - percent_10_1_liquid_material_name: '' - percent_10_1_target_weigh: '' - percent_10_1_volume: '' - percent_10_2_assign_material_name: '' - percent_10_2_liquid_material_name: '' - percent_10_2_target_weigh: '' - percent_10_2_volume: '' - percent_10_3_assign_material_name: '' - percent_10_3_liquid_material_name: '' - percent_10_3_target_weigh: '' - percent_10_3_volume: '' - percent_90_1_assign_material_name: '' - percent_90_1_target_weigh: '' - percent_90_2_assign_material_name: '' - percent_90_2_target_weigh: '' - percent_90_3_assign_material_name: '' - percent_90_3_target_weigh: '' - speed: '' - temperature: '' - handles: {} - result: - return_info: return_info - schema: - description: '' - properties: - feedback: - properties: {} - required: [] - title: DispenStationVialFeed_Feedback - type: object - goal: - properties: - delay_time: - type: string - hold_m_name: - type: string - order_name: - type: string - percent_10_1_assign_material_name: - type: string - percent_10_1_liquid_material_name: - type: string - percent_10_1_target_weigh: - type: string - percent_10_1_volume: - type: string - percent_10_2_assign_material_name: - type: string - percent_10_2_liquid_material_name: - type: string - percent_10_2_target_weigh: - type: string - percent_10_2_volume: - type: string - percent_10_3_assign_material_name: - type: string - percent_10_3_liquid_material_name: - type: string - percent_10_3_target_weigh: - type: string - percent_10_3_volume: - type: string - percent_90_1_assign_material_name: - type: string - percent_90_1_target_weigh: - type: string - percent_90_2_assign_material_name: - type: string - percent_90_2_target_weigh: - type: string - percent_90_3_assign_material_name: - type: string - percent_90_3_target_weigh: - type: string - speed: - type: string - temperature: - type: string - required: - - order_name - - percent_90_1_assign_material_name - - percent_90_1_target_weigh - - percent_90_2_assign_material_name - - percent_90_2_target_weigh - - percent_90_3_assign_material_name - - percent_90_3_target_weigh - - percent_10_1_assign_material_name - - percent_10_1_target_weigh - - percent_10_1_volume - - percent_10_1_liquid_material_name - - percent_10_2_assign_material_name - - percent_10_2_target_weigh - - percent_10_2_volume - - percent_10_2_liquid_material_name - - percent_10_3_assign_material_name - - percent_10_3_target_weigh - - percent_10_3_volume - - percent_10_3_liquid_material_name - - speed - - temperature - - delay_time - - hold_m_name - title: DispenStationVialFeed_Goal - type: object - result: - properties: - return_info: - type: string - required: - - return_info - title: DispenStationVialFeed_Result - type: object - required: - - goal - title: DispenStationVialFeed - type: object - type: DispenStationVialFeed - create_diamine_solution_task: - feedback: {} - goal: - delay_time: delay_time - hold_m_name: hold_m_name - liquid_material_name: liquid_material_name - material_name: material_name - order_name: order_name - speed: speed - target_weigh: target_weigh - temperature: temperature - volume: volume - goal_default: - delay_time: '' - hold_m_name: '' - liquid_material_name: '' - material_name: '' - order_name: '' - speed: '' - target_weigh: '' - temperature: '' - volume: '' - handles: {} - result: - return_info: return_info - schema: - description: '' - properties: - feedback: - properties: {} - required: [] - title: DispenStationSolnPrep_Feedback - type: object - goal: - properties: - delay_time: - type: string - hold_m_name: - type: string - liquid_material_name: - type: string - material_name: - type: string - order_name: - type: string - speed: - type: string - target_weigh: - type: string - temperature: - type: string - volume: - type: string - required: - - order_name - - material_name - - target_weigh - - volume - - liquid_material_name - - speed - - temperature - - delay_time - - hold_m_name - title: DispenStationSolnPrep_Goal - type: object - result: - properties: - return_info: - type: string - required: - - return_info - title: DispenStationSolnPrep_Result - type: object - required: - - goal - title: DispenStationSolnPrep - type: object - type: DispenStationSolnPrep - module: unilabos.devices.workstation.bioyond_studio.dispensing_station:BioyondDispensingStation - status_types: {} - type: python - config_info: [] - description: '' - handles: [] - icon: preparation_station.webp - init_param_schema: - config: - properties: - config: - type: string - required: - - config - type: object - data: - properties: {} - required: [] - type: object - version: 1.0.0 diff --git a/unilabos/registry/devices/reaction_station_bioyond.yaml b/unilabos/registry/devices/reaction_station_bioyond.yaml index c88a47c..f1a16ec 100644 --- a/unilabos/registry/devices/reaction_station_bioyond.yaml +++ b/unilabos/registry/devices/reaction_station_bioyond.yaml @@ -4,48 +4,6 @@ reaction_station.bioyond: - reaction_station_bioyond class: action_value_mappings: - drip_back: - feedback: {} - goal: - assign_material_name: assign_material_name - temperature: temperature - time: time - titration_type: titration_type - torque_variation: torque_variation - volume: volume - goal_default: - assign_material_name: '' - temperature: '' - time: '' - titration_type: '' - torque_variation: '' - volume: '' - handles: {} - result: {} - schema: - description: 滴回去 - properties: - feedback: {} - goal: - properties: - assign_material_name: - description: 物料名称(不能为空) - type: string - temperature: - description: 温度设定(°C) - type: string - time: - description: 观察时间(分钟) - type: string - required: - - file_path - type: object - result: {} - required: - - goal - title: load_bioyond_data_from_file参数 - type: object - type: UniLabJsonCommand auto-post_init: feedback: {} goal: {} @@ -104,6 +62,48 @@ reaction_station.bioyond: title: drip_back参数 type: object type: UniLabJsonCommand + drip_back: + feedback: {} + goal: + assign_material_name: assign_material_name + temperature: temperature + time: time + titration_type: titration_type + torque_variation: torque_variation + volume: volume + goal_default: + assign_material_name: '' + temperature: '' + time: '' + titration_type: '' + torque_variation: '' + volume: '' + handles: {} + result: {} + schema: + description: 滴回去 + properties: + feedback: {} + goal: + properties: + assign_material_name: + description: 物料名称(不能为空) + type: string + temperature: + description: 温度设定(°C) + type: string + time: + description: 观察时间(分钟) + type: string + required: + - file_path + type: object + result: {} + required: + - goal + title: load_bioyond_data_from_file参数 + type: object + type: UniLabJsonCommand liquid_feeding_beaker: feedback: {} goal: From 39bc317bfcf0c89a6fe1717bb478febb3c236650 Mon Sep 17 00:00:00 2001 From: calvincao Date: Wed, 29 Oct 2025 18:39:22 +0800 Subject: [PATCH 029/104] =?UTF-8?q?feat(workstation):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=A4=9A=E7=A7=8D=E8=BE=93=E5=85=A5=E7=B1=BB=E5=9E=8B=E7=9A=84?= =?UTF-8?q?=20station=5Fresource=20=E5=B9=B6=E4=BC=98=E5=8C=96=E7=89=A9?= =?UTF-8?q?=E6=96=99=E7=B3=BB=E7=BB=9F=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 `_coerce_station_resource_input` 函数以支持 dict、list 和其他类型转换为 Deck - 添加对 Modbus 客户端方法的兼容性封装,确保 slave/unit 参数正确传递- 在初始化时根据 station_resource 动态创建或赋值 deck - 自动构建默认物料台面及三个料盘,并分配初始电极片资源 - 移除旧有的硬编码物料系统注释代码 - 更新资源导出逻辑以使用工作站实例中的 deck 属性 --- .../coin_cell_assembly/coin_cell_assembly.py | 147 +++++++++++++++++- 1 file changed, 139 insertions(+), 8 deletions(-) diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index 51d668b..966de33 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -1,11 +1,15 @@ + import csv +import inspect import json import os import threading import time +import types from datetime import datetime from typing import Any, Dict, Optional -from pylabrobot.resources import Resource as PLRResource +from functools import wraps +from pylabrobot.resources import Deck, Resource as PLRResource from unilabos_msgs.msg import Resource from unilabos.device_comms.modbus_plc.client import ModbusTcpClient from unilabos.devices.workstation.workstation_base import WorkstationBase @@ -15,12 +19,99 @@ from unilabos.devices.workstation.coin_cell_assembly.button_battery_station impo from unilabos.ros.nodes.base_device_node import ROS2DeviceNode, BaseROS2DeviceNode from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import CoincellDeck +from unilabos.resources.graphio import convert_resources_to_type +from unilabos.utils.log import logger + + +def _ensure_modbus_slave_kw_alias(modbus_client): + if modbus_client is None: + return + + method_names = [ + "read_coils", + "write_coils", + "write_coil", + "read_discrete_inputs", + "read_holding_registers", + "write_register", + "write_registers", + ] + + def _wrap(func): + signature = inspect.signature(func) + has_var_kwargs = any(param.kind == param.VAR_KEYWORD for param in signature.parameters.values()) + accepts_unit = has_var_kwargs or "unit" in signature.parameters + accepts_slave = has_var_kwargs or "slave" in signature.parameters + + @wraps(func) + def _wrapped(self, *args, **kwargs): + if "slave" in kwargs and not accepts_slave: + slave_value = kwargs.pop("slave") + if accepts_unit and "unit" not in kwargs: + kwargs["unit"] = slave_value + if "unit" in kwargs and not accepts_unit: + unit_value = kwargs.pop("unit") + if accepts_slave and "slave" not in kwargs: + kwargs["slave"] = unit_value + return func(self, *args, **kwargs) + + _wrapped._has_slave_alias = True + return _wrapped + + for name in method_names: + if not hasattr(modbus_client, name): + continue + bound_method = getattr(modbus_client, name) + func = getattr(bound_method, "__func__", None) + if func is None: + continue + if getattr(func, "_has_slave_alias", False): + continue + wrapped = _wrap(func) + setattr(modbus_client, name, types.MethodType(wrapped, modbus_client)) + + +def _coerce_station_resource_input(station_resource: Any) -> Optional[Deck]: + if station_resource is None: + return None + + if isinstance(station_resource, Deck): + return station_resource + + if isinstance(station_resource, PLRResource): + return station_resource if isinstance(station_resource, Deck) else None + + candidates = None + if isinstance(station_resource, dict): + if "nodes" in station_resource and isinstance(station_resource["nodes"], list): + candidates = station_resource["nodes"] + else: + candidates = [station_resource] + elif isinstance(station_resource, list): + candidates = station_resource + + if candidates is None: + return None + + try: + converted = convert_resources_to_type(resources_list=candidates, resource_type=Deck) + if isinstance(converted, Deck): + return converted + if isinstance(converted, list): + for item in converted: + if isinstance(item, Deck): + return item + except Exception as exc: + logger.warning(f"station_resource 转换 Deck 失败: {exc}") + return None + #构建物料系统 class CoinCellAssemblyWorkstation(WorkstationBase): def __init__( self, + station_resource: Optional[Any] = None, deck: Deck=None, address: str = "172.21.32.20", port: str = "502", @@ -28,6 +119,16 @@ class CoinCellAssemblyWorkstation(WorkstationBase): *args, **kwargs, ): + if station_resource is None and "station_resource" in kwargs: + station_resource = kwargs.pop("station_resource") + else: + kwargs.pop("station_resource", None) + + normalized_station_resource = _coerce_station_resource_input(station_resource) + + if deck is None and isinstance(normalized_station_resource, Deck): + deck = normalized_station_resource + super().__init__( #桌子 deck=deck, @@ -35,10 +136,34 @@ class CoinCellAssemblyWorkstation(WorkstationBase): **kwargs, ) self.debug_mode = debug_mode - self.deck = deck + + self.station_resource = normalized_station_resource if isinstance(normalized_station_resource, Deck) else None + + if self.deck is None: + self.deck = CoincellDeck(size_x=1000, size_y=1000, size_z=900) + self.station_resource = self.deck + # 创建料盘1并添加极片 + liaopan1 = MaterialPlate(name="liaopan1", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) + self.deck.assign_child_resource(liaopan1, Coordinate(x=0, y=0, z=0)) + for i in range(16): + jipian = ElectrodeSheet(name=f"jipian1_{i}", size_x=12, size_y=12, size_z=0.1) + liaopan1.children[i].assign_child_resource(jipian, location=None) + # 创建料盘2 + liaopan2 = MaterialPlate(name="liaopan2", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) + self.deck.assign_child_resource(liaopan2, Coordinate(x=500, y=0, z=0)) + # 创建电池料盘 + liaopan3 = MaterialPlate(name="电池料盘", size_x=120.8, size_y=160.5, size_z=10.0, fill=True) + self.deck.assign_child_resource(liaopan3, Coordinate(x=100, y=100, z=0)) + else: + if self.station_resource is None: + self.station_resource = self.deck + elif self.deck is not self.station_resource: + self.deck = self.station_resource + """ 连接初始化 """ modbus_client = TCPClient(addr=address, port=port) print("modbus_client", modbus_client) + _ensure_modbus_slave_kw_alias(modbus_client.client) if not debug_mode: modbus_client.client.connect() count = 100 @@ -62,8 +187,6 @@ class CoinCellAssemblyWorkstation(WorkstationBase): self.csv_export_file = None self.coin_num_N = 0 #已组装电池数量 #创建一个物料台面,包含两个极片板 - #self.deck = create_a_coin_cell_deck() - #self._ros_node.update_resource(self.deck) #ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ @@ -907,7 +1030,10 @@ class CoinCellAssemblyWorkstation(WorkstationBase): ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ "resources": [self.station_resource] }) - time.sleep(4) + # for i in range(40): + # print(f"fun_wuliao_test 运行结束{i}") + # time.sleep(1) + # time.sleep(40) # 数据读取与输出 def func_read_data_and_output(self, file_path: str="D:\\coin_cell_data"): # 检查CSV导出是否正在运行,已运行则跳出,防止同时启动两个while循环 @@ -1145,8 +1271,11 @@ if __name__ == "__main__": #liaopan2.children[1].assign_child_resource(jipian1, location=None) ##print(jipian2.parent) - liaopan1 = deck.get_resource("liaopan1") - liaopan2 = deck.get_resource("liaopan2") + # 使用 Coin_Cell 对象的 deck 属性 + deck = Coin_Cell.deck + + liaopan1 = Coin_Cell.deck.get_resource("liaopan1") + liaopan2 = Coin_Cell.deck.get_resource("liaopan2") for i in range(16): #找到liaopan1上每一个jipian jipian_linshi = liaopan1.children[i].children[0] @@ -1172,7 +1301,7 @@ if __name__ == "__main__": BasicConfig.sk = "15d4f25e-3512-4f9c-9bfb-43ab85e7b561" from unilabos.app.web.client import http_client - resources = convert_resources_from_type([deck], [Resource]) + resources = convert_resources_from_type([Coin_Cell.deck], [Resource]) json.dump({"nodes": resources, "links": []}, open("button_battery_station_resources_unilab.json", "w"), indent=2) #print(resources) @@ -1180,4 +1309,6 @@ if __name__ == "__main__": http_client.resource_add(resources) + + \ No newline at end of file From 9fe8f4f28fe1c663ed9b0428dd694d4ba0d460dd Mon Sep 17 00:00:00 2001 From: calvincao Date: Wed, 29 Oct 2025 21:36:52 +0800 Subject: [PATCH 030/104] =?UTF-8?q?fix(workstation):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=20coin=5Fcell=5Fassembly=5Fa.csv=20=E6=96=87=E4=BB=B6=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E5=AF=84=E5=AD=98=E5=99=A8=E5=92=8C=E7=BA=BF=E5=9C=88?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重新排列并清理了 coin_cell_assembly_a.csv 中的寄存器和线圈定义 - 确保所有定义的格式一致,提升可读性和维护性 --- .../coin_cell_assembly_a.csv | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv index 836fb71..1805c23 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv @@ -43,21 +43,10 @@ REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,10000, UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,8730, UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,8530, REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,10018,ASSEMBLY_TYPE7or8 -COIL_ALUMINUM_FOIL,BOOL,,ʹ,,coil,8340, -REG_MSG_NE_PLATE_MATRIX,INT16,,Ƭλ,,hold_register,440, -REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,Ĥλ,,hold_register,450, -REG_MSG_TIP_BOX_MATRIX,INT16,,Һǹͷλ,,hold_register,480, -REG_MSG_NE_PLATE_NUM,INT16,,Ƭ,,hold_register,443, -REG_MSG_SEPARATOR_PLATE_NUM,INT16,,Ĥ,,hold_register,453, -REG_MSG_PRESS_MODE,BOOL,,ѹģʽfalse:ѹģʽTrue:ģʽ,,coil,8360,ѹģʽ -,,,,,,, -,BOOL,,Ӿλfalse:ʹãtrue:ԣ,,coil,8300,Ӿλ -,BOOL,,죨false:ʹãtrue:ԣ,,coil,8310,Ӿ -,BOOL,,_֣false:ʹãtrue:ԣ,,coil,8320, -,BOOL,,_Ҳ֣false:ʹãtrue:ԣ,,coil,8420,Ҳ -,BOOL,,ռ֪false:ʹãtrue:ԣ,,coil,8350,ռ֪ -,BOOL,,Һģʽfalse:εҺtrue:εҺ,,coil,8370,Һģʽ -,BOOL,,Ƭأfalse:ʹãtrue:ԣ,,coil,8380,Ƭ -,BOOL,,Ƭװʽfalse:װtrue:װ,,coil,8390,װ -,BOOL,,ѹࣨfalse:ʹãtrue:ԣ,,coil,8400,ѹ -,BOOL,,̷̰ʽfalse:ˮƽ̣true:ѵ̣,,coil,8410,Ƭ̷ʽ +COIL_ALUMINUM_FOIL,BOOL,,ʹ��������,,coil,8340, +REG_MSG_NE_PLATE_MATRIX,INT16,,����Ƭ�����λ,,hold_register,440, +REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,��Ĥ�����λ,,hold_register,450, +REG_MSG_TIP_BOX_MATRIX,INT16,,��Һǹͷ�����λ,,hold_register,480, +REG_MSG_NE_PLATE_NUM,INT16,,����Ƭ����,,hold_register,443, +REG_MSG_SEPARATOR_PLATE_NUM,INT16,,��Ĥ����,,hold_register,453, +REG_MSG_PRESS_MODE,BOOL,,ѹ��ģʽ��false:ѹ�����ģʽ��True:����ģʽ��,,coil,8360,���ѹ��ģʽ From b3e28196c665de6f0819543b4e48eb319091852d Mon Sep 17 00:00:00 2001 From: calvincao Date: Thu, 30 Oct 2025 11:22:17 +0800 Subject: [PATCH 031/104] =?UTF-8?q?feat(battery):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=94=B5=E6=B1=A0=E5=B7=A5=E4=BD=8D=E8=B5=84=E6=BA=90=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 coin_cell_deck 类型从 container 更改为 coin_cell_deck - 将 material_plate 类型从 container 更改为 material_plate - 将 material_hole 类型从 container 更改为 material_hole- 移除电极片容器结构,直接使用 electrode_sheet 类型 - 更新物料孔配置参数,包括直径、深度和最大片数 -重新组织料盘结构,明确父子节点关系 - 添加新的电极片定义并关联到对应的物料孔 - 调整所有物料孔坐标位置以匹配新布局 - 为 liaopan2 添加完整的子节点结构和排序规则 --- button_battery_station_resources_unilab.json | 1384 ++++++++--------- .../workstation/bioyond_studio/config.py | 2 +- ..._battery_station.py => YB_YH_materials.py} | 0 .../coin_cell_assembly/coin_cell_assembly.py | 68 +- .../coin_cell_assembly_a.csv | 25 +- .../coin_cell_assembly/new_cellconfig3c.json | 683 +------- 6 files changed, 759 insertions(+), 1403 deletions(-) rename unilabos/devices/workstation/coin_cell_assembly/{button_battery_station.py => YB_YH_materials.py} (100%) diff --git a/button_battery_station_resources_unilab.json b/button_battery_station_resources_unilab.json index 2d55750..f60d1c7 100644 --- a/button_battery_station_resources_unilab.json +++ b/button_battery_station_resources_unilab.json @@ -10,7 +10,7 @@ "\u7535\u6c60\u6599\u76d8" ], "parent": null, - "type": "container", + "type": "coin_cell_deck", "class": "", "position": { "x": 0, @@ -56,7 +56,7 @@ "liaopan1_materialhole_3_3" ], "parent": "coin_cell_deck", - "type": "container", + "type": "material_plate", "class": "", "position": { "x": 0, @@ -102,11 +102,9 @@ "id": "liaopan1_materialhole_0_0", "name": "liaopan1_materialhole_0_0", "sample_id": null, - "children": [ - "jipian1_0" - ], + "children": [], "parent": "liaopan1", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 12.4, @@ -135,51 +133,13 @@ "info": null } }, - { - "id": "jipian1_0", - "name": "jipian1_0", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, { "id": "liaopan1_materialhole_0_1", "name": "liaopan1_materialhole_0_1", "sample_id": null, - "children": [ - "jipian1_1" - ], + "children": [], "parent": "liaopan1", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 12.4, @@ -208,51 +168,13 @@ "info": null } }, - { - "id": "jipian1_1", - "name": "jipian1_1", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, { "id": "liaopan1_materialhole_0_2", "name": "liaopan1_materialhole_0_2", "sample_id": null, - "children": [ - "jipian1_2" - ], + "children": [], "parent": "liaopan1", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 12.4, @@ -281,51 +203,13 @@ "info": null } }, - { - "id": "jipian1_2", - "name": "jipian1_2", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, { "id": "liaopan1_materialhole_0_3", "name": "liaopan1_materialhole_0_3", "sample_id": null, - "children": [ - "jipian1_3" - ], + "children": [], "parent": "liaopan1", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 12.4, @@ -354,51 +238,13 @@ "info": null } }, - { - "id": "jipian1_3", - "name": "jipian1_3", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_0_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, { "id": "liaopan1_materialhole_1_0", "name": "liaopan1_materialhole_1_0", "sample_id": null, - "children": [ - "jipian1_4" - ], + "children": [], "parent": "liaopan1", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 36.4, @@ -427,51 +273,13 @@ "info": null } }, - { - "id": "jipian1_4", - "name": "jipian1_4", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, { "id": "liaopan1_materialhole_1_1", "name": "liaopan1_materialhole_1_1", "sample_id": null, - "children": [ - "jipian1_5" - ], + "children": [], "parent": "liaopan1", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 36.4, @@ -500,51 +308,13 @@ "info": null } }, - { - "id": "jipian1_5", - "name": "jipian1_5", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, { "id": "liaopan1_materialhole_1_2", "name": "liaopan1_materialhole_1_2", "sample_id": null, - "children": [ - "jipian1_6" - ], + "children": [], "parent": "liaopan1", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 36.4, @@ -573,51 +343,13 @@ "info": null } }, - { - "id": "jipian1_6", - "name": "jipian1_6", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, { "id": "liaopan1_materialhole_1_3", "name": "liaopan1_materialhole_1_3", "sample_id": null, - "children": [ - "jipian1_7" - ], + "children": [], "parent": "liaopan1", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 36.4, @@ -646,51 +378,13 @@ "info": null } }, - { - "id": "jipian1_7", - "name": "jipian1_7", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_1_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, { "id": "liaopan1_materialhole_2_0", "name": "liaopan1_materialhole_2_0", "sample_id": null, - "children": [ - "jipian1_8" - ], + "children": [], "parent": "liaopan1", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 60.4, @@ -719,51 +413,13 @@ "info": null } }, - { - "id": "jipian1_8", - "name": "jipian1_8", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, { "id": "liaopan1_materialhole_2_1", "name": "liaopan1_materialhole_2_1", "sample_id": null, - "children": [ - "jipian1_9" - ], + "children": [], "parent": "liaopan1", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 60.4, @@ -792,51 +448,13 @@ "info": null } }, - { - "id": "jipian1_9", - "name": "jipian1_9", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, { "id": "liaopan1_materialhole_2_2", "name": "liaopan1_materialhole_2_2", "sample_id": null, - "children": [ - "jipian1_10" - ], + "children": [], "parent": "liaopan1", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 60.4, @@ -865,51 +483,13 @@ "info": null } }, - { - "id": "jipian1_10", - "name": "jipian1_10", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, { "id": "liaopan1_materialhole_2_3", "name": "liaopan1_materialhole_2_3", "sample_id": null, - "children": [ - "jipian1_11" - ], + "children": [], "parent": "liaopan1", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 60.4, @@ -938,51 +518,13 @@ "info": null } }, - { - "id": "jipian1_11", - "name": "jipian1_11", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_2_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, { "id": "liaopan1_materialhole_3_0", "name": "liaopan1_materialhole_3_0", "sample_id": null, - "children": [ - "jipian1_12" - ], + "children": [], "parent": "liaopan1", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 84.4, @@ -1011,51 +553,13 @@ "info": null } }, - { - "id": "jipian1_12", - "name": "jipian1_12", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_0", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, { "id": "liaopan1_materialhole_3_1", "name": "liaopan1_materialhole_3_1", "sample_id": null, - "children": [ - "jipian1_13" - ], + "children": [], "parent": "liaopan1", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 84.4, @@ -1084,51 +588,13 @@ "info": null } }, - { - "id": "jipian1_13", - "name": "jipian1_13", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_1", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, { "id": "liaopan1_materialhole_3_2", "name": "liaopan1_materialhole_3_2", "sample_id": null, - "children": [ - "jipian1_14" - ], + "children": [], "parent": "liaopan1", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 84.4, @@ -1157,51 +623,13 @@ "info": null } }, - { - "id": "jipian1_14", - "name": "jipian1_14", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_2", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, { "id": "liaopan1_materialhole_3_3", "name": "liaopan1_materialhole_3_3", "sample_id": null, - "children": [ - "jipian1_15" - ], + "children": [], "parent": "liaopan1", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 84.4, @@ -1230,42 +658,6 @@ "info": null } }, - { - "id": "jipian1_15", - "name": "jipian1_15", - "sample_id": null, - "children": [], - "parent": "liaopan1_materialhole_3_3", - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "ElectrodeSheet", - "size_x": 12, - "size_y": 12, - "size_z": 0.1, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "electrode_sheet", - "model": null, - "barcode": null - }, - "data": { - "diameter": 14, - "thickness": 0.1, - "mass": 0.5, - "material_type": "copper", - "info": null - } - }, { "id": "liaopan2", "name": "liaopan2", @@ -1289,7 +681,7 @@ "liaopan2_materialhole_3_3" ], "parent": "coin_cell_deck", - "type": "container", + "type": "material_plate", "class": "", "position": { "x": 500, @@ -1335,9 +727,11 @@ "id": "liaopan2_materialhole_0_0", "name": "liaopan2_materialhole_0_0", "sample_id": null, - "children": [], + "children": [ + "jipian1_0" + ], "parent": "liaopan2", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 12.4, @@ -1366,13 +760,51 @@ "info": null } }, + { + "id": "jipian1_0", + "name": "jipian1_0", + "sample_id": null, + "children": [], + "parent": "liaopan2_materialhole_0_0", + "type": "electrode_sheet", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, { "id": "liaopan2_materialhole_0_1", "name": "liaopan2_materialhole_0_1", "sample_id": null, - "children": [], + "children": [ + "jipian1_1" + ], "parent": "liaopan2", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 12.4, @@ -1401,13 +833,51 @@ "info": null } }, + { + "id": "jipian1_1", + "name": "jipian1_1", + "sample_id": null, + "children": [], + "parent": "liaopan2_materialhole_0_1", + "type": "electrode_sheet", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, { "id": "liaopan2_materialhole_0_2", "name": "liaopan2_materialhole_0_2", "sample_id": null, - "children": [], + "children": [ + "jipian1_2" + ], "parent": "liaopan2", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 12.4, @@ -1436,13 +906,51 @@ "info": null } }, + { + "id": "jipian1_2", + "name": "jipian1_2", + "sample_id": null, + "children": [], + "parent": "liaopan2_materialhole_0_2", + "type": "electrode_sheet", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, { "id": "liaopan2_materialhole_0_3", "name": "liaopan2_materialhole_0_3", "sample_id": null, - "children": [], + "children": [ + "jipian1_3" + ], "parent": "liaopan2", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 12.4, @@ -1471,13 +979,51 @@ "info": null } }, + { + "id": "jipian1_3", + "name": "jipian1_3", + "sample_id": null, + "children": [], + "parent": "liaopan2_materialhole_0_3", + "type": "electrode_sheet", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, { "id": "liaopan2_materialhole_1_0", "name": "liaopan2_materialhole_1_0", "sample_id": null, - "children": [], + "children": [ + "jipian1_4" + ], "parent": "liaopan2", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 36.4, @@ -1506,13 +1052,51 @@ "info": null } }, + { + "id": "jipian1_4", + "name": "jipian1_4", + "sample_id": null, + "children": [], + "parent": "liaopan2_materialhole_1_0", + "type": "electrode_sheet", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, { "id": "liaopan2_materialhole_1_1", "name": "liaopan2_materialhole_1_1", "sample_id": null, - "children": [], + "children": [ + "jipian1_5" + ], "parent": "liaopan2", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 36.4, @@ -1541,13 +1125,51 @@ "info": null } }, + { + "id": "jipian1_5", + "name": "jipian1_5", + "sample_id": null, + "children": [], + "parent": "liaopan2_materialhole_1_1", + "type": "electrode_sheet", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, { "id": "liaopan2_materialhole_1_2", "name": "liaopan2_materialhole_1_2", "sample_id": null, - "children": [], + "children": [ + "jipian1_6" + ], "parent": "liaopan2", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 36.4, @@ -1576,13 +1198,51 @@ "info": null } }, + { + "id": "jipian1_6", + "name": "jipian1_6", + "sample_id": null, + "children": [], + "parent": "liaopan2_materialhole_1_2", + "type": "electrode_sheet", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, { "id": "liaopan2_materialhole_1_3", "name": "liaopan2_materialhole_1_3", "sample_id": null, - "children": [], + "children": [ + "jipian1_7" + ], "parent": "liaopan2", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 36.4, @@ -1611,13 +1271,51 @@ "info": null } }, + { + "id": "jipian1_7", + "name": "jipian1_7", + "sample_id": null, + "children": [], + "parent": "liaopan2_materialhole_1_3", + "type": "electrode_sheet", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, { "id": "liaopan2_materialhole_2_0", "name": "liaopan2_materialhole_2_0", "sample_id": null, - "children": [], + "children": [ + "jipian1_8" + ], "parent": "liaopan2", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 60.4, @@ -1646,13 +1344,51 @@ "info": null } }, + { + "id": "jipian1_8", + "name": "jipian1_8", + "sample_id": null, + "children": [], + "parent": "liaopan2_materialhole_2_0", + "type": "electrode_sheet", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, { "id": "liaopan2_materialhole_2_1", "name": "liaopan2_materialhole_2_1", "sample_id": null, - "children": [], + "children": [ + "jipian1_9" + ], "parent": "liaopan2", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 60.4, @@ -1681,13 +1417,51 @@ "info": null } }, + { + "id": "jipian1_9", + "name": "jipian1_9", + "sample_id": null, + "children": [], + "parent": "liaopan2_materialhole_2_1", + "type": "electrode_sheet", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, { "id": "liaopan2_materialhole_2_2", "name": "liaopan2_materialhole_2_2", "sample_id": null, - "children": [], + "children": [ + "jipian1_10" + ], "parent": "liaopan2", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 60.4, @@ -1716,13 +1490,51 @@ "info": null } }, + { + "id": "jipian1_10", + "name": "jipian1_10", + "sample_id": null, + "children": [], + "parent": "liaopan2_materialhole_2_2", + "type": "electrode_sheet", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, { "id": "liaopan2_materialhole_2_3", "name": "liaopan2_materialhole_2_3", "sample_id": null, - "children": [], + "children": [ + "jipian1_11" + ], "parent": "liaopan2", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 60.4, @@ -1751,13 +1563,51 @@ "info": null } }, + { + "id": "jipian1_11", + "name": "jipian1_11", + "sample_id": null, + "children": [], + "parent": "liaopan2_materialhole_2_3", + "type": "electrode_sheet", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, { "id": "liaopan2_materialhole_3_0", "name": "liaopan2_materialhole_3_0", "sample_id": null, - "children": [], + "children": [ + "jipian1_12" + ], "parent": "liaopan2", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 84.4, @@ -1786,13 +1636,51 @@ "info": null } }, + { + "id": "jipian1_12", + "name": "jipian1_12", + "sample_id": null, + "children": [], + "parent": "liaopan2_materialhole_3_0", + "type": "electrode_sheet", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, { "id": "liaopan2_materialhole_3_1", "name": "liaopan2_materialhole_3_1", "sample_id": null, - "children": [], + "children": [ + "jipian1_13" + ], "parent": "liaopan2", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 84.4, @@ -1821,13 +1709,51 @@ "info": null } }, + { + "id": "jipian1_13", + "name": "jipian1_13", + "sample_id": null, + "children": [], + "parent": "liaopan2_materialhole_3_1", + "type": "electrode_sheet", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, { "id": "liaopan2_materialhole_3_2", "name": "liaopan2_materialhole_3_2", "sample_id": null, - "children": [], + "children": [ + "jipian1_14" + ], "parent": "liaopan2", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 84.4, @@ -1856,13 +1782,51 @@ "info": null } }, + { + "id": "jipian1_14", + "name": "jipian1_14", + "sample_id": null, + "children": [], + "parent": "liaopan2_materialhole_3_2", + "type": "electrode_sheet", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, { "id": "liaopan2_materialhole_3_3", "name": "liaopan2_materialhole_3_3", "sample_id": null, - "children": [], + "children": [ + "jipian1_15" + ], "parent": "liaopan2", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 84.4, @@ -1891,6 +1855,42 @@ "info": null } }, + { + "id": "jipian1_15", + "name": "jipian1_15", + "sample_id": null, + "children": [], + "parent": "liaopan2_materialhole_3_3", + "type": "electrode_sheet", + "class": "", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "ElectrodeSheet", + "size_x": 12, + "size_y": 12, + "size_z": 0.1, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "electrode_sheet", + "model": null, + "barcode": null + }, + "data": { + "diameter": 14, + "thickness": 0.1, + "mass": 0.5, + "material_type": "copper", + "info": null + } + }, { "id": "\u7535\u6c60\u6599\u76d8", "name": "\u7535\u6c60\u6599\u76d8", @@ -1914,7 +1914,7 @@ "\u7535\u6c60\u6599\u76d8_materialhole_3_3" ], "parent": "coin_cell_deck", - "type": "container", + "type": "material_plate", "class": "", "position": { "x": 100, @@ -1962,7 +1962,7 @@ "sample_id": null, "children": [], "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 12.4, @@ -1997,7 +1997,7 @@ "sample_id": null, "children": [], "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 12.4, @@ -2032,7 +2032,7 @@ "sample_id": null, "children": [], "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 12.4, @@ -2067,7 +2067,7 @@ "sample_id": null, "children": [], "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 12.4, @@ -2102,7 +2102,7 @@ "sample_id": null, "children": [], "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 36.4, @@ -2137,7 +2137,7 @@ "sample_id": null, "children": [], "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 36.4, @@ -2172,7 +2172,7 @@ "sample_id": null, "children": [], "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 36.4, @@ -2207,7 +2207,7 @@ "sample_id": null, "children": [], "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 36.4, @@ -2242,7 +2242,7 @@ "sample_id": null, "children": [], "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 60.4, @@ -2277,7 +2277,7 @@ "sample_id": null, "children": [], "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 60.4, @@ -2312,7 +2312,7 @@ "sample_id": null, "children": [], "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 60.4, @@ -2347,7 +2347,7 @@ "sample_id": null, "children": [], "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 60.4, @@ -2382,7 +2382,7 @@ "sample_id": null, "children": [], "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 84.4, @@ -2417,7 +2417,7 @@ "sample_id": null, "children": [], "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 84.4, @@ -2452,7 +2452,7 @@ "sample_id": null, "children": [], "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 84.4, @@ -2487,7 +2487,7 @@ "sample_id": null, "children": [], "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", + "type": "material_hole", "class": "", "position": { "x": 84.4, diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 1141dfa..504cf45 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -16,7 +16,7 @@ API_CONFIG = { "report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"), # HTTP 服务配置 - "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.21.33.161"), # HTTP服务监听地址,监听计算机飞连ip地址 + "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.21.32.210"), # HTTP服务监听地址,监听计算机飞连ip地址 "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), "debug_mode": False,# 调试模式 } diff --git a/unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py similarity index 100% rename from unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py rename to unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index 966de33..7ca5627 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -15,10 +15,10 @@ from unilabos.device_comms.modbus_plc.client import ModbusTcpClient from unilabos.devices.workstation.workstation_base import WorkstationBase from unilabos.device_comms.modbus_plc.client import TCPClient, ModbusNode, PLCWorkflow, ModbusWorkflow, WorkflowAction, BaseClient from unilabos.device_comms.modbus_plc.modbus import DeviceType, Base as ModbusNodeBase, DataType, WorderOrder -from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import * +from unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials import * from unilabos.ros.nodes.base_device_node import ROS2DeviceNode, BaseROS2DeviceNode from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode -from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import CoincellDeck +from unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials import CoincellDeck from unilabos.resources.graphio import convert_resources_to_type from unilabos.utils.log import logger @@ -71,24 +71,24 @@ def _ensure_modbus_slave_kw_alias(modbus_client): setattr(modbus_client, name, types.MethodType(wrapped, modbus_client)) -def _coerce_station_resource_input(station_resource: Any) -> Optional[Deck]: - if station_resource is None: +def _coerce_deck_input(deck: Any) -> Optional[Deck]: + if deck is None: return None - if isinstance(station_resource, Deck): - return station_resource + if isinstance(deck, Deck): + return deck - if isinstance(station_resource, PLRResource): - return station_resource if isinstance(station_resource, Deck) else None + if isinstance(deck, PLRResource): + return deck if isinstance(deck, Deck) else None candidates = None - if isinstance(station_resource, dict): - if "nodes" in station_resource and isinstance(station_resource["nodes"], list): - candidates = station_resource["nodes"] + if isinstance(deck, dict): + if "nodes" in deck and isinstance(deck["nodes"], list): + candidates = deck["nodes"] else: - candidates = [station_resource] - elif isinstance(station_resource, list): - candidates = station_resource + candidates = [deck] + elif isinstance(deck, list): + candidates = deck if candidates is None: return None @@ -102,7 +102,7 @@ def _coerce_station_resource_input(station_resource: Any) -> Optional[Deck]: if isinstance(item, Deck): return item except Exception as exc: - logger.warning(f"station_resource 转换 Deck 失败: {exc}") + logger.warning(f"deck 转换 Deck 失败: {exc}") return None @@ -111,23 +111,22 @@ def _coerce_station_resource_input(station_resource: Any) -> Optional[Deck]: class CoinCellAssemblyWorkstation(WorkstationBase): def __init__( self, - station_resource: Optional[Any] = None, deck: Deck=None, - address: str = "172.21.32.20", + address: str = "172.21.32.111", port: str = "502", debug_mode: bool = False, *args, **kwargs, ): - if station_resource is None and "station_resource" in kwargs: - station_resource = kwargs.pop("station_resource") + if deck is None and "deck" in kwargs: + deck = kwargs.pop("deck") else: - kwargs.pop("station_resource", None) + kwargs.pop("deck", None) - normalized_station_resource = _coerce_station_resource_input(station_resource) + normalized_deck = _coerce_deck_input(deck) - if deck is None and isinstance(normalized_station_resource, Deck): - deck = normalized_station_resource + if deck is None and isinstance(normalized_deck, Deck): + deck = normalized_deck super().__init__( #桌子 @@ -137,11 +136,10 @@ class CoinCellAssemblyWorkstation(WorkstationBase): ) self.debug_mode = debug_mode - self.station_resource = normalized_station_resource if isinstance(normalized_station_resource, Deck) else None + self.deck = None if self.deck is None: self.deck = CoincellDeck(size_x=1000, size_y=1000, size_z=900) - self.station_resource = self.deck # 创建料盘1并添加极片 liaopan1 = MaterialPlate(name="liaopan1", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) self.deck.assign_child_resource(liaopan1, Coordinate(x=0, y=0, z=0)) @@ -155,10 +153,10 @@ class CoinCellAssemblyWorkstation(WorkstationBase): liaopan3 = MaterialPlate(name="电池料盘", size_x=120.8, size_y=160.5, size_z=10.0, fill=True) self.deck.assign_child_resource(liaopan3, Coordinate(x=100, y=100, z=0)) else: - if self.station_resource is None: - self.station_resource = self.deck - elif self.deck is not self.station_resource: - self.deck = self.station_resource + if self.deck is None: + self.deck = self.deck + elif self.deck is not self.deck: + self.deck = self.deck """ 连接初始化 """ modbus_client = TCPClient(addr=address, port=port) @@ -831,7 +829,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): print("data_electrolyte_code", data_electrolyte_code) print("data_coin_cell_code", data_coin_cell_code) #接收完信息后,读取完毕标志位置True - liaopan3 = self.station_resource.get_resource("\u7535\u6c60\u6599\u76d8") + liaopan3 = self.deck.get_resource("\u7535\u6c60\u6599\u76d8") #把物料解绑后放到另一盘上 battery = ElectrodeSheet(name=f"battery_{self.coin_num_N}", size_x=14, size_y=14, size_z=2) battery._unilabos_state = { @@ -844,7 +842,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): liaopan3.children[self.coin_num_N].assign_child_resource(battery, location=None) #print(jipian2.parent) ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ - "resources": [self.station_resource] + "resources": [self.deck] }) @@ -1015,7 +1013,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): def fun_wuliao_test(self) -> bool: #找到data_init中构建的2个物料盘 - liaopan3 = self.station_resource.get_resource("\u7535\u6c60\u6599\u76d8") + liaopan3 = self.deck.get_resource("\u7535\u6c60\u6599\u76d8") for i in range(16): battery = ElectrodeSheet(name=f"battery_{i}", size_x=16, size_y=16, size_z=2) battery._unilabos_state = { @@ -1028,7 +1026,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): liaopan3.children[i].assign_child_resource(battery, location=None) ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ - "resources": [self.station_resource] + "resources": [self.deck] }) # for i in range(40): # print(f"fun_wuliao_test 运行结束{i}") @@ -1286,7 +1284,7 @@ if __name__ == "__main__": from unilabos.resources.graphio import resource_ulab_to_plr, convert_resources_to_type - #with open("./button_battery_station_resources_unilab.json", "r", encoding="utf-8") as f: + #with open("./button_battery_decks_unilab.json", "r", encoding="utf-8") as f: # bioyond_resources_unilab = json.load(f) #print(f"成功读取 JSON 文件,包含 {len(bioyond_resources_unilab)} 个资源") #ulab_resources = convert_resources_to_type(bioyond_resources_unilab, List[PLRResource]) @@ -1302,7 +1300,7 @@ if __name__ == "__main__": from unilabos.app.web.client import http_client resources = convert_resources_from_type([Coin_Cell.deck], [Resource]) - json.dump({"nodes": resources, "links": []}, open("button_battery_station_resources_unilab.json", "w"), indent=2) + json.dump({"nodes": resources, "links": []}, open("button_battery_decks_unilab.json", "w"), indent=2) #print(resources) http_client.remote_addr = "https://uni-lab.test.bohrium.com/api/v1" diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv index 1805c23..d8ece23 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv @@ -43,10 +43,21 @@ REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,10000, UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,8730, UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,8530, REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,10018,ASSEMBLY_TYPE7or8 -COIL_ALUMINUM_FOIL,BOOL,,ʹ��������,,coil,8340, -REG_MSG_NE_PLATE_MATRIX,INT16,,����Ƭ�����λ,,hold_register,440, -REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,��Ĥ�����λ,,hold_register,450, -REG_MSG_TIP_BOX_MATRIX,INT16,,��Һǹͷ�����λ,,hold_register,480, -REG_MSG_NE_PLATE_NUM,INT16,,����Ƭ����,,hold_register,443, -REG_MSG_SEPARATOR_PLATE_NUM,INT16,,��Ĥ����,,hold_register,453, -REG_MSG_PRESS_MODE,BOOL,,ѹ��ģʽ��false:ѹ�����ģʽ��True:����ģʽ��,,coil,8360,���ѹ��ģʽ +COIL_ALUMINUM_FOIL,BOOL,,,,coil,8340, +REG_MSG_NE_PLATE_MATRIX,INT16,,Ƭλ,,hold_register,440, +REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,Ĥλ,,hold_register,450, +REG_MSG_TIP_BOX_MATRIX,INT16,,Һǹͷλ,,hold_register,480, +REG_MSG_NE_PLATE_NUM,INT16,,Ƭ,,hold_register,443, +REG_MSG_SEPARATOR_PLATE_NUM,INT16,,Ĥ,,hold_register,453, +,,,,,,, +,BOOL,,Ӿλfalse:ʹãtrue:ԣ,,coil,8300,Ӿλ +,BOOL,,죨false:ʹãtrue:ԣ,,coil,8310,Ӿ +,BOOL,,_֣false:ʹãtrue:ԣ,,coil,8320, +,BOOL,,_Ҳ֣false:ʹãtrue:ԣ,,coil,8420,Ҳ +,BOOL,,ռ֪false:ʹãtrue:ԣ,,coil,8350,ռ֪ +,BOOL,,ѹģʽfalse:ѹģʽTrue:ģʽ,,coil,8360,ѹģʽ +,BOOL,,Һģʽfalse:εҺtrue:εҺ,,coil,8370,Һģʽ +,BOOL,,Ƭأfalse:ʹãtrue:ԣ,,coil,8380,Ƭ +,BOOL,,Ƭװʽfalse:װtrue:װ,,coil,8390,װ +,BOOL,,ѹࣨfalse:ʹãtrue:ԣ,,coil,8400,ѹ +,BOOL,,̷̰ʽfalse:ˮƽ̣true:ѵ̣,,coil,8410,Ƭ̷ʽ diff --git a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json index 8efce63..c96e6b2 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json +++ b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json @@ -1,6 +1,20 @@ { "nodes": [ { + "id": "bioyond_cell_workstation", + "name": "配液分液工站", + "children": [ + ], + "parent": null, + "type": "device", + "class": "bioyond_cell", + "config": { + "protocol_type": [], + "station_resource": {} + }, + "data": {} + }, + { "id": "BatteryStation", "name": "扣电工作站", "children": [ @@ -16,674 +30,7 @@ }, "config": { "debug_mode": false, - "_comment": "protocol_type接外部工站固定写法字段,一般为空,station_resource写法也固定", - "protocol_type": [], - "station_resource": { - "data": { - "_resource_child_name": "coin_cell_deck", - "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" - } - }, - - "address": "172.21.32.20", - "port": 502 - }, - "data": {} - }, - { - "id": "coin_cell_deck", - "name": "coin_cell_deck", - "sample_id": null, - "children": [ - "\u7535\u6c60\u6599\u76d8" - ], - "parent": null, - "type": "container", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "CoincellDeck", - "size_x": 1000, - "size_y": 1000, - "size_z": 900, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "coin_cell_deck", - "barcode": null - }, - "data": {} - }, - { - "id": "\u7535\u6c60\u6599\u76d8", - "name": "\u7535\u6c60\u6599\u76d8", - "sample_id": null, - "children": [ - "\u7535\u6c60\u6599\u76d8_materialhole_0_0", - "\u7535\u6c60\u6599\u76d8_materialhole_0_1", - "\u7535\u6c60\u6599\u76d8_materialhole_0_2", - "\u7535\u6c60\u6599\u76d8_materialhole_0_3", - "\u7535\u6c60\u6599\u76d8_materialhole_1_0", - "\u7535\u6c60\u6599\u76d8_materialhole_1_1", - "\u7535\u6c60\u6599\u76d8_materialhole_1_2", - "\u7535\u6c60\u6599\u76d8_materialhole_1_3", - "\u7535\u6c60\u6599\u76d8_materialhole_2_0", - "\u7535\u6c60\u6599\u76d8_materialhole_2_1", - "\u7535\u6c60\u6599\u76d8_materialhole_2_2", - "\u7535\u6c60\u6599\u76d8_materialhole_2_3", - "\u7535\u6c60\u6599\u76d8_materialhole_3_0", - "\u7535\u6c60\u6599\u76d8_materialhole_3_1", - "\u7535\u6c60\u6599\u76d8_materialhole_3_2", - "\u7535\u6c60\u6599\u76d8_materialhole_3_3" - ], - "parent": "coin_cell_deck", - "type": "container", - "class": "", - "position": { - "x": 100, - "y": 100, - "z": 0 - }, - "config": { - "type": "MaterialPlate", - "size_x": 120.8, - "size_y": 160.5, - "size_z": 10.0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_plate", - "model": null, - "barcode": null, - "ordering": { - "A1": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", - "B1": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", - "C1": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", - "D1": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", - "A2": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", - "B2": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", - "C2": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", - "D2": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", - "A3": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", - "B3": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", - "C3": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", - "D3": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", - "A4": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", - "B4": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", - "C4": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", - "D4": "\u7535\u6c60\u6599\u76d8_materialhole_3_3" - } - }, - "data": {} - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_0", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 104.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_1", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 80.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_2", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 56.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_0_3", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 12.4, - "y": 32.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_0", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 104.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_1", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 80.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_2", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 56.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_1_3", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 36.4, - "y": 32.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_0", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 104.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_1", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 80.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_2", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 56.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_2_3", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 60.4, - "y": 32.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_0", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 104.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_1", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 80.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_2", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 56.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null - } - }, - { - "id": "\u7535\u6c60\u6599\u76d8_materialhole_3_3", - "name": "\u7535\u6c60\u6599\u76d8_materialhole_3_3", - "sample_id": null, - "children": [], - "parent": "\u7535\u6c60\u6599\u76d8", - "type": "container", - "class": "", - "position": { - "x": 84.4, - "y": 32.25, - "z": 10.0 - }, - "config": { - "type": "MaterialHole", - "size_x": 16, - "size_y": 16, - "size_z": 16, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "material_hole", - "model": null, - "barcode": null - }, - "data": { - "diameter": 20, - "depth": 10, - "max_sheets": 1, - "info": null + "protocol_type": [] } } ], From 95bdd39bf86c328388372c291a5887d04a26b0b2 Mon Sep 17 00:00:00 2001 From: calvincao Date: Thu, 30 Oct 2025 14:25:33 +0800 Subject: [PATCH 032/104] =?UTF-8?q?fix(workstation):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=20coin=5Fcell=5Fassembly=5Fa.csv=20=E4=B8=AD=E7=9A=84=E5=AF=84?= =?UTF-8?q?=E5=AD=98=E5=99=A8=E5=92=8C=E7=BA=BF=E5=9C=88=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E4=B8=BA=E4=B8=AD=E6=96=87=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将寄存器和线圈定义中的英文描述替换为中文,提升可读性 - 确保所有定义格式一致,保持文件的整洁性和维护性 --- .../coin_cell_assembly_a.csv | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv index d8ece23..bb66a7a 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv @@ -44,20 +44,20 @@ UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,8730, UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,8530, REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,10018,ASSEMBLY_TYPE7or8 COIL_ALUMINUM_FOIL,BOOL,,,,coil,8340, -REG_MSG_NE_PLATE_MATRIX,INT16,,Ƭλ,,hold_register,440, -REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,Ĥλ,,hold_register,450, -REG_MSG_TIP_BOX_MATRIX,INT16,,Һǹͷλ,,hold_register,480, -REG_MSG_NE_PLATE_NUM,INT16,,Ƭ,,hold_register,443, -REG_MSG_SEPARATOR_PLATE_NUM,INT16,,Ĥ,,hold_register,453, +REG_MSG_NE_PLATE_MATRIX,INT16,,负极片矩阵点位,,hold_register,440, +REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,隔膜矩阵点位,,hold_register,450, +REG_MSG_TIP_BOX_MATRIX,INT16,,移液枪头矩阵点位,,hold_register,480, +REG_MSG_NE_PLATE_NUM,INT16,,负极片盘数,,hold_register,443, +REG_MSG_SEPARATOR_PLATE_NUM,INT16,,隔膜盘数,,hold_register,453, ,,,,,,, -,BOOL,,Ӿλfalse:ʹãtrue:ԣ,,coil,8300,Ӿλ -,BOOL,,죨false:ʹãtrue:ԣ,,coil,8310,Ӿ -,BOOL,,_֣false:ʹãtrue:ԣ,,coil,8320, -,BOOL,,_Ҳ֣false:ʹãtrue:ԣ,,coil,8420,Ҳ -,BOOL,,ռ֪false:ʹãtrue:ԣ,,coil,8350,ռ֪ -,BOOL,,ѹģʽfalse:ѹģʽTrue:ģʽ,,coil,8360,ѹģʽ -,BOOL,,Һģʽfalse:εҺtrue:εҺ,,coil,8370,Һģʽ -,BOOL,,Ƭأfalse:ʹãtrue:ԣ,,coil,8380,Ƭ -,BOOL,,Ƭװʽfalse:װtrue:װ,,coil,8390,װ -,BOOL,,ѹࣨfalse:ʹãtrue:ԣ,,coil,8400,ѹ -,BOOL,,̷̰ʽfalse:ˮƽ̣true:ѵ̣,,coil,8410,Ƭ̷ʽ +,BOOL,,视觉对位(false:使用,true:忽略),,coil,8300,视觉对位 +,BOOL,,复检(false:使用,true:忽略),,coil,8310,视觉复检 +,BOOL,,手套箱_左仓(false:使用,true:忽略),,coil,8320,手套箱左仓 +,BOOL,,手套箱_右仓(false:使用,true:忽略),,coil,8420,手套箱右仓 +,BOOL,,真空检知(false:使用,true:忽略),,coil,8350,真空检知 +,BOOL,,压制模式(false:压力检测模式,True:距离模式),,coil,8360,电池压制模式 +,BOOL,,电解液添加模式(false:单次滴液,true:二次滴液),,coil,8370,滴液模式 +,BOOL,,正极片称重(false:使用,true:忽略),,coil,8380,正极片称重 +,BOOL,,正负极片组装方式(false:正装,true:倒装),,coil,8390,正负极反装 +,BOOL,,压制清洁(false:使用,true:忽略),,coil,8400,压制清洁 +,BOOL,,物料盘摆盘方式(false:水平摆盘,true:堆叠摆盘),,coil,8410,负极片摆盘方式 From f58921ef823cfb45a3a10064aac68d1d37c464a4 Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Thu, 30 Oct 2025 16:16:53 +0800 Subject: [PATCH 033/104] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=89=A3?= =?UTF-8?q?=E7=94=B5=E5=B7=A5=E4=BD=9C=E7=AB=99=20setup()=20=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=B9=B6=E4=BF=AE=E5=A4=8D=E6=98=BE=E7=A4=BA=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要改动: - ✅ 在 CoincellDeck 实现 setup() 方法(模仿 decks.py 三步配置模式) - ✅ 统一 Deck 默认尺寸为 1000x1000x900mm - ✅ 优化料盘布局:横向排列,留50mm边距 - ✅ 简化工作站 deck 创建逻辑(从30行减至1行) - ✅ 新增 create_coin_cell_deck() 便捷函数 - ✅ 修复 ClipMagazine 参数错误 - ✅ 删除约200行冗余代码 - ✅ 修复底座不显示问题 技术细节: - MaterialPlate 位置: liaopan1(50,50), liaopan2(250,50), 电池料盘(450,50) - 自动为 liaopan1 添加16个初始极片 - 支持3种 deck 创建方式 - 智能判断是否需要 setup --- .../coin_cell_assembly/YB_YH_materials.py | 976 +++++++++++++ .../coin_cell_assembly/coin_cell_assembly.py | 1224 +++++++++++++++++ 2 files changed, 2200 insertions(+) create mode 100644 unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py create mode 100644 unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py diff --git a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py new file mode 100644 index 0000000..6ea8939 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py @@ -0,0 +1,976 @@ +""" +纽扣电池组装工作站物料类定义 +Button Battery Assembly Station Resource Classes +""" + +from __future__ import annotations + +from collections import OrderedDict +from typing import Any, Dict, List, Optional, TypedDict, Union, cast + +from pylabrobot.resources.coordinate import Coordinate +from pylabrobot.resources.container import Container +from pylabrobot.resources.deck import Deck +from pylabrobot.resources.itemized_resource import ItemizedResource +from pylabrobot.resources.resource import Resource +from pylabrobot.resources.resource_stack import ResourceStack +from pylabrobot.resources.tip_rack import TipRack, TipSpot +from pylabrobot.resources.trash import Trash +from pylabrobot.resources.utils import create_ordered_items_2d + + +class ElectrodeSheetState(TypedDict): + diameter: float # 直径 (mm) + thickness: float # 厚度 (mm) + mass: float # 质量 (g) + material_type: str # 材料类型(正极、负极、隔膜、弹片、垫片、铝箔等) + height: float + electrolyte_name: str + data_electrolyte_code: str + open_circuit_voltage: float + assembly_pressure: float + electrolyte_volume: float + + info: Optional[str] # 附加信息 + +class ElectrodeSheet(Resource): + """极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料""" + + def __init__( + self, + name: str = "极片", + size_x=10, + size_y=10, + size_z=10, + category: str = "electrode_sheet", + model: Optional[str] = None, + ): + """初始化极片 + + Args: + name: 极片名称 + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + category=category, + model=model, + ) + self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState( + diameter=14, + thickness=0.1, + mass=0.5, + material_type="copper", + info=None + ) + + # TODO: 这个还要不要?给self._unilabos_state赋值的? + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + #序列化 + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + +# TODO: 这个应该只能放一个极片 +class MaterialHoleState(TypedDict): + diameter: int + depth: int + max_sheets: int + info: Optional[str] # 附加信息 + +class MaterialHole(Resource): + """料板洞位类""" + children: List[ElectrodeSheet] = [] + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + category: str = "material_hole", + **kwargs + ): + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + category=category, + ) + self._unilabos_state: MaterialHoleState = MaterialHoleState( + diameter=20, + depth=10, + max_sheets=1, + info=None + ) + + def get_all_sheet_info(self): + info_list = [] + for sheet in self.children: + info_list.append(sheet._unilabos_state["info"]) + return info_list + + #这个函数函数好像没用,一般不会集中赋值质量 + def set_all_sheet_mass(self): + for sheet in self.children: + sheet._unilabos_state["mass"] = 0.5 # 示例:设置质量为0.5g + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + #移动极片前先取出对象 + def get_sheet_with_name(self, name: str) -> Optional[ElectrodeSheet]: + for sheet in self.children: + if sheet.name == name: + return sheet + return None + + def has_electrode_sheet(self) -> bool: + """检查洞位是否有极片""" + return len(self.children) > 0 + + def assign_child_resource( + self, + resource: ElectrodeSheet, + location: Optional[Coordinate], + reassign: bool = True, + ): + """放置极片""" + # TODO: 这里要改,diameter找不到,加入._unilabos_state后应该没问题 + #if resource._unilabos_state["diameter"] > self._unilabos_state["diameter"]: + # raise ValueError(f"极片直径 {resource._unilabos_state['diameter']} 超过洞位直径 {self._unilabos_state['diameter']}") + #if len(self.children) >= self._unilabos_state["max_sheets"]: + # raise ValueError(f"洞位已满,无法放置更多极片") + super().assign_child_resource(resource, location, reassign) + + # 根据children的编号取物料对象。 + def get_electrode_sheet_info(self, index: int) -> ElectrodeSheet: + return self.children[index] + + + +class MaterialPlateState(TypedDict): + hole_spacing_x: float + hole_spacing_y: float + hole_diameter: float + info: Optional[str] # 附加信息 + +class MaterialPlate(ItemizedResource[MaterialHole]): + """料板类 - 4x4个洞位,每个洞位放1个极片""" + + children: List[MaterialHole] + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + ordered_items: Optional[Dict[str, MaterialHole]] = None, + ordering: Optional[OrderedDict[str, str]] = None, + category: str = "material_plate", + model: Optional[str] = None, + fill: bool = False + ): + """初始化料板 + + Args: + name: 料板名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + hole_diameter: 洞直径 (mm) + hole_depth: 洞深度 (mm) + hole_spacing_x: X方向洞位间距 (mm) + hole_spacing_y: Y方向洞位间距 (mm) + number: 编号 + category: 类别 + model: 型号 + """ + self._unilabos_state: MaterialPlateState = MaterialPlateState( + hole_spacing_x=24.0, + hole_spacing_y=24.0, + hole_diameter=20.0, + info="", + ) + # 创建4x4的洞位 + # TODO: 这里要改,对应不同形状 + holes = create_ordered_items_2d( + klass=MaterialHole, + num_items_x=4, + num_items_y=4, + dx=(size_x - 4 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中 + dy=(size_y - 4 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中 + dz=size_z, + item_dx=self._unilabos_state["hole_spacing_x"], + item_dy=self._unilabos_state["hole_spacing_y"], + size_x = 16, + size_y = 16, + size_z = 16, + ) + if fill: + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=holes, + category=category, + model=model, + ) + else: + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=ordered_items, + ordering=ordering, + category=category, + model=model, + ) + + def update_locations(self): + # TODO:调多次相加 + holes = create_ordered_items_2d( + klass=MaterialHole, + num_items_x=4, + num_items_y=4, + dx=(self._size_x - 3 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中 + dy=(self._size_y - 3 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中 + dz=self._size_z, + item_dx=self._unilabos_state["hole_spacing_x"], + item_dy=self._unilabos_state["hole_spacing_y"], + size_x = 1, + size_y = 1, + size_z = 1, + ) + for item, original_item in zip(holes.items(), self.children): + original_item.location = item[1].location + + +class PlateSlot(ResourceStack): + """板槽位类 - 1个槽上能堆放8个板,移板只能操作最上方的板""" + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + max_plates: int = 8, + category: str = "plate_slot", + model: Optional[str] = None + ): + """初始化板槽位 + + Args: + name: 槽位名称 + max_plates: 最大板数量 + category: 类别 + """ + super().__init__( + name=name, + direction="z", # Z方向堆叠 + resources=[], + ) + self.max_plates = max_plates + self.category = category + + def can_add_plate(self) -> bool: + """检查是否可以添加板""" + return len(self.children) < self.max_plates + + def add_plate(self, plate: MaterialPlate) -> None: + """添加料板""" + if not self.can_add_plate(): + raise ValueError(f"槽位 {self.name} 已满,无法添加更多板") + self.assign_child_resource(plate) + + def get_top_plate(self) -> MaterialPlate: + """获取最上方的板""" + if len(self.children) == 0: + raise ValueError(f"槽位 {self.name} 为空") + return cast(MaterialPlate, self.get_top_item()) + + def take_top_plate(self) -> MaterialPlate: + """取出最上方的板""" + top_plate = self.get_top_plate() + self.unassign_child_resource(top_plate) + return top_plate + + def can_access_for_picking(self) -> bool: + """检查是否可以进行取料操作(只有最上方的板能进行取料操作)""" + return len(self.children) > 0 + + def serialize(self) -> dict: + return { + **super().serialize(), + "max_plates": self.max_plates, + } + + +class ClipMagazineHole(Container): + """子弹夹洞位类""" + + def __init__( + self, + name: str, + diameter: float, + depth: float, + max_sheets: int = 100, + category: str = "clip_magazine_hole", + ): + """初始化子弹夹洞位 + + Args: + name: 洞位名称 + diameter: 洞直径 (mm) + depth: 洞深度 (mm) + max_sheets: 最大极片数量 + category: 类别 + """ + super().__init__( + name=name, + size_x=diameter, + size_y=diameter, + size_z=depth, + category=category, + ) + self.diameter = diameter + self.depth = depth + self.max_sheets = max_sheets + self._sheets: List[ElectrodeSheet] = [] + + def can_add_sheet(self, sheet: ElectrodeSheet) -> bool: + """检查是否可以添加极片""" + return (len(self._sheets) < self.max_sheets and + sheet.diameter <= self.diameter) + + def add_sheet(self, sheet: ElectrodeSheet) -> None: + """添加极片""" + if not self.can_add_sheet(sheet): + raise ValueError(f"无法向洞位 {self.name} 添加极片") + self._sheets.append(sheet) + + def take_sheet(self) -> ElectrodeSheet: + """取出极片""" + if len(self._sheets) == 0: + raise ValueError(f"洞位 {self.name} 没有极片") + return self._sheets.pop() + + def get_sheet_count(self) -> int: + """获取极片数量""" + return len(self._sheets) + + def serialize_state(self) -> Dict[str, Any]: + return { + "sheet_count": len(self._sheets), + "sheets": [sheet.serialize() for sheet in self._sheets], + } + +# TODO: 这个要改 +class ClipMagazine(Resource): + """子弹夹类 - 有6个洞位,每个洞位放多个极片""" + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + hole_diameter: float = 20.0, + hole_depth: float = 50.0, + hole_spacing: float = 25.0, + max_sheets_per_hole: int = 100, + category: str = "clip_magazine", + model: Optional[str] = None, + ): + """初始化子弹夹 + + Args: + name: 子弹夹名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + hole_diameter: 洞直径 (mm) + hole_depth: 洞深度 (mm) + hole_spacing: 洞位间距 (mm) + max_sheets_per_hole: 每个洞位最大极片数量 + category: 类别 + model: 型号 + """ + # 创建6个洞位,排成2x3布局 + holes = create_ordered_items_2d( + klass=ClipMagazineHole, + num_items_x=3, + num_items_y=2, + dx=(size_x - 2 * hole_spacing) / 2, # 居中 + dy=(size_y - hole_spacing) / 2, # 居中 + dz=size_z - 0, + item_dx=hole_spacing, + item_dy=hole_spacing, + diameter=hole_diameter, + depth=hole_depth, + ) + + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=holes, + category=category, + model=model, + ) + + self.hole_diameter = hole_diameter + self.hole_depth = hole_depth + self.max_sheets_per_hole = max_sheets_per_hole + + def serialize(self) -> dict: + return { + **super().serialize(), + "hole_diameter": self.hole_diameter, + "hole_depth": self.hole_depth, + "max_sheets_per_hole": self.max_sheets_per_hole, + } +#是一种类型注解,不用self +class BatteryState(TypedDict): + """电池状态字典""" + diameter: float + height: float + assembly_pressure: float + electrolyte_volume: float + electrolyte_name: str + +class Battery(Resource): + """电池类 - 可容纳极片""" + children: List[ElectrodeSheet] = [] + + def __init__( + self, + name: str, + size_x=1, + size_y=1, + size_z=1, + category: str = "battery", + ): + """初始化电池 + + Args: + name: 电池名称 + diameter: 直径 (mm) + height: 高度 (mm) + max_volume: 最大容量 (μL) + barcode: 二维码编号 + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=1, + size_y=1, + size_z=1, + category=category, + ) + self._unilabos_state: BatteryState = BatteryState( + diameter = 1.0, + height = 1.0, + assembly_pressure = 1.0, + electrolyte_volume = 1.0, + electrolyte_name = "DP001" + ) + + def add_electrolyte_with_bottle(self, bottle: Bottle) -> bool: + to_add_name = bottle._unilabos_state["electrolyte_name"] + if bottle.aspirate_electrolyte(10): + if self.add_electrolyte(to_add_name, 10): + pass + else: + bottle._unilabos_state["electrolyte_volume"] += 10 + + def set_electrolyte(self, name: str, volume: float) -> None: + """设置电解液信息""" + self._unilabos_state["electrolyte_name"] = name + self._unilabos_state["electrolyte_volume"] = volume + #这个应该没用,不会有加了后再加的事情 + def add_electrolyte(self, name: str, volume: float) -> bool: + """添加电解液信息""" + if name != self._unilabos_state["electrolyte_name"]: + return False + self._unilabos_state["electrolyte_volume"] += volume + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + +# 电解液作为属性放进去 + +class BatteryPressSlotState(TypedDict): + """电池状态字典""" + diameter: float =20.0 + depth: float = 4.0 + +class BatteryPressSlot(Resource): + """电池压制槽类 - 设备,可容纳一个电池""" + children: List[Battery] = [] + + def __init__( + self, + name: str = "BatteryPressSlot", + category: str = "battery_press_slot", + ): + """初始化电池压制槽 + + Args: + name: 压制槽名称 + diameter: 直径 (mm) + depth: 深度 (mm) + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=10, + size_y=12, + size_z=13, + category=category, + ) + self._unilabos_state: BatteryPressSlotState = BatteryPressSlotState() + + def has_battery(self) -> bool: + """检查是否有电池""" + return len(self.children) > 0 + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + + def assign_child_resource( + self, + resource: Battery, + location: Optional[Coordinate], + reassign: bool = True, + ): + """放置极片""" + # TODO: 让高京看下槽位只有一个电池时是否这么写。 + if self.has_battery(): + raise ValueError(f"槽位已含有一个电池,无法再放置其他电池") + super().assign_child_resource(resource, location, reassign) + + # 根据children的编号取物料对象。 + def get_battery_info(self, index: int) -> Battery: + return self.children[0] + +# TODO:这个移液枪架子看一下从哪继承 +class TipBox64State(TypedDict): + """电池状态字典""" + tip_diameter: float = 5.0 + tip_length: float = 50.0 + with_tips: bool = True + +class TipBox64(TipRack): + """64孔枪头盒类""" + + children: List[TipSpot] = [] + def __init__( + self, + name: str, + size_x: float = 127.8, + size_y: float = 85.5, + size_z: float = 60.0, + category: str = "tip_box_64", + model: Optional[str] = None, + ): + """初始化64孔枪头盒 + + Args: + name: 枪头盒名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + tip_diameter: 枪头直径 (mm) + tip_length: 枪头长度 (mm) + category: 类别 + model: 型号 + with_tips: 是否带枪头 + """ + from pylabrobot.resources.tip import Tip + + # 创建8x8=64个枪头位 + def make_tip(): + return Tip( + has_filter=False, + total_tip_length=20.0, + maximal_volume=1000, # 1mL + fitting_depth=8.0, + ) + + tip_spots = create_ordered_items_2d( + klass=TipSpot, + num_items_x=8, + num_items_y=8, + dx=8.0, + dy=8.0, + dz=0.0, + item_dx=9.0, + item_dy=9.0, + size_x=10, + size_y=10, + size_z=0.0, + make_tip=make_tip, + ) + self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate() + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=tip_spots, + category=category, + model=model, + with_tips=True, + ) + + + +class WasteTipBoxstate(TypedDict): + """"废枪头盒状态字典""" + max_tips: int = 100 + tip_count: int = 0 + +#枪头不是一次性的(同一溶液则反复使用),根据寄存器判断 +class WasteTipBox(Trash): + """废枪头盒类 - 100个枪头容量""" + + def __init__( + self, + name: str, + size_x: float = 127.8, + size_y: float = 85.5, + size_z: float = 60.0, + category: str = "waste_tip_box", + model: Optional[str] = None, + ): + """初始化废枪头盒 + + Args: + name: 废枪头盒名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + max_tips: 最大枪头容量 + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + category=category, + model=model, + ) + self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate() + + def add_tip(self) -> None: + """添加废枪头""" + if self._unilabos_state["tip_count"] >= self._unilabos_state["max_tips"]: + raise ValueError(f"废枪头盒 {self.name} 已满") + self._unilabos_state["tip_count"] += 1 + + def get_tip_count(self) -> int: + """获取枪头数量""" + return self._unilabos_state["tip_count"] + + def empty(self) -> None: + """清空废枪头盒""" + self._unilabos_state["tip_count"] = 0 + + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + + +class BottleRackState(TypedDict): + """ bottle_diameter: 瓶子直径 (mm) + bottle_height: 瓶子高度 (mm) + position_spacing: 位置间距 (mm)""" + bottle_diameter: float + bottle_height: float + name_to_index: dict + + + +class BottleRack(Resource): + """瓶架类 - 12个待配位置+12个已配位置""" + children: List[Bottle] = [] + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + category: str = "bottle_rack", + model: Optional[str] = None, + ): + """初始化瓶架 + + Args: + name: 瓶架名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + category=category, + model=model, + ) + # TODO: 添加瓶位坐标映射 + self.index_to_pos = { + 0: Coordinate.zero(), + 1: Coordinate(x=1, y=2, z=3) # 添加 + } + self.name_to_index = {} + self.name_to_pos = {} + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + + # TODO: 这里有些问题要重新写一下 + def assign_child_resource(self, resource: Bottle, location=Coordinate.zero(), reassign = True): + assert len(self.children) <= 12, "瓶架已满,无法添加更多瓶子" + index = len(self.children) + location = Coordinate(x=20 + (index % 4) * 15, y=20 + (index // 4) * 15, z=0) + self.name_to_pos[resource.name] = location + self.name_to_index[resource.name] = index + return super().assign_child_resource(resource, location, reassign) + + def assign_child_resource_by_index(self, resource: Bottle, index: int): + assert 0 <= index < 12, "无效的瓶子索引" + self.name_to_index[resource.name] = index + location = self.index_to_pos[index] + return super().assign_child_resource(resource, location) + + def unassign_child_resource(self, resource: Bottle): + super().unassign_child_resource(resource) + self.index_to_pos.pop(self.name_to_index.pop(resource.name, None), None) + + # def serialize(self): + # self.children.sort(key=lambda x: self.name_to_index.get(x.name, 0)) + # return super().serialize() + + +class BottleState(TypedDict): + diameter: float + height: float + electrolyte_name: str + electrolyte_volume: float + max_volume: float + +class Bottle(Resource): + """瓶子类 - 容纳电解液""" + + def __init__( + self, + name: str, + category: str = "bottle", + ): + """初始化瓶子 + + Args: + name: 瓶子名称 + diameter: 直径 (mm) + height: 高度 (mm) + max_volume: 最大体积 (μL) + barcode: 二维码 + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=1, + size_y=1, + size_z=1, + category=category, + ) + self._unilabos_state: BottleState = BottleState() + + def aspirate_electrolyte(self, volume: float) -> bool: + current_volume = self._unilabos_state["electrolyte_volume"] + assert current_volume > volume, f"Cannot aspirate {volume}μL, only {current_volume}μL available." + self._unilabos_state["electrolyte_volume"] -= volume + return True + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + +class CoincellDeck(Deck): + """纽扣电池组装工作站台面类""" + + def __init__( + self, + name: str = "coin_cell_deck", + size_x: float = 1000.0, # 1m + size_y: float = 1000.0, # 1m + size_z: float = 900.0, # 0.9m + origin: Coordinate = Coordinate(0, 0, 0), + category: str = "coin_cell_deck", + setup: bool = False, # 是否自动执行 setup + ): + """初始化纽扣电池组装工作站台面 + + Args: + name: 台面名称 + size_x: 长度 (mm) - 1m + size_y: 宽度 (mm) - 1m + size_z: 高度 (mm) - 0.9m + origin: 原点坐标 + category: 类别 + setup: 是否自动执行 setup 配置标准布局 + """ + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + origin=origin, + category=category, + ) + if setup: + self.setup() + + def setup(self) -> None: + """设置工作站的标准布局 - 包含3个料盘""" + # 步骤 1: 创建所有料盘 + self.plates = { + "liaopan1": MaterialPlate( + name="liaopan1", + size_x=120.8, + size_y=120.5, + size_z=10.0, + fill=True + ), + "liaopan2": MaterialPlate( + name="liaopan2", + size_x=120.8, + size_y=120.5, + size_z=10.0, + fill=True + ), + "电池料盘": MaterialPlate( + name="电池料盘", + size_x=120.8, + size_y=160.5, + size_z=10.0, + fill=True + ), + } + + # 步骤 2: 定义料盘在 deck 上的位置 + # Deck 尺寸: 1000×1000mm,料盘尺寸: 120.8×120.5mm 或 120.8×160.5mm + self.plate_locations = { + "liaopan1": Coordinate(x=50, y=50, z=0), # 左上角,留 50mm 边距 + "liaopan2": Coordinate(x=250, y=50, z=0), # 中间,liaopan1 右侧 + "电池料盘": Coordinate(x=450, y=50, z=0), # 右侧 + } + + # 步骤 3: 将料盘分配到 deck 上 + for plate_name, plate in self.plates.items(): + self.assign_child_resource( + plate, + location=self.plate_locations[plate_name] + ) + + # 步骤 4: 为 liaopan1 添加初始极片 + for i in range(16): + jipian = ElectrodeSheet( + name=f"jipian1_{i}", + size_x=12, + size_y=12, + size_z=0.1 + ) + self.plates["liaopan1"].children[i].assign_child_resource( + jipian, + location=None + ) + + +def create_coin_cell_deck(name: str = "coin_cell_deck", size_x: float = 1000.0, size_y: float = 1000.0, size_z: float = 900.0) -> CoincellDeck: + """创建并配置标准的纽扣电池组装工作站台面 + + Args: + name: 台面名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + + Returns: + 已配置好的 CoincellDeck 对象 + """ + deck = CoincellDeck(name=name, size_x=size_x, size_y=size_y, size_z=size_z) + deck.setup() + return deck \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py new file mode 100644 index 0000000..cf06f70 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -0,0 +1,1224 @@ + +import csv +import inspect +import json +import os +import threading +import time +import types +from datetime import datetime +from typing import Any, Dict, Optional +from functools import wraps +from pylabrobot.resources import Deck, Resource as PLRResource +from unilabos_msgs.msg import Resource +from unilabos.device_comms.modbus_plc.client import ModbusTcpClient +from unilabos.devices.workstation.workstation_base import WorkstationBase +from unilabos.device_comms.modbus_plc.client import TCPClient, ModbusNode, PLCWorkflow, ModbusWorkflow, WorkflowAction, BaseClient +from unilabos.device_comms.modbus_plc.modbus import DeviceType, Base as ModbusNodeBase, DataType, WorderOrder +from unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials import * +from unilabos.ros.nodes.base_device_node import ROS2DeviceNode, BaseROS2DeviceNode +from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode +from unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials import CoincellDeck +from unilabos.resources.graphio import convert_resources_to_type +from unilabos.utils.log import logger + + +def _ensure_modbus_slave_kw_alias(modbus_client): + if modbus_client is None: + return + + method_names = [ + "read_coils", + "write_coils", + "write_coil", + "read_discrete_inputs", + "read_holding_registers", + "write_register", + "write_registers", + ] + + def _wrap(func): + signature = inspect.signature(func) + has_var_kwargs = any(param.kind == param.VAR_KEYWORD for param in signature.parameters.values()) + accepts_unit = has_var_kwargs or "unit" in signature.parameters + accepts_slave = has_var_kwargs or "slave" in signature.parameters + + @wraps(func) + def _wrapped(self, *args, **kwargs): + if "slave" in kwargs and not accepts_slave: + slave_value = kwargs.pop("slave") + if accepts_unit and "unit" not in kwargs: + kwargs["unit"] = slave_value + if "unit" in kwargs and not accepts_unit: + unit_value = kwargs.pop("unit") + if accepts_slave and "slave" not in kwargs: + kwargs["slave"] = unit_value + return func(self, *args, **kwargs) + + _wrapped._has_slave_alias = True + return _wrapped + + for name in method_names: + if not hasattr(modbus_client, name): + continue + bound_method = getattr(modbus_client, name) + func = getattr(bound_method, "__func__", None) + if func is None: + continue + if getattr(func, "_has_slave_alias", False): + continue + wrapped = _wrap(func) + setattr(modbus_client, name, types.MethodType(wrapped, modbus_client)) + + +def _coerce_deck_input(deck: Any) -> Optional[Deck]: + if deck is None: + return None + + if isinstance(deck, Deck): + return deck + + if isinstance(deck, PLRResource): + return deck if isinstance(deck, Deck) else None + + candidates = None + if isinstance(deck, dict): + if "nodes" in deck and isinstance(deck["nodes"], list): + candidates = deck["nodes"] + else: + candidates = [deck] + elif isinstance(deck, list): + candidates = deck + + if candidates is None: + return None + + try: + converted = convert_resources_to_type(resources_list=candidates, resource_type=Deck) + if isinstance(converted, Deck): + return converted + if isinstance(converted, list): + for item in converted: + if isinstance(item, Deck): + return item + except Exception as exc: + logger.warning(f"deck 转换 Deck 失败: {exc}") + return None + + +#构建物料系统 + +class CoinCellAssemblyWorkstation(WorkstationBase): + def __init__( + self, + deck: Deck=None, + address: str = "172.21.32.111", + port: str = "502", + debug_mode: bool = False, + *args, + **kwargs, + ): + if deck is None and "deck" in kwargs: + deck = kwargs.pop("deck") + else: + kwargs.pop("deck", None) + + normalized_deck = _coerce_deck_input(deck) + + if deck is None and isinstance(normalized_deck, Deck): + deck = normalized_deck + + super().__init__( + #桌子 + deck=deck, + *args, + **kwargs, + ) + self.debug_mode = debug_mode + + # 如果没有传入 deck,则创建标准配置的 deck + if self.deck is None: + self.deck = CoincellDeck(size_x=1000, size_y=1000, size_z=900, setup=True) + else: + # 如果传入了 deck 但还没有 setup,可以选择是否 setup + if self.deck is not None and len(self.deck.children) == 0: + # deck 为空,执行 setup + self.deck.setup() + # 否则使用传入的 deck(可能已经配置好了) + self.deck = self.deck + + """ 连接初始化 """ + modbus_client = TCPClient(addr=address, port=port) + print("modbus_client", modbus_client) + _ensure_modbus_slave_kw_alias(modbus_client.client) + if not debug_mode: + modbus_client.client.connect() + count = 100 + while count >0: + count -=1 + if modbus_client.client.is_socket_open(): + break + time.sleep(2) + if not modbus_client.client.is_socket_open(): + raise ValueError('modbus tcp connection failed') + else: + print("测试模式,跳过连接") + + """ 工站的配置 """ + self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_a.csv')) + self.client = modbus_client.register_node_list(self.nodes) + self.success = False + self.allow_data_read = False #允许读取函数运行标志位 + self.csv_export_thread = None + self.csv_export_running = False + self.csv_export_file = None + self.coin_num_N = 0 #已组装电池数量 + #创建一个物料台面,包含两个极片板 + #self._ros_node.update_resource(self.deck) + + #ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ + # "resources": [self.deck] + #}) + + + def post_init(self, ros_node: ROS2WorkstationNode): + self._ros_node = ros_node + #self.deck = create_a_coin_cell_deck() + ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ + "resources": [self.deck] + }) + + # 批量操作在这里写 + async def change_hole_sheet_to_2(self, hole: MaterialHole): + hole._unilabos_state["max_sheets"] = 2 + return await self._ros_node.update_resource(hole) + + + async def fill_plate(self): + plate_1: MaterialPlate = self.deck.children[0].children[0] + #plate_1 + return await self._ros_node.update_resource(plate_1) + + #def run_assembly(self, wf_name: str, resource: PLRResource, params: str = "\{\}"): + # """启动工作流""" + # self.current_workflow_status = WorkflowStatus.RUNNING + # logger.info(f"工作站 {self.device_id} 启动工作流: {wf_name}") +# + # # TODO: 实现工作流逻辑 +# + # anode_sheet = self.deck.get_resource("anode_sheet") + + """ Action逻辑代码 """ + def _sys_start_cmd(self, cmd=None): + """设备启动命令 (可读写)""" + if cmd is not None: # 写入模式 + self.success = False + node = self.client.use_node('COIL_SYS_START_CMD') + ret = node.write(cmd) + print(ret) + self.success = True + return self.success + else: # 读取模式 + cmd_feedback, read_err = self.client.use_node('COIL_SYS_START_CMD').read(1) + return cmd_feedback[0] + + def _sys_stop_cmd(self, cmd=None): + """设备停止命令 (可读写)""" + if cmd is not None: # 写入模式 + self.success = False + node = self.client.use_node('COIL_SYS_STOP_CMD') + node.write(cmd) + self.success = True + return self.success + else: # 读取模式 + cmd_feedback, read_err = self.client.use_node('COIL_SYS_STOP_CMD').read(1) + return cmd_feedback[0] + + def _sys_reset_cmd(self, cmd=None): + """设备复位命令 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_SYS_RESET_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_SYS_RESET_CMD').read(1) + return cmd_feedback[0] + + def _sys_hand_cmd(self, cmd=None): + """手动模式命令 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_SYS_HAND_CMD').write(cmd) + self.success = True + print("步骤0") + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_SYS_HAND_CMD').read(1) + return cmd_feedback[0] + + def _sys_auto_cmd(self, cmd=None): + """自动模式命令 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_SYS_AUTO_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_SYS_AUTO_CMD').read(1) + return cmd_feedback[0] + + def _sys_init_cmd(self, cmd=None): + """初始化命令 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_SYS_INIT_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_SYS_INIT_CMD').read(1) + return cmd_feedback[0] + + def _unilab_send_msg_succ_cmd(self, cmd=None): + """UNILAB发送配方完毕 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_UNILAB_SEND_MSG_SUCC_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_UNILAB_SEND_MSG_SUCC_CMD').read(1) + return cmd_feedback[0] + + def _unilab_rec_msg_succ_cmd(self, cmd=None): + """UNILAB接收测试电池数据完毕 (可读写)""" + if cmd is not None: + self.success = False + self.client.use_node('COIL_UNILAB_REC_MSG_SUCC_CMD').write(cmd) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('COIL_UNILAB_REC_MSG_SUCC_CMD').read(1) + return cmd_feedback + + + # ====================== 命令类指令(REG_x_) ====================== + def _unilab_send_msg_electrolyte_num(self, num=None): + """UNILAB写电解液使用瓶数(可读写)""" + if num is not None: + self.success = False + ret = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').write(num) + print(ret) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').read(1) + return cmd_feedback[0] + + def _unilab_send_msg_electrolyte_use_num(self, use_num=None): + """UNILAB写单次电解液使用瓶数(可读写)""" + if use_num is not None: + self.success = False + self.client.use_node('REG_MSG_ELECTROLYTE_USE_NUM').write(use_num) + self.success = True + return self.success + else: + return False + + def _unilab_send_msg_assembly_type(self, num=None): + """UNILAB写组装参数""" + if num is not None: + self.success = False + self.client.use_node('REG_MSG_ASSEMBLY_TYPE').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ASSEMBLY_TYPE').read(1) + return cmd_feedback[0] + + def _unilab_send_msg_electrolyte_vol(self, vol=None): + """UNILAB写电解液吸取量参数""" + if vol is not None: + self.success = False + self.client.use_node('REG_MSG_ELECTROLYTE_VOLUME').write(vol, data_type=DataType.FLOAT32, word_order=WorderOrder.LITTLE) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_VOLUME').read(2, word_order=WorderOrder.LITTLE) + return cmd_feedback[0] + + def _unilab_send_msg_assembly_pressure(self, vol=None): + """UNILAB写电池压制力""" + if vol is not None: + self.success = False + self.client.use_node('REG_MSG_ASSEMBLY_PRESSURE').write(vol, data_type=DataType.FLOAT32, word_order=WorderOrder.LITTLE) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ASSEMBLY_PRESSURE').read(2, word_order=WorderOrder.LITTLE) + return cmd_feedback[0] + + # ==================== 0905新增内容(COIL_x_STATUS) ==================== + def _unilab_send_electrolyte_bottle_num(self, num=None): + """UNILAB发送电解液瓶数完毕""" + if num is not None: + self.success = False + self.client.use_node('UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM').read(1) + return cmd_feedback[0] + + def _unilab_rece_electrolyte_bottle_num(self, num=None): + """设备请求接受电解液瓶数""" + if num is not None: + self.success = False + self.client.use_node('UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM').read(1) + return cmd_feedback[0] + + def _reg_msg_electrolyte_num(self, num=None): + """电解液已使用瓶数""" + if num is not None: + self.success = False + self.client.use_node('REG_MSG_ELECTROLYTE_NUM').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_MSG_ELECTROLYTE_NUM').read(1) + return cmd_feedback[0] + + def _reg_data_electrolyte_use_num(self, num=None): + """单瓶电解液完成组装数""" + if num is not None: + self.success = False + self.client.use_node('REG_DATA_ELECTROLYTE_USE_NUM').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_USE_NUM').read(1) + return cmd_feedback[0] + + def _unilab_send_finished_cmd(self, num=None): + """Unilab发送已知一组组装完成信号""" + if num is not None: + self.success = False + self.client.use_node('UNILAB_SEND_FINISHED_CMD').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('UNILAB_SEND_FINISHED_CMD').read(1) + return cmd_feedback[0] + + def _unilab_rece_finished_cmd(self, num=None): + """Unilab接收已知一组组装完成信号""" + if num is not None: + self.success = False + self.client.use_node('UNILAB_RECE_FINISHED_CMD').write(num) + self.success = True + return self.success + else: + cmd_feedback, read_err = self.client.use_node('UNILAB_RECE_FINISHED_CMD').read(1) + return cmd_feedback[0] + + + + # ==================== 状态类属性(COIL_x_STATUS) ==================== + def _sys_start_status(self) -> bool: + """设备启动中( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_START_STATUS').read(1) + return status[0] + + def _sys_stop_status(self) -> bool: + """设备停止中( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_STOP_STATUS').read(1) + return status[0] + + def _sys_reset_status(self) -> bool: + """设备复位中( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_RESET_STATUS').read(1) + return status[0] + + def _sys_init_status(self) -> bool: + """设备初始化完成( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_INIT_STATUS').read(1) + return status[0] + + # 查找资源 + def modify_deck_name(self, resource_name: str): + # figure_res = self._ros_node.resource_tracker.figure_resource({"name": resource_name}) + # print(f"!!! figure_res: {type(figure_res)}") + self.deck.children[1] + return + + @property + def sys_status(self) -> str: + if self.debug_mode: + return "设备调试模式" + if self._sys_start_status(): + return "设备启动中" + elif self._sys_stop_status(): + return "设备停止中" + elif self._sys_reset_status(): + return "设备复位中" + elif self._sys_init_status(): + return "设备初始化中" + else: + return "未知状态" + + def _sys_hand_status(self) -> bool: + """设备手动模式( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_HAND_STATUS').read(1) + return status[0] + + def _sys_auto_status(self) -> bool: + """设备自动模式( BOOL)""" + status, read_err = self.client.use_node('COIL_SYS_AUTO_STATUS').read(1) + return status[0] + + @property + def sys_mode(self) -> str: + if self.debug_mode: + return "设备调试模式" + if self._sys_hand_status(): + return "设备手动模式" + elif self._sys_auto_status(): + return "设备自动模式" + else: + return "未知模式" + + @property + def request_rec_msg_status(self) -> bool: + """设备请求接受配方( BOOL)""" + if self.debug_mode: + return True + status, read_err = self.client.use_node('COIL_REQUEST_REC_MSG_STATUS').read(1) + return status[0] + + @property + def request_send_msg_status(self) -> bool: + """设备请求发送测试数据( BOOL)""" + if self.debug_mode: + return True + status, read_err = self.client.use_node('COIL_REQUEST_SEND_MSG_STATUS').read(1) + return status[0] + + # ======================= 其他属性(特殊功能) ======================== + ''' + @property + def warning_1(self) -> bool: + status, read_err = self.client.use_node('COIL_WARNING_1').read(1) + return status[0] + ''' + # ===================== 生产数据区 ====================== + + @property + def data_assembly_coin_cell_num(self) -> int: + """已完成电池数量 (INT16)""" + if self.debug_mode: + return 0 + num, read_err = self.client.use_node('REG_DATA_ASSEMBLY_COIN_CELL_NUM').read(1) + return num + + @property + def data_assembly_time(self) -> float: + """单颗电池组装时间 (秒, REAL/FLOAT32)""" + if self.debug_mode: + return 0 + time, read_err = self.client.use_node('REG_DATA_ASSEMBLY_PER_TIME').read(2, word_order=WorderOrder.LITTLE) + return time + + @property + def data_open_circuit_voltage(self) -> float: + """开路电压值 (FLOAT32)""" + if self.debug_mode: + return 0 + vol, read_err = self.client.use_node('REG_DATA_OPEN_CIRCUIT_VOLTAGE').read(2, word_order=WorderOrder.LITTLE) + return vol + + @property + def data_axis_x_pos(self) -> float: + """分液X轴当前位置 (FLOAT32)""" + if self.debug_mode: + return 0 + pos, read_err = self.client.use_node('REG_DATA_AXIS_X_POS').read(2, word_order=WorderOrder.LITTLE) + return pos + + @property + def data_axis_y_pos(self) -> float: + """分液Y轴当前位置 (FLOAT32)""" + if self.debug_mode: + return 0 + pos, read_err = self.client.use_node('REG_DATA_AXIS_Y_POS').read(2, word_order=WorderOrder.LITTLE) + return pos + + @property + def data_axis_z_pos(self) -> float: + """分液Z轴当前位置 (FLOAT32)""" + if self.debug_mode: + return 0 + pos, read_err = self.client.use_node('REG_DATA_AXIS_Z_POS').read(2, word_order=WorderOrder.LITTLE) + return pos + + @property + def data_pole_weight(self) -> float: + """当前电池正极片称重数据 (FLOAT32)""" + if self.debug_mode: + return 0 + weight, read_err = self.client.use_node('REG_DATA_POLE_WEIGHT').read(2, word_order=WorderOrder.LITTLE) + return weight + + @property + def data_assembly_pressure(self) -> int: + """当前电池压制力 (INT16)""" + if self.debug_mode: + return 0 + pressure, read_err = self.client.use_node('REG_DATA_ASSEMBLY_PRESSURE').read(1) + return pressure + + @property + def data_electrolyte_volume(self) -> int: + """当前电解液加注量 (INT16)""" + if self.debug_mode: + return 0 + vol, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_VOLUME').read(1) + return vol + + @property + def data_coin_num(self) -> int: + """当前电池数量 (INT16)""" + if self.debug_mode: + return 0 + num, read_err = self.client.use_node('REG_DATA_COIN_NUM').read(1) + return num + + @property + def data_coin_cell_code(self) -> str: + """电池二维码序列号 (STRING)""" + try: + # 尝试不同的字节序读取 + code_little, read_err = self.client.use_node('REG_DATA_COIN_CELL_CODE').read(10, word_order=WorderOrder.LITTLE) + print(code_little) + clean_code = code_little[-8:][::-1] + return clean_code + except Exception as e: + print(f"读取电池二维码失败: {e}") + return "N/A" + + + @property + def data_electrolyte_code(self) -> str: + try: + # 尝试不同的字节序读取 + code_little, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_CODE').read(10, word_order=WorderOrder.LITTLE) + print(code_little) + clean_code = code_little[-8:][::-1] + return clean_code + except Exception as e: + print(f"读取电解液二维码失败: {e}") + return "N/A" + + # ===================== 环境监控区 ====================== + @property + def data_glove_box_pressure(self) -> float: + """手套箱压力 (bar, FLOAT32)""" + if self.debug_mode: + return 0 + status, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_PRESSURE').read(2, word_order=WorderOrder.LITTLE) + return status + + @property + def data_glove_box_o2_content(self) -> float: + """手套箱氧含量 (ppm, FLOAT32)""" + if self.debug_mode: + return 0 + value, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_O2_CONTENT').read(2, word_order=WorderOrder.LITTLE) + return value + + @property + def data_glove_box_water_content(self) -> float: + """手套箱水含量 (ppm, FLOAT32)""" + if self.debug_mode: + return 0 + value, read_err = self.client.use_node('REG_DATA_GLOVE_BOX_WATER_CONTENT').read(2, word_order=WorderOrder.LITTLE) + return value + +# @property +# def data_stack_vision_code(self) -> int: +# """物料堆叠复检图片编码 (INT16)""" +# if self.debug_mode: +# return 0 +# code, read_err = self.client.use_node('REG_DATA_STACK_VISON_CODE').read(1) +# #code, _ = self.client.use_node('REG_DATA_STACK_VISON_CODE').read(1).type +# print(f"读取物料堆叠复检图片编码", {code}, "error", type(code)) +# #print(code.type) +# # print(read_err) +# return int(code) + + def func_pack_device_init(self): + #切换手动模式 + print("切换手动模式") + self._sys_hand_cmd(True) + time.sleep(1) + while (self._sys_hand_status()) == False: + print("waiting for hand_cmd") + time.sleep(1) + #设备初始化 + self._sys_init_cmd(True) + time.sleep(1) + #sys_init_status为bool值,不加括号 + while (self._sys_init_status())== False: + print("waiting for init_cmd") + time.sleep(1) + #手动按钮置回False + self._sys_hand_cmd(False) + time.sleep(1) + while (self._sys_hand_cmd()) == True: + print("waiting for hand_cmd to False") + time.sleep(1) + #初始化命令置回False + self._sys_init_cmd(False) + time.sleep(1) + while (self._sys_init_cmd()) == True: + print("waiting for init_cmd to False") + time.sleep(1) + + def func_pack_device_auto(self): + #切换自动 + print("切换自动模式") + self._sys_auto_cmd(True) + time.sleep(1) + while (self._sys_auto_status()) == False: + print("waiting for auto_status") + time.sleep(1) + #自动按钮置False + self._sys_auto_cmd(False) + time.sleep(1) + while (self._sys_auto_cmd()) == True: + print("waiting for auto_cmd") + time.sleep(1) + + def func_pack_device_start(self): + #切换自动 + print("启动") + self._sys_start_cmd(True) + time.sleep(1) + while (self._sys_start_status()) == False: + print("waiting for start_status") + time.sleep(1) + #自动按钮置False + self._sys_start_cmd(False) + time.sleep(1) + while (self._sys_start_cmd()) == True: + print("waiting for start_cmd") + time.sleep(1) + + def func_pack_send_bottle_num(self, bottle_num): + bottle_num = int(bottle_num) + #发送电解液平台数 + print("启动") + while (self._unilab_rece_electrolyte_bottle_num()) == False: + print("waiting for rece_electrolyte_bottle_num to True") + # self.client.use_node('8520').write(True) + time.sleep(1) + #发送电解液瓶数为2 + self._reg_msg_electrolyte_num(bottle_num) + time.sleep(1) + #完成信号置True + self._unilab_send_electrolyte_bottle_num(True) + time.sleep(1) + #检测到依华已接收 + while (self._unilab_rece_electrolyte_bottle_num()) == True: + print("waiting for rece_electrolyte_bottle_num to False") + time.sleep(1) + #完成信号置False + self._unilab_send_electrolyte_bottle_num(False) + time.sleep(1) + #自动按钮置False + + + # 下发参数 + #def func_pack_send_msg_cmd(self, elec_num: int, elec_use_num: int, elec_vol: float, assembly_type: int, assembly_pressure: int) -> bool: + # """UNILAB写参数""" + # while (self.request_rec_msg_status) == False: + # print("wait for res_msg") + # time.sleep(1) + # self.success = False + # self._unilab_send_msg_electrolyte_num(elec_num) + # time.sleep(1) + # self._unilab_send_msg_electrolyte_use_num(elec_use_num) + # time.sleep(1) + # self._unilab_send_msg_electrolyte_vol(elec_vol) + # time.sleep(1) + # self._unilab_send_msg_assembly_type(assembly_type) + # time.sleep(1) + # self._unilab_send_msg_assembly_pressure(assembly_pressure) + # time.sleep(1) + # self._unilab_send_msg_succ_cmd(True) + # time.sleep(1) + # self._unilab_send_msg_succ_cmd(False) + # #将允许读取标志位置True + # self.allow_data_read = True + # self.success = True + # return self.success + + def func_pack_send_msg_cmd(self, elec_use_num, elec_vol, assembly_type, assembly_pressure) -> bool: + """UNILAB写参数""" + while (self.request_rec_msg_status) == False: + print("wait for request_rec_msg_status to True") + time.sleep(1) + self.success = False + #self._unilab_send_msg_electrolyte_num(elec_num) + #设置平行样数目 + self._unilab_send_msg_electrolyte_use_num(elec_use_num) + time.sleep(1) + #发送电解液加注量 + self._unilab_send_msg_electrolyte_vol(elec_vol) + time.sleep(1) + #发送电解液组装类型 + self._unilab_send_msg_assembly_type(assembly_type) + time.sleep(1) + #发送电池压制力 + self._unilab_send_msg_assembly_pressure(assembly_pressure) + time.sleep(1) + self._unilab_send_msg_succ_cmd(True) + time.sleep(1) + while (self.request_rec_msg_status) == True: + print("wait for request_rec_msg_status to False") + time.sleep(1) + self._unilab_send_msg_succ_cmd(False) + #将允许读取标志位置True + self.allow_data_read = True + self.success = True + return self.success + + def func_pack_get_msg_cmd(self, file_path: str="D:\\coin_cell_data") -> bool: + """UNILAB读参数""" + while self.request_send_msg_status == False: + print("waiting for send_read_msg_status to True") + time.sleep(1) + data_open_circuit_voltage = self.data_open_circuit_voltage + data_pole_weight = self.data_pole_weight + data_assembly_time = self.data_assembly_time + data_assembly_pressure = self.data_assembly_pressure + data_electrolyte_volume = self.data_electrolyte_volume + data_coin_num = self.data_coin_num + data_electrolyte_code = self.data_electrolyte_code + data_coin_cell_code = self.data_coin_cell_code + print("data_open_circuit_voltage", data_open_circuit_voltage) + print("data_pole_weight", data_pole_weight) + print("data_assembly_time", data_assembly_time) + print("data_assembly_pressure", data_assembly_pressure) + print("data_electrolyte_volume", data_electrolyte_volume) + print("data_coin_num", data_coin_num) + print("data_electrolyte_code", data_electrolyte_code) + print("data_coin_cell_code", data_coin_cell_code) + #接收完信息后,读取完毕标志位置True + liaopan3 = self.deck.get_resource("\u7535\u6c60\u6599\u76d8") + #把物料解绑后放到另一盘上 + battery = ElectrodeSheet(name=f"battery_{self.coin_num_N}", size_x=14, size_y=14, size_z=2) + battery._unilabos_state = { + "electrolyte_name": data_coin_cell_code, + "data_electrolyte_code": data_electrolyte_code, + "open_circuit_voltage": data_open_circuit_voltage, + "assembly_pressure": data_assembly_pressure, + "electrolyte_volume": data_electrolyte_volume + } + liaopan3.children[self.coin_num_N].assign_child_resource(battery, location=None) + #print(jipian2.parent) + ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ + "resources": [self.deck] + }) + + + self._unilab_rec_msg_succ_cmd(True) + time.sleep(1) + #等待允许读取标志位置False + while self.request_send_msg_status == True: + print("waiting for send_msg_status to False") + time.sleep(1) + self._unilab_rec_msg_succ_cmd(False) + time.sleep(1) + #将允许读取标志位置True + time_date = datetime.now().strftime("%Y%m%d") + #秒级时间戳用于标记每一行电池数据 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + #生成输出文件的变量 + self.csv_export_file = os.path.join(file_path, f"date_{time_date}.csv") + #将数据存入csv文件 + if not os.path.exists(self.csv_export_file): + #创建一个表头 + with open(self.csv_export_file, 'w', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow([ + 'Time', 'open_circuit_voltage', 'pole_weight', + 'assembly_time', 'assembly_pressure', 'electrolyte_volume', + 'coin_num', 'electrolyte_code', 'coin_cell_code' + ]) + #立刻写入磁盘 + csvfile.flush() + #开始追加电池信息 + with open(self.csv_export_file, 'a', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow([ + timestamp, data_open_circuit_voltage, data_pole_weight, + data_assembly_time, data_assembly_pressure, data_electrolyte_volume, + data_coin_num, data_electrolyte_code, data_coin_cell_code + ]) + #立刻写入磁盘 + csvfile.flush() + self.success = True + return self.success + + + + def func_pack_send_finished_cmd(self) -> bool: + """UNILAB写参数""" + while (self._unilab_rece_finished_cmd()) == False: + print("wait for rece_finished_cmd to True") + time.sleep(1) + self.success = False + self._unilab_send_finished_cmd(True) + time.sleep(1) + while (self._unilab_rece_finished_cmd()) == True: + print("wait for rece_finished_cmd to False") + time.sleep(1) + self._unilab_send_finished_cmd(False) + #将允许读取标志位置True + self.success = True + return self.success + + + + def func_allpack_cmd(self, elec_num, elec_use_num, elec_vol:int=50, assembly_type:int=7, assembly_pressure:int=4200, file_path: str="D:\\coin_cell_data") -> bool: + elec_num, elec_use_num, elec_vol, assembly_type, assembly_pressure = int(elec_num), int(elec_use_num), int(elec_vol), int(assembly_type), int(assembly_pressure) + summary_csv_file = os.path.join(file_path, "duandian.csv") + # 如果断点文件存在,先读取之前的进度 + if os.path.exists(summary_csv_file): + read_status_flag = True + with open(summary_csv_file, 'r', newline='', encoding='utf-8') as csvfile: + reader = csv.reader(csvfile) + header = next(reader) # 跳过标题行 + data_row = next(reader) # 读取数据行 + if len(data_row) >= 2: + elec_num_r = int(data_row[0]) + elec_use_num_r = int(data_row[1]) + elec_num_N = int(data_row[2]) + elec_use_num_N = int(data_row[3]) + coin_num_N = int(data_row[4]) + if elec_num_r == elec_num and elec_use_num_r == elec_use_num: + print("断点文件与当前任务匹配,继续") + else: + print("断点文件中elec_num、elec_use_num与当前任务不匹配,请检查任务下发参数或修改断点文件") + return False + print(f"从断点文件读取进度: elec_num_N={elec_num_N}, elec_use_num_N={elec_use_num_N}, coin_num_N={coin_num_N}") + + else: + read_status_flag = False + print("未找到断点文件,从头开始") + elec_num_N = 0 + elec_use_num_N = 0 + coin_num_N = 0 + for i in range(20): + print(f"剩余电解液瓶数: {elec_num}, 已组装电池数: {elec_use_num}") + print(f"剩余电解液瓶数: {type(elec_num)}, 已组装电池数: {type(elec_use_num)}") + print(f"剩余电解液瓶数: {type(int(elec_num))}, 已组装电池数: {type(int(elec_use_num))}") + + #如果是第一次运行,则进行初始化、切换自动、启动, 如果是断点重启则跳过。 + if read_status_flag == False: + pass + #初始化 + #self.func_pack_device_init() + #切换自动 + #self.func_pack_device_auto() + #启动,小车收回 + #self.func_pack_device_start() + #发送电解液瓶数量,启动搬运,多搬运没事 + #self.func_pack_send_bottle_num(elec_num) + last_i = elec_num_N + last_j = elec_use_num_N + for i in range(last_i, elec_num): + print(f"开始第{last_i+i+1}瓶电解液的组装") + #第一个循环从上次断点继续,后续循环从0开始 + j_start = last_j if i == last_i else 0 + self.func_pack_send_msg_cmd(elec_use_num-j_start, elec_vol, assembly_type, assembly_pressure) + + for j in range(j_start, elec_use_num): + print(f"开始第{last_i+i+1}瓶电解液的第{j+j_start+1}个电池组装") + #读取电池组装数据并存入csv + self.func_pack_get_msg_cmd(file_path) + time.sleep(1) + # TODO:读完再将电池数加一还是进入循环就将电池数加一需要考虑 + + + + # 生成断点文件 + # 生成包含elec_num_N、coin_num_N、timestamp的CSV文件 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + with open(summary_csv_file, 'w', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow(['elec_num','elec_use_num', 'elec_num_N', 'elec_use_num_N', 'coin_num_N', 'timestamp']) + writer.writerow([elec_num, elec_use_num, elec_num_N, elec_use_num_N, coin_num_N, timestamp]) + csvfile.flush() + coin_num_N += 1 + self.coin_num_N = coin_num_N + elec_use_num_N += 1 + elec_num_N += 1 + elec_use_num_N = 0 + + #循环正常结束,则删除断点文件 + os.remove(summary_csv_file) + #全部完成后等待依华发送完成信号 + self.func_pack_send_finished_cmd() + + + def func_pack_device_stop(self) -> bool: + """打包指令:设备停止""" + for i in range(3): + time.sleep(2) + print(f"输出{i}") + #print("_sys_hand_cmd", self._sys_hand_cmd()) + #time.sleep(1) + #print("_sys_hand_status", self._sys_hand_status()) + #time.sleep(1) + #print("_sys_init_cmd", self._sys_init_cmd()) + #time.sleep(1) + #print("_sys_init_status", self._sys_init_status()) + #time.sleep(1) + #print("_sys_auto_status", self._sys_auto_status()) + #time.sleep(1) + #print("data_axis_y_pos", self.data_axis_y_pos) + #time.sleep(1) + #self.success = False + #with open('action_device_stop.json', 'r', encoding='utf-8') as f: + # action_json = json.load(f) + #self.client.execute_procedure_from_json(action_json) + #self.success = True + #return self.success + + def fun_wuliao_test(self) -> bool: + #找到data_init中构建的2个物料盘 + liaopan3 = self.deck.get_resource("\u7535\u6c60\u6599\u76d8") + for i in range(16): + battery = ElectrodeSheet(name=f"battery_{i}", size_x=16, size_y=16, size_z=2) + battery._unilabos_state = { + "diameter": 20.0, + "height": 20.0, + "assembly_pressure": i, + "electrolyte_volume": 20.0, + "electrolyte_name": f"DP{i}" + } + liaopan3.children[i].assign_child_resource(battery, location=None) + + ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ + "resources": [self.deck] + }) + # for i in range(40): + # print(f"fun_wuliao_test 运行结束{i}") + # time.sleep(1) + # time.sleep(40) + # 数据读取与输出 + def func_read_data_and_output(self, file_path: str="D:\\coin_cell_data"): + # 检查CSV导出是否正在运行,已运行则跳出,防止同时启动两个while循环 + if self.csv_export_running: + return False, "读取已在运行中" + + #若不存在该目录则创建 + if not os.path.exists(file_path): + os.makedirs(file_path) + print(f"创建目录: {file_path}") + + # 只要允许读取标志位为true,就持续运行该函数,直到触发停止条件 + while self.allow_data_read: + + #函数运行标志位,确保只同时启动一个导出函数 + self.csv_export_running = True + + #等待接收结果标志位置True + while self.request_send_msg_status == False: + print("waiting for send_msg_status to True") + time.sleep(1) + #日期时间戳用于按天存放csv文件 + time_date = datetime.now().strftime("%Y%m%d") + #秒级时间戳用于标记每一行电池数据 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + #生成输出文件的变量 + self.csv_export_file = os.path.join(file_path, f"date_{time_date}.csv") + + #接收信息 + data_open_circuit_voltage = self.data_open_circuit_voltage + data_pole_weight = self.data_pole_weight + data_assembly_time = self.data_assembly_time + data_assembly_pressure = self.data_assembly_pressure + data_electrolyte_volume = self.data_electrolyte_volume + data_coin_num = self.data_coin_num + data_electrolyte_code = self.data_electrolyte_code + data_coin_cell_code = self.data_coin_cell_code + # 电解液瓶位置 + elec_bottle_site = 2 + # 极片夹取位置(应当通过寄存器读光标) + Pos_elec_site = 0 + Al_elec_site = 0 + Gasket_site = 0 + + #接收完信息后,读取完毕标志位置True + self._unilab_rec_msg_succ_cmd()# = True + #等待允许读取标志位置False + while self.request_send_msg_status == True: + print("waiting for send_msg_status to False") + time.sleep(1) + self._unilab_rec_msg_succ_cmd()# = False + + #此处操作物料信息(如果中途报错停止,如何) + #报错怎么办(加个判断标志位,如果发生错误,则根据停止位置扣除物料) + #根据物料光标判断取哪个物料(人工摆盘,电解液瓶,移液枪头都有光标位置,寄存器读即可) + + #物料读取操作写在这里 + #在这里进行物料调取 + #转移物料瓶,elec_bottle_site对应第几瓶电解液(从依华寄存器读取) + # transfer_bottle(deck, elec_bottle_site) + # #找到电解液瓶的对象 + # electrolyte_rack = deck.get_resource("electrolyte_rack") + # pending_positions = electrolyte_rack.get_pending_positions()[elec_bottle_site] + # # TODO: 瓶子取液体操作需要加入 +# +# + # #找到压制工站对应的对象 + # battery_press_slot = deck.get_resource("battery_press_1") + # #创建一个新电池 + # test_battery = Battery( + # name=f"test_battery_{data_coin_num}", + # diameter=20.0, # 与压制槽直径匹配 + # height=3.0, # 电池高度 + # max_volume=100.0, # 100μL容量 + # barcode=data_coin_cell_code, # 电池条码 + # ) + # if battery_press_slot.has_battery(): + # return False, "压制工站已有电池,无法放置新电池" + # #在压制位放置电池 + # battery_press_slot.place_battery(test_battery) + # #从第一个子弹夹中取料 + # clip_magazine_1_hole = self.deck.get_resource("clip_magazine_1").get_item(Pos_elec_site) + # clip_magazine_2_hole = self.deck.get_resource("clip_magazine_2").get_item(Al_elec_site) + # clip_magazine_3_hole = self.deck.get_resource("clip_magazine_3").get_item(Gasket_site) + # + # if clip_magazine_1_hole.get_sheet_count() > 0: # 检查洞位是否有极片 + # electrode_sheet_1 = clip_magazine_1_hole.take_sheet() # 从洞位取出极片 + # test_battery.add_electrode_sheet(electrode_sheet_1) # 添加到电池中 + # print(f"已将极片 {electrode_sheet_1.name} 从子弹夹转移到电池") + # else: + # print("子弹夹洞位0没有极片") +# + # if clip_magazine_2_hole.get_sheet_count() > 0: # 检查洞位是否有极片 + # electrode_sheet_2 = clip_magazine_2_hole.take_sheet() # 从洞位取出极片 + # test_battery.add_electrode_sheet(electrode_sheet_2) # 添加到电池中 + # print(f"已将极片 {electrode_sheet_2.name} 从子弹夹转移到电池") + # else: + # print("子弹夹洞位0没有极片") +# + # if clip_magazine_3_hole.get_sheet_count() > 0: # 检查洞位是否有极片 + # electrode_sheet_3 = clip_magazine_3_hole.take_sheet() # 从洞位取出极片 + # test_battery.add_electrode_sheet(electrode_sheet_3) # 添加到电池中 + # print(f"已将极片 {electrode_sheet_3.name} 从子弹夹转移到电池") + # else: + # print("子弹夹洞位0没有极片") + # + # # TODO:#把电解液从瓶中取到电池夹子中 + # battery_site = deck.get_resource("battery_press_1") + # clip_magazine_battery = deck.get_resource("clip_magazine_battery") + # if battery_site.has_battery(): + # battery = battery_site.take_battery() #从压制槽取出电池 + # clip_magazine_battery.add_battery(battery) #从压制槽取出电池 +# +# +# +# + # # 保存配置到文件 + # self.deck.save("button_battery_station_layout.json", indent=2) + # print("\n台面配置已保存到: button_battery_station_layout.json") + # + # # 保存状态到文件 + # self.deck.save_state_to_file("button_battery_station_state.json", indent=2) + # print("台面状态已保存到: button_battery_station_state.json") + + + + + + + #将数据写入csv中 + #如当前目录下无同名文件则新建一个csv用于存放数据 + if not os.path.exists(self.csv_export_file): + #创建一个表头 + with open(self.csv_export_file, 'w', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow([ + 'Time', 'open_circuit_voltage', 'pole_weight', + 'assembly_time', 'assembly_pressure', 'electrolyte_volume', + 'coin_num', 'electrolyte_code', 'coin_cell_code' + ]) + #立刻写入磁盘 + csvfile.flush() + #开始追加电池信息 + with open(self.csv_export_file, 'a', newline='', encoding='utf-8') as csvfile: + writer = csv.writer(csvfile) + writer.writerow([ + timestamp, data_open_circuit_voltage, data_pole_weight, + data_assembly_time, data_assembly_pressure, data_electrolyte_volume, + data_coin_num, data_electrolyte_code, data_coin_cell_code + ]) + #立刻写入磁盘 + csvfile.flush() + + # 只要不在自动模式运行中,就将允许标志位置False + if self.sys_auto_status == False or self.sys_start_status == False: + self.allow_data_read = False + self.csv_export_running = False + time.sleep(1) + + def func_stop_read_data(self): + """停止CSV导出""" + if not self.csv_export_running: + return False, "read data未在运行" + + self.csv_export_running = False + self.allow_data_read = False + + if self.csv_export_thread and self.csv_export_thread.is_alive(): + self.csv_export_thread.join(timeout=5) + + def func_get_csv_export_status(self): + """获取CSV导出状态""" + return { + 'allow_read': self.allow_data_read, + 'running': self.csv_export_running, + 'thread_alive': self.csv_export_thread.is_alive() if self.csv_export_thread else False + } + + + ''' + # ===================== 物料管理区 ====================== + @property + def data_material_inventory(self) -> int: + """主物料库存 (数量, INT16)""" + inventory, read_err = self.client.use_node('REG_DATA_MATERIAL_INVENTORY').read(1) + return inventory + + @property + def data_tips_inventory(self) -> int: + """移液枪头库存 (数量, INT16)""" + inventory, read_err = self.client.register_node_list(self.nodes).use_node('REG_DATA_TIPS_INVENTORY').read(1) + return inventory + + ''' + + +if __name__ == "__main__": + # 简单测试 + workstation = CoinCellAssemblyWorkstation() + print(f"工作站创建成功: {workstation.deck.name}") + print(f"料盘数量: {len(workstation.deck.children)}") From 0fba4cf2752853f403da4ee55a7d4faddd6deaa1 Mon Sep 17 00:00:00 2001 From: calvincao Date: Thu, 30 Oct 2025 19:56:34 +0800 Subject: [PATCH 034/104] =?UTF-8?q?feat(unilabos):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E9=85=8D=E7=BD=AE=E5=92=8C=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改了 bioyond_cell.yaml 中的 xlsx_path 路径分隔符为反斜杠- 在 bioyond_cell.yaml 中新增多个自动命令定义,包括创建物料、处理报告和调度重置等功能- 修改 coin_cell_assembly.py 中 func_pack_send_msg_cmd 函数签名并调整调用参数 - 新增 qiming_coin_cell_code 方法用于设置启明扣电配置参数 - 更新 coin_cell_assembly_a.csv 文件中的寄存器描述和新增压制模式及清洁忽略选项- 修改 bioyond_studio 配置文件中的默认 API 主机地址 - 更新 new_cellconfig3c.json 中的设备类名为 coincellassemblyworkstation_device- 删除 reaction_station_bioyond.yaml 的全部内容,仅保留空对象 -重新组织 YB_bottle.yaml 和 YB_bottle_carriers.yaml 中的资源分类和命名定义 --- .../bioyond_cell/2025092701.xlsx | Bin 9287 -> 18159 bytes .../bioyond_cell/样品导入模板.xlsx | Bin 9686 -> 22129 bytes .../workstation/bioyond_studio/config.py | 2 +- .../coin_cell_assembly/coin_cell_assembly.py | 27 +- .../coin_cell_assembly_a.csv | 5 +- .../coin_cell_assembly/new_cellconfig3c.json | 2 +- unilabos/registry/devices/bioyond_cell.yaml | 216 +- .../devices/coin_cell_workstation.yaml | 576 ++ .../devices/reaction_station_bioyond.yaml | 528 +- unilabos/registry/devices/work_station.yaml | 6698 ----------------- .../registry/resources/bioyond/YB_bottle.yaml | 106 +- .../resources/bioyond/YB_bottle_carriers.yaml | 218 +- 12 files changed, 977 insertions(+), 7401 deletions(-) create mode 100644 unilabos/registry/devices/coin_cell_workstation.yaml delete mode 100644 unilabos/registry/devices/work_station.yaml diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx index 3ce894f1e5033c9a9bbfc0b61024bce1ce3fbf49..d1e9d2f31a2385b1028209891e31c975ebc9744b 100644 GIT binary patch literal 18159 zcmeGkS!^Un)q#M}0)hyT5CW++5(tFso*5szHn!1tj(B%%@5D3f-2`#ebl1%Ey03Ki z_~4M1grJ2@2uC&{B3yzfK|ppPCuI5Mmp}O9C$XI+pa{ed5+A%*)mL@*jE^0GfOs`K z-Bs_sdiAb)bxr;B8*hF0NbvXLTfX$dvu_v~!O#1ka7&jz?NHs7H!WwqWm)TETZTS{ zC9@xgCuxH^u4b8Yxx!dJCs9+eRL$(n<<2&1qbG8b>k(5Wx@FS2+%|P{AD@5wvDsCs z6AzHwu4cOuP%+)PT-WpL8CiCfE;WcdX4%w)nzrQ_#DiC-Bilq-Cmkvm^Z6+mIEZNE z<^`J4Np&vgoKy=rskv=K%lp%|+HFmt6-((E)bui%VEuQylzKpvI34QE+BI#Zq+!$Vs{rPx4g1TO0Z91J+*l3|PF@3xe(q{m=mxilW ztL17Tq{je=qCihMFL9a^wG)MffMyJcKc;Dy^ON}r5k5wLJjL_$!otGjw9gAC%7FOe zL<;^yerlpr6yO;Uho4NrPb`$mWj4CosZjL11{Ifuh&c8MlnkS@^9AYJvk0^rIv(X-$d zs3^QO2ZBcy;O)H&0Vg<=Cq0iC zStOeTvYnV>mWbEQVzROAC~>%|>w%^_RJWw6N?kV#=ZpjWjnO_$AYNsveOcc&L`cWe z*0X3X5zC50R;=}Mml$>yY(q1J}s$X(wH_L=VAt&;v31s(VetOfrfa z`v-!vT1Q|hG7zlkIT>u-PA$QH8{2xDP!?q%qzjSS(9A($Th}0N^A=tY+jF@R3}RW% zvzQ3h{owu!Lv)4o%sK_HNL&Bw5&HEAo%^4U(EWUQ_;EQ3#So=2A>p|;%e;-giFaGm z^#;z#mt2;6yD$K$H7GKZlgZT|(8k7( z`$VB{M`^5pOdYtj0%MH@MWMB@%P3+tF9(r_U4z0x!m+*aWW?<#GcEwVWta@h7C${F&89y2Np#Y&*4_PuP-tu zje0ztZB+$Wyy)XFf*ae8>s5%`{FLUvbEY7UJ1EOvmkzzQu?gYaP^g?&481HZY?aT?66 zVIt_QY8}bZU^wx*4yAPnOU(NAAy)8*9!^sC2{DU`vFqBL?H1jjdJ|*d6vp73)CE&# z(Zmk}3@&MZ;=Aubtq$fWaeoscSvYiZANY4CF5H=+Q;A#x?KhBr$Rd-Ln7D9!h8~QO z@s40clqF3BULg#A)6;%aM`Yn318{7tWG?Kh05e z%FGxC)TXrxu~mZY2QEJeta=oq8dwkpNdDA=W{d+p! zopNn>V}4-Q9cEoEOWpP#b(asg-0E-+gOIwpO52jAZUys?P!imQ_zp>sA~R@2LqiJ@ z$Fn94BBBqEsC={xk&F8yhZwA72-zVrTTsg)0Q+Np;Kr#P!1Ex>dyUQ&NJ=+vMq~$I zK3im&GoOQsj87B?D(VO#^R@Cm4f<1RUeD8p8PzgXRdjSGF77c)F=n7n{HC|Uz=fqlWM1IB}p0rVm) zn4g7F2vU8s1}`|c;WLVGE@#5w7S;t0w+}RoTiXSgHnMgZVHq)?-InF3aF`~p2wpTl ztJ5}`Eo60F{Prvx{%={HXBqIIYNTVCM2AN?!0;b{tU~?l>U>+Ez15B>7{*@|0_qwc zfg%5q)8j8lKTeI)zG<@3R@-lk*tWjC(lc5VHjwe$kFQ(SDzp>pjL?;kDgWfd3L?YS zR7kkh({xC)GP(gY)vZV{Y>4+rOQ$R$4O;=BsC+PMhLv-<=y{o{TF;mcq3YU(#fP-H zT=dMLCYbJ{ppXbc@4r#}t#WK+1V8WPK`8T_(b3~kNN#B12=a-eMWf^?E)}-JO*~H~ zIkXMiEQOiU!Z^x{MMBs!+XRJ$&r)zE4J#t$hV=n#1x^To_gI^lWMY6*@Sz09!@$B| zGZIQR=J%d@WcTSSyI=X$?$3TWD{r8!`+y4~;Enqp*nRj{Co3XQSp?d-^qrj-f4%$2 z^I`WRB5W-I z`;{NNB2SOg*M>)!rl{CMWowBq*s1(b?5&3_Cs0}k)WBKtJim*z5L46 zEB#wKxgQdUVE4*3k-bwQ(vnuMO?|kN==kX*P^ETeAqBN0LRFfT6v(m&(kP`MRz!#; zt=2f10y>ifs-G#=iYc(V2v#kpAnp($*3uBGB1CB=-KmBMve+!Iq#A09FqP^`H3f54 zglR4}>M5AD6iiwV=R}xln#y?*qLLw*J4KM{a#}QZi6D)1uS zHU`!<2iBe)Si3f`_T0eQ^8;(|99VnTz}gZVYm5&^2{n`wHXYfA;3haD=WYV0kz?a; zwws7BaW_dAXIX^Ib`ue1fSZVbDK`%LR&pE*h(rh76N%|(E{=Th(i=xc z@beCyn8puVA3Yxps0=L~-O?SH$RsjSBRGUNrogO;pZAxO)9k`=+0m3PKT^)8+FNS+ z==MQrsoO9u)fD2uq%SvrOp<1UzOvbOY^n!!iCTXybAG8(s;}lGn5{N?whdbZR9y@2 zT=?5die+yz?b>jXo0fD~U-nloC&b-`}fqhe79&-MZ#oQN&M0epOsS$}1r!>p*?^Lh?N zFcfp5o6#x+mq=?JahPzxf^o~he+6klfz)%fx!mVkYO8fZQI*j)DbUf0=_xu&PD~yj z9aqNN<9Q`toL0uaz!bn~0Wzp{##c`X)q{&76iyDpY20>&tBrN34U*A`?c#1c&JLsI zWoSQmoyC`sJD7$uwqa#K=qp0A`FPm*0o`>JjE*2wf2W5v98nHLjWV1+HXF)0;k18S ze%qrLZy6cEkMFd>r0M1swE`+bbK0TJAp_3p4)~W!SJ?H5W><61`8Sxt`D8FZ^lfnG zv0v{#dbJ?!Jazx>rLXU@2^8F3;`19mSiE-q{+C~TeCNTh-njQknCt93aAoiE&-ko~ z13-Yqg+82&qGQcy@W5^&;o=Fa3PLR2rAjF4)P>vru>|+7@bgxPu>9iHf5~Ml$PM^< zSR(Eo>LOQY#L=vf4{b0w{Jay4E|jm|nWK;Ev5?A!($tWgVH#qu z_%*0Z*$;gkyabDnFjV8c#Q6sL@TtbE950+*gcW%>gkC2waKeg_H?skIT(Au7YM+JS z5A451T*?)^LEwNe+rLLJ{=!gn8`hiVF%t1$0CN>@h8MEOIBfh`o0!LWp*I5$A|MuW zMxa7uv8j1*L+?zxP8{5X3juwO!5kxw84tJ+7O*pnXqf696BzKB;7pE8B@q&T|IN?e zb{C9Y__-}^ofsc(ZUj^886*-l&4;oX1&(taGKK|7PC9CFGgXz4q}_9LcAHgIWa{7E zlM7=78J4T@BJ7N+z}A8hU5Je%u$ma8p?UH9t#MFQ;guoumJ6AZ->=Qh&9TjK#s*}N z1^N8)65Adlqne2~b`{*v5U)x;`G*S^-}9D{5&Ym)$xoDFzW^@eeHsnk{p8K5g3&m5 zCLvq|RyZG{CAwXy30wvNU%3^IS8LYJdv>$ z5`YZ67T)mei??B_V}?6Qw;3uMw6Ne8)v6JwW?BEr|b~!0;HsypYk~hYz-|9{)Lkn_+%7I!0>t)&b%}6#QFCNr37r5KMazjl3Y)XsJt)FsKm9EL{6^jM99Az4_OF)t|f__TxW=oKR+p~IwTR3bHxbp yugB9f$0T+#myfU?oQYuv&X`0`W}p%JduKEA(fa8(y%~Xy!N1ReQGBX`Z~p=i!+J&l literal 9287 zcmeHtg;yNe_I2Z~jXMOF-~k%f1b2eFGz1IonxG*#1P$&D!3pk8fDj-!1lPuc1%5r5 znQvw?@B0hhyS@6})vIcsRb6%V+2@{9YKjPm1OQ|JDgXeW1(+XYfsNq+fH*_|00=;Z zH;{I6bhC1FGtu&PwsJLk?B!rjlZOb;oCAP|o&Ue_Uwj8Dl15Z|xNv2^Kf9CO;80ks zkwE1?3>m;@RTt~*P3$kTFvzyGeaMcy#g)n>v=gYtA7Aw4IT-`jI65?hMD(|4U`7WI zc4`}w3vl)g958eclP0+6>p^n}$R)T*O-y2}GXU>iHuh-n$*f5#RFHuQiNu1|v*rhM z@K*XiVwUNb2n{ZDt*YoO;|UIzPT{Uk0>9YVviKUGt#imDM$`E&5_uRGs?q9dvY2ap zbh$}YYa=ZS=X? zP4g2%unC7jwnQ*($mg#BwHMKKL=YawasOU%!hEHX<8GRzTZT0tMeho^Wpj3HoTUm0 zodf%7;Xt;Gs*nI{NI>!6P?KP%>Y)XNpJUM^$_AqB)dRsL-fr_*rxfl`CoR2y_~Gi{ zuCVXH#HK;$(Mif~AkRHA0PygD08sk}E$g+p9-YH{O$o+23>Ymd1o_wi;i2_%PEEM5s zU|Yc5@WPTv%-$gF+t{Ncx; zk~yeB$A^E#GyZgdA?G4czmD%@B+I*30IjEd@OUUBn!02ercnPTiT0KXE-nH9-~yu% z4(!f&*>ie2xx6rUa(eNbZWU-ts(z6mXwSH!PXCzd(L7SaM*xq(jK++YJi=(%kW~nr zPXqBsad0<{}dz4-gGT7D&uPJE|B2p_IZw;6xDyu0cVM`8B?SDDs4gybpcA0f+>rFh@8&G@4eMQob>*bd8ivuZ6yRo>b?M z0<-10Tn`OIicO3jngCw6Srp+Dowi16Gf|kO)ANWfDAGWy!OuIF>g9`p=Y!Ln%$26L z%pTihhpQ5ZY(v{ndOzf?t)CO1Z@v_hrVrg;WQSKCWlUF4;d4N(lnnV1SIOE4--3sM z+vpxHHkej0p-IDm3S>v~|Ey6iGobT05x{@9HSBVRJSA|Zb4F!z$zwa$IGZkHgJz31 z{YCsMX5wm6b;SKoGTB?KpEN}!E(>* z>sI}314#Gk`jaMHq-#7h7Z13 zjzMN^_LD_X@<#O|F-@1 zjmMXdZlhoJDtW0QRf6p85j%KA4hcTF6YY}JZBL8nY6P^%&sk%>d1}So?#Q@vW#}O#<->fS z$LZ!2biIdqW%(jo{QBb9AG)dzohzRzrVa=Y-=jQA#aJ~leGAQ#8BAz5V@{KW`vGfZK~0H|;<^879`f2Yu&f&&N3|6mmS z?;aHz%8I>QxNR8s;ar{>9)x&vZk%)n+WR=DLv<_*jI_J~=PTr_%|^O2ikt{eVcyWO z0gvze*xwL<7hP;6QTT{HKzpb#lGE7P7y_#KA%rUADH0I>U~j+t5Cc2i75GssVURtW zKyd4dnSnGszlcue{Nv{^KD$p`?mTv`6O_ZSfICvR5p?rP^8gZ3*Vsst&SJg!@KJi< z68U|IH}&KhDVn?pmN#H>=L;>{RhR4~>S^npwX*l87hU-`n41*kid1F%qJCj+G~t6! zsRLOOTsQO5TC|~SZc?|yEFBA{>?7w;>y4p-=@rqx!s4v~1^_?;JL8Xd z>k78Aa&zVUxp4m$crz1?oEB&Z!k|ot;xtDV_5v3=Y%0?nWiu_dOEWJ*Vl4=`s8;>J z^MNaH+xZ$kc+)j-2;1J{+*LN;g&~8;6^TXV;FN+S2__|u7H!$UAAXsBMZMn3(}ro- ztj#D-q1)8rqYeF)Mi{GT_@L*LGq>%Qqme{>o{_t`J<0vLW1{WzgsyE!#sf4E-RvTy zDMJr8`lL&;Fk3PfQ?uTj+_Oi$A>XLZJRt{5@kM?7f@6n1J51d>RCtP|1uIXP8T6A> z%RlM1Yw3P}oZ%wrK?J3EpOLSW*`fAXLw8+i9Y|lxy*+XY{Ytt1O{TfxfmP{=710wO zidE9&4$i`Z%iiJf4;h}_Q9yRT#|GFnl{+iv&dMLgr4q9<*5}1R@2Bxq1+`?^`3U_7 zhr>CAtj_fKCY;a=zS}8@n23eKuffUkD1?Zo_E+@uGi55^?S2aj+UOp^@v^cPJ27K9kR$kViZ0(*a+jHfpq&?G)HEG6#7kVbwL9vA$Z-JRD|E4RzSN9=_ppTDZ8QW6*?Duf-(*ZK4 zM^r-92wtx}rxgiGlAfHWLF0h5fuY|?B@^D3u@vkr_`Xq|dx3S?NqW2~aU~=yvsukt z##de=2U!aECvPB@{Hzq8=L(nrduWm*3Xxa*>ko%=*h}fkP2>C7E|`V14;v^rT1z* z@nIGcH;zDuO|71^Fjp*Mvd8>}woNn6`D;2oSz*3dIAn}y%&M67pK_Kl0(sY_#dD~C z-oBrc;oC9vZZ6zcCJYA%x|(>B=~8S|BB&wl5-%DZCal-3fB5=p=Awum`XTVvra^~t zzn%qaE2?95>v0A45tuXMD>~3Hy2P@8>^G}{1=qWmp3*fdB z?*!6ZX6x243_PJjNnvMj_}2R&nLtRR<;!D>BNs`H26wgui(q)!sk~J9whOg7tT;eh zK}Wws)DxsrVi5iF@B=Qwd&w<^M^M`&GL4HRMJFT z5{;$PQ6)vs_{L1LWiSLHnA#D|9(o8b`3#K%oJm58bW&Ky<(vwavyUr+4mR1LDiFOF zO;_j-g`{cE7Y;pK>*M-(_-TCS-gQ-dNg*eX#$FC$Zx;%6suKWZS)n*d5Os)zLXGr{ z9Gkol6iCJ{ttaMl+l;%~USSHgti7%R_!a7O7BfeL@id>~OJ|pQSEqbKF+2z41Y@$L z)AD~B!VSmmqs+uc?DK(Zm2=*V@h2Egd+NhMFD>ahjV?CIZP}Z&Qog;?;d4*W=5xQX zvsRlQE>0=x<92@hbYz6KHMq7(;p%i@{QmC7;(TDr$yn^ZJ2qAOu1ehRbY%SfZE&2V zW~8y-^+uzOs zCKBzJ!X(h!nWNRwmkypu($krUg%-CeQWV^cq@M`h%F7XahF($8QXL*??tOKS4hvBf z)7lY}bx>+!+?@&rKGV!2=R@jv;|~fgr(CdKl6Lsyr|*>DPmG2Zoc8oe%afRKnfi8m>t>*w;#kXdS;9`qz!96({1?n+MxEr0!0HAc+X|sQHj3zM)!P41D z>uaGZI)Q6rrY_r~gzD&UF=E@rM0qu9z*((K^e+nh7jd* zJb{U1&BKmTEfPGEH|FVF-5G|ARw1homjjUTbWEXoGZG6M=v1DSNHDNVEmslv4 zt+JeaMOg}rZ$velHJ&~#A!LLKBuadF(JhzF^^Q!hc&XanT#vs|Hw;_9%g(i$GzWJZ zVv3sh*sw@QPv=oE=m+bUt#`z+$s+;^W>~P{I@z#06^6Q4r>Labp{lUhB zmF8(_BQbZ;V`1100{KB$Uof6k*TK`CHp;_!xEU#iN#mE5)v~FMR=X2wLaAs zx+xW=mULxwm7|ca2sNs@CbOMZMxh8v5+xaS8cA@W!c>7r{_&Eby5E@}zV!Doo_TwE zwtZZ{9{T*gIvK1L6=21td(p_tBtW8x_o)VMWY?v^d<@+BHM8YxM+Y)LzTu4h>YV!P zLkViy593^cFJddf^HHm3p@dYDS|*>hvFM%=Kl9}eY9A1*>nhNjBaGTQ=kSo+Y>q1= zw$&Q9J*bIvNp*EO*=XPL>wnCC@U@xjDV1iZO>A+AV=SX<#LG9=6WTwB`cQB>Co`P) z#LX}FlSbD*ZAi9rD7j&I@jZT=R!3eL0}}1|%4ijii5G*-bT&{enSb?F^U0H-Cy($E z_2rv3$jJ<)DBmHKE^#P+>zj&HM6cyZI!O3RQF2Yv`Mp}oQgl8o*`3#>e&#t)vHHMW z$pd*!Z;Ln0^Ao6i=pe@{FaNPAkGogR5V~UUi8TUFyE8gwPw5Y1q>Q&5tc;ek53v2w zzf)n$zJ;&}*pZJgg~a+@9bMhL?X6sYFFcGmPDv0fJvzb3pKA%A$9`$qLZ{534UbkT zYc5ebMIgl$^7hbiJ8^ zwV((ZS6+=4VPZ69M+a*ww_HEI9$qE=*l`${UwVgMJXy=yxr=h~Ojna*W*yOrLxd6TNw(Wog=Mp)QgzF$;2ObfCJs8dg|EVncDknnA;jtS z-ONX##|Givpy$mYaPx&rR!y3lEXDOmm+2fSM=z$)1Gb0t!8IZf5C4q(iJB6mC6QJ-!7~Ue=naYf`Txn8(6OyKTN3 z^Q=9djg5f;ey@?2q;js)Uyf{uqas*OQ)u8eq3a5)Ik;RJ(*Ow}L+K0UKdi!VP7U;{ z>z`ffK)=S+aL9z=xY$fLP&gcrBRF81)XlK>ydN*FN|^J;^IkUvU4|i$`O1rAd7m8H zgE9!^agl6He0;Y^4T_ASz0uEvxhXv2myuhrPEK=1oT114{QAOeViQNMs(I?iAZ&yx z62x2qiuh^)DlGG=h#v40GL`Xlx}%q|@0()a#W7pjwgv})BZ!+yM|Sn)Y9<)6A?pZ| zO=|NBSK$wtcbxw*G0gY3IQp#~2ZnWw2>x(=u$haMrKX#Ut)tCP2E0m=beiI#=z~7< z3m+_gse4#xH=>qc%NJQv=N^yW^b|5HXP1fF60szxR%a_sA-4;MC{Sb4oYeg6m{<>+iibYu z8totL(`2)cI+VA?H#ZXfzQgJB3XiZMla@JJ$t13Gm6hPJLr>`YIJ+wG@mHhkCjv$#o~^Q>os%G{cLLu=Snced!mOHLO~Dz z!so+ngzqjt`Acw#0&|WS1$#UUXgoc%43G%Q(K?^vF4{|TQ+bU&Nt&}W09-a0q4ZDm za}2%+iG(;X*I>vYK%NNBxM}6h3^vcxSVFww_H`Xh>ycNM&(F03?qnecOmV<1TI5H55eO&!})E2jr8MA|iDJoUjTa+5|aKi1;6QXfmR%v}$DvygotsPoZ$U z-YS(5<_}QV7Y|lQvUIXgb8&KZ<+N~evHGKb_`muQj41Z;iZ5XOvscj9_bB--9CPv} z-5xm{LJVppiqcL}^MZLP?DU0iAN;MD+@}WAM-Lin^Zj0`DWAj54CxRvE2Bn9oasC> zBHk&~?{8#w$+qoE*O3nPaR#d!8*ePCE$e&LK;ZTnnwaD2@)rrf+ikfd!tDrshWHeq zh=m!}t)8iwuUz+^>^ezlXLW;ZLcGIJOzj!zK(t8ju!aYaZQX&i zJ|@!~-MTz-?}*jm83?R5Jm#j$w~J0Mgb5#yZCG^`t!8u|=`&>FC(|=}=5bp;n48 zHWE2(E%+ggCpa?h=E9y2chp;8|zV;y4eBpzD3gdG)HCL00o>S41d%6AQlT zEK_uCk?#XDZ11ZwNs8l2{ zKa;d8=}fl}BBoV8TESa)72xLK<8`w3&a`T_t;}9OqrOQoe|R-o)`C>c)ypjY15+mA zE&U{|8Yq4`(eCSc*iF3;fb0e|a(Dob3lNECxmya#tx0joL**%n`i6hP}%B-`@4(c=qX$7IS7`3<@P9_tzr){C{SE#{j8#)af<0XT Q03G%Tg6(;#X@C3mf3EfhZ2$lO diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/样品导入模板.xlsx b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/样品导入模板.xlsx index e6addeb9855082c3ba401dbff45cc4aecf81e8ca..0d8d0e297d6ff5c2d304bc438c499a27da375b01 100644 GIT binary patch literal 22129 zcmeG^?{6&ERSpdZ1yv;iqzVZ!S_!^X_Ih`1Kik+Y_U`z-m-qZ)Jv*;S5R|(!v)*~0 zote(e`bP^=`r0NhjnbATq=XQ7sv4yzA|!2SqBQ&o@C5hFO~SfIrq-Y zopW|}{o`4nLSE$T%soHux#ygF&Y8J)?)sIF-T%vD*}w1H_vLr~=4Zyn@b6clanGxM zHgdeUx*J5>?I75m*z0){*s}0xxH5K}D0TyXu~M6utQd}O1-9#NEmj`cTpgdS7;z$e zTX=!*ELQfNxbo?xk3X<*-SI>ML~+**V*|MO@nWT$B;kCu8e3hbC*p}9bbNqxf~Y4F zxQ(`|p|G~amQ$@yPR>+80?nhcM0gsP?ZrxT*{)TL&HWI1KAv~b>A05D3aoz5@spA+ zSV5J@rnoA9YN(q^wOp9`c?y$&k{m6FysgUrkCdTto-)MAz6Tb~ zwW01usbK0{yGL8BIf<}EBD55}v?Y_HN}O$JojCjCCvI>5=-0-^@b8zDvvXCR-OI(i zk+CB@@LkupgWXMM5B%8^aRPwF%B1{<9$d|dykrP%fo@g)OX*Sq zd%G_CjJDc&r?u6oxeKkFzeMnoK|Iqmn`SNNClQ2Y!AO-XMV?cuv$d6sX8}oF4)ShJ zPESrz`~v^RfXs6%D=X7;sVs`AL{gVi1NgI(GgHfTf-jL2{`3I;)XH+RIYaOzlER-E zz+b&EH#0@?B~sUO{p|s4H0bJT$~{Nv1m2}m^jSckolMa+7I;JMUD!n@@RMP_))O~_ zXcZ6;5o5G(BoKrq6|5_1zRcF|FATA!=q$Np#=rIz-HMPxuLx zAik3r2n)6yr(|@Gxh~P`nrlTt9CQ-nG1piTZmCK(-8OL~O?1iafnD~?#tfz=x>>tn ztOQ=!s9Vks#e@kVWU->Ab3Vm39gnE-qUa~0SH^NvK($ja%vF(e%Y@w6kE|k|87E-r zEyoKC({|#xjOO78##`WhML@hNHT#;k-=mmO;%=AmyefjAh}jCZn_bZh%WyYbzgxt; z9B+e*3S&KhVq9r0%)?sIcw15VutiABFA3$}%LevdMFa1?ypWKZPN>CHnrl1G8e#`#*}% zA4TZO|M>_#Y%ZUFT-Ko&qBIsHs@9g3cW-Fn-Elo{1E0yS#*MwThEJ zh=pCa#F-2o5WKBONE#ywlFztFccUxt1xigmMsCF^>#f)b19)dMDVs}qW2aF2C+cZ) z8lOW{ibFG2Wju%3@#c=;Qp^`HOnPR1p_-yk0;@t9lV|TGK zH(j5u7?ubZD;;>!h09(DMvJi)6y6rR6t$8_dF`=tQBG1l;<<9Ks=>>PArpp7<1mVo zmWaE`h7y#T%JZ=&C4YT-3Z3nI22c`AP9{ZT7F^_iO$zW4YFek$ajc|h>_Q9bPTi)F zFz6>vw9&P9jked1t_v_f=oH6eyD_}5kP{kj;O!b}TY_k8O(RY5otDH??ZOMYB8`^# zb|#x~3SGKtE`z))hzDaUm0b?*^h3cJe_lf=y7B+Bph zlyn1#LSfgnRwKy!GIoHsz#CcsgYsb^MgB(44*se+r+CO|4G)6Jb$82%Tu3KLH*%bH z2usrYj$u|yQ;%ZlrHrIU6~v80C3f4{alB28fioC`D@GSwT}G2M3`jigfAl_`LG3N9 zQHt}8M6x_}syXmKUAU;p41;2|1p05L|6D|=En2uJdP$z;$l^c{psS>*Y*vuuH#e7d zbw&|Rl7I`jRz<9V5!a_P;lPT6BZkJjo(bVSkb(b${O3lN=SVlkgt{4AhuCVs@`KW! z0bV_hQ4Kr@1EjiYL*1hql`|nLrP{POMXhDY`kbqvykDkllyh-Xc3|(P zmtL$AuJSs3hCxivzwUGl*WSyXKXOf|DWq0NvJzQhBN;kcPS_}WXrP2CK9Z_znG@HK zC(bEk&k(Y6V);U?i~+2VrGXnC?Eqf|Sv6{$T$!dk|6Wvf0^!S5R{G>~QkAI(#gT?O zgUXV<(JD(d1ZLmWnqu;s1-x*9BZOU&;;;5^%=20O6UqaciLY{H^k4F2i&d#k-9 zHmsoU16MSlYBJOh?xmQXsTgV}Ml-PCb--_at{)`7b@!dG9z1)ydiR}Y;KxvH-LCpQ zeDRjL==Id^!R;s2MI5W&qgTI~(#YA6im|kSlY&Tt4ND90=hdXI$)!~*@Pf#YHcTIq zag|(QG-0VAeOcXsg$r+AHA_0>n5scdQ1k}XgZzQF4j2z|0dN*!!_op|At?3zRk*?A zhS#XV#flG`TjUTF-Z9vaxAtrB*eLszjH^fi{kDV1hRrlZL#U>u1<&cA+d@^>#or_d z;s5O*NrE0+*sjFF~O6Qt?wa$iK)< z@xEua#$G4wOoXAgf34qZJFtL^`+ic}%3h(L!eGcya-q_l57&?wmZoyX?Y`?lnN>0j z;A!t^#mE$&h_>g*f;8_1lwzx7wwVJLEBboPvE6=eF2`DTcLJ5t7AyK%VNFTf>!84b z(5>IA{iA@LSNQiy6@*In8J#^I#CwrIq6aoMmO?&E$kBXT;h%u<`bJSA-+ zBO$Dr?SjGbYa`o}h5&8c9G>B}V22RQo?sV?Obl=qUM$1*Fz_%~jD(h*rGuAVI{5nA zhfhCq@XEK2UVro8&Xb3CZZA}K(CJf(6-sgR-U~oMQJNIx;N@=|-g)X#iqk5?d5q$~ z?iV7Qocy?St>U^});E&M7703Pr3@XmcdAH4yxhH40~FRtJTb;_$r zp=^LH3T3n=g=$(N$W$)K6cMDs1ffvPOb((&p^Vm~P)$n&na%})_nHNbOff+yR5Me8 zP$;7{DOA%EL1uD6pz0AMUS^m_FTD;`(%m0Ecku3WIVQEg&P=so2}H7Pjs~|zgG~lz zzO~B0t+i$@uI&9HLzt;guvl`5VSth-i^~k*=(QIQp8QIVL$#vQWQ}GcCU}#9wOdQE zTMW#o&7oi_jm~v7nv&QYY%=hGG$W}^My-XV(&&U&qj`x<`6dHveyMIKH51kxN=>EF zS+7Quldc<5|E1soX-0xg##~bqY3e%j!DTVH^Rk42H9w^r24;3P8CX+OX>{hR)6~Ul z69#78wHTONih+koqZ419W-eZ!Fz|phlv;NuRcA744W_!$d9P0M9*ZkY1|E=xQZum0 zsE0|DN8x4M_)sX`?!NPM@!|RIyYC*n{YvR0G$Sm{f()npA-WWaXTee=o&_1w(bIo< z_`-{2dFx_1CVdS+?WZGjoknQpoz2nU)@ZOf6ilVj5xP!etTwocLJZ7=Z80#THW^q` zQ)zUBuG1LHytB!`nqL})MpBzYsi`zNLf2`G<@thv+2C6Y%!DGEIklCJ#tj;cYm8x&!3V^l z4af8O=8XJC-!3V^llm1>a+nCQ>k*|OB{KQZ>j+aDVn!@p0cWvSw))@NUh%D9}{V$_}& z!7?UWkLqQg;7wStW~#pSukuCq+J$Q5T3z))xmsx7vj;!iJ}EDI9~PxuOGL2bTUmO* zFcz}0%C+~xLNCA=G+oJj^lEE){d&cKwd#$27{UfY$G(xDxR7oxwSsV8S~rC6xSeGW zwpW3v<;@M@^&Qx#5BIRoa7*swH{#H9lT}x4IRpSK|1<+{qw98*kHCg4k*>cgO4-m1 zdh$zi$xFmB_>H`@0w=;Rdw~%y!>+;S_jps4Ktp_~a?e?r?TjL#3bH=CUW}I;`JAGliVKq99No%q`XEpv% z8bI*^VleA`YMx~@4{nKY;JZQi7Pr$vvUBH`MI#`64 zV#B@yGFHTvtMU2ZM+`TzFvfsV(|w+@W6e2JHR^EbfrVVpY|^&>;n#n%a^Kh({-u*P zTQa@3EiDwB!ZhCGP3+E6sGQnZa(;vt8i`#{`~^PNOf?^Psw2^P)e))EG%-K z>L8Zf5vvwtp7AiBR{DUBYy*ZIrA zRBIEpDr^|Tqp0R>3pV%l^pR9W0tqQ#_S~d+!c-B}wiGbO-ix7HNyoLjyF0PlkYYd; zSF1j{c2#ar72~dtXJ%QrrH~$0^xc2H`NXgM{MZ=&;bBF;(}W#+a3s!W(PiV`y*E=x z9w*P{ z(Qy?*A&kqT$R>ckrH+Lf(t&zh899Q{J@lYX@;)Wmln@Gdv`d94cp5pLi_}h zh@>d6y_{mp?UYhrdpU*EE2r2SM@Tu%?ob+pl+)Y{%g1svbcPK^g387}f+iWF0B9d3 znFN(}GlC|xxz>u)1fk_xfSsB!w~0xMge88Q#t1Bb^+|o7N+;sOaBg7a!birK+f5M#D z?egP^Q9b9yYF12~uJ_Ev*u%z&dIay4_4U1IwxqHh)i*eJR;S!^*Ch^~y;sOYsLG@c zRm6SpXfXVQPbeh{|AwFU$@%cZ6E&0nfc}6FcVfhof3*Z$0vVnL14QVT$tN;K2 literal 9686 zcmeHtg;yNe_I2X~cXtbJfe_pY4#9$Z<23Hx1a}V_BuH=#?(PH&?j*RogkMi)-ZwLu z`Tm0Us#jO7UUm1W>bm=!eeOA>EC&ON1Aqr00ssI?fbmhLg+3Gj5DN`0`2zB%%^c9=vWLa81Wrf{ih~?l~@l;`rFL`mEj#^ZMY(IlT`Ufj_(idHU#vA#ySJ{lXFGj##D(%*(@R_n z0~VbukpN2Y;gmZ(tq)FCbi#hj<+_#4QaRfi_NTtiSF5=Z^oyKKPfv zUK+2U)Xk0(d@TJCGH^Y!9E%|)=O!xMOsVGUFSCSR7nMgtyxjhr1VfD|5azXCtM9|m z;<7;0-T>v*23KhW77jo4XScH8lsg9(1V%c?WGRQz^&U*unai20)Yq~e^scQjj3o_4 zIdX%m)KXKY5>*)EtZ#{)Vds+s;|Zn)X!j{-tr*{wL(Pb(9+m}H*7IcVC5)&0eN8Ud zMHLR^l|PtF#T{@mHlHu|9k}^kz}a7*L_& z!n)xcf89@;ed(uN%XK=O>CwaUth;33WDp!dUbF&HsDC<%3`6xmAH+e#Aufatc{1)c zY;Jar*2Z>r*1zf2N3~_eMRuIF54CQg-sERfFGVE6W#xEi<|b~M!`_kf+cJ~z*N`qC zIW#_>dvQQ}CDhU%o$y)NvztQp;8p+aL#9)k6B^ogTDPN5lk(xPaQs4!MHHOo#%b9l z!&S{8B({|U94TNr3sG*q1HT{6>Vp9njmgi;&*{#VJ z$xi{taGg?!-xfLCiB-O;US!Ralxuz3$t=y~*0?~yyCIiC7SWESaIg3ofMzR_5(Ukhijni`omcS$p9F*n$0uZSQgtWntSY zR^$S^WWM@z5o{Px5!T#94h}p`gY#~lqjhCS5Uc~g@XmM|0}?mfBs7mv4^3aHP=A5F zRWCMISGOg+=-32H#`0iAA3J1;(_sLHvQ_+Wx9g_w}QqCln8VDJWo}65w0zS2CQ}ljQSvw z3?3_^U_=9vcjY~feWBlL+8op0a(&&J)hvv7y|ip(XTL;z7>)4b1~cQ$8+n>?g{tY0 zAVp>To=PBJ_jwZS$!6O2I2YN&_V&W7OWh+lsWoxt7 zTrOu9`{Prqs}oDM!XR)Y3GU*HX_=M8clCEshKe?N9i}{Y3c+t5+n*qe{wL)Fgc4KE zAyjXI(4Gi@2nC`1@6zyB;{Qh`pdeWr#Bu(+w^9`axgK_mR^%Tc>~86_*Q= zVGxZE%gDg5;jpm|_V!B-kG{9|GRgp2W8;e`g)2q2D9y_M)Acf_zz$Y@-OFv&twEpJpf;JrYV_jIwh|V z&srYL6gPwn<9l$cniIy@Q=8LIz%w`!U2jZRe&os~@jBi`@?~v@f194__Rc zZ4LI%tU_k`pOj(Mk(A3x8lT`u#Z0pl7da(lAeZ+{TplSlJt$Kk$WD`T)& zP@spRwqY1;$^Jk-o{1pqy;iU_L`1)XCqNnlvJ1A}$7_Q}gnibBUyGJANO+zLKkD=K zrJ;R6wc@#dJ7~;%(?8{qu1qtT=33s#awj^rS&ray@kjqKTsc&_0}3=8^!M!*z|Cte z7S>M7BfQ}P>QWE}M}lmyEpom@gf}10>}hIO6OZKw z&c$tS{U83Q1o#M(9*_7&lR{3F@u$!+MF&lZlg%(p{9T@JyN=K+-E~iR2N3b=476q9s^35CZFcc$?TAd zHDf~=EWOGaD$NSk?Zl>6pRn`{A$tO;8%JUtWIGTYUuNkjwezWn^`u0}y)08f`NB(mXf9^6;^+1d481E{KZvZaRe8Q-@FmXMvc!Sr822{0s zL>E@t)U-qS#Sg(7=?Uo6D7(1kDJy#VW8bgWZ+w@IS9~9|TYYqzFZa&QZ#-o_9JQCf zWjo&OzVy6Yw2djeKlTeg-*)y@cxt)ZsQ7xnY&k7J^XBd=?c-xr`+}{|RrBbs@IH;x zfajn|Jv+QTdEBblv>OfN-?8T4yO8eI=`%1Z%kM*^_gGUyR@Wd#1-w8{pxk~fo zaxF;>21`t9qdO;hJxJY`Kf5dB4wmHQ?$esqB(skpciMUq!h(rf7>zHhbWNSByrCe>?w zXM^`n?dXkIuPxtj%3E8AHRtzNSk*chDvht7;np7&_Z}4u`JO{P5vmBPEObH9r<6nI zkm$-ey5=$J6ddft;U(uMw*?@o*rw=ol{<0zkh~_E?BcX&j1(>L03p46t~%gQOUWV- zm>fY^oAy1~-^-pe!uyMcXrzMyoC>IvhMAA)S;Q<7nH;|Zp#9=ZaNU?*B`eNM=?2UV zcRbgk$fo(GRO3v02r+hV7t=7im(1UDVe|P%=Wjxr33X9cL*C|Fp6IbUIC6xzp{u1M zj~9(rJ3Az>!fyT|$oZtjHd99>>n#nxWG=DJ7cZ~@x zL4UYBXa}4G1(7-vbOpe5ph%;&C_`ZamE>^sv!Z$w1%O)+8GRwP@{taJ(PbTot!zgK z&d}*TP2ID93D|Cv3}_gxMt}_D9b3*p&_1Y^q_(KDP2N@CzolHR+KRf^YJ?dod?xz| z0Wcs45IKT6shX}Ni)B2FO}SNjx+JUt3Ja;o-FAZC1ZdhR+-^nPRwE3Ynlj|&N^~pi zZj}+%AZWjO*{>=Yo}9ihs1s<0Ui<3cd;f%A0FrEeq`0a;6yqZZj-LOsA#4#ZNVX7g z+Z}*ZYi}d@7WR`_BSR+i7CHi>iqKwwV>Tm(oxT&_?Y*<_SC<+ntY8>iOQZP~zp@5@ zR1Xn7V?rrqwCU4SPjtgcM75Ggk|H^QZXd?gdf5HXE&y#csa`#sfzJ9K&ZWLHM1CRF zRxL7r#}YW@#cdKFIp%Kf-mffB^p`9!Dxj_at1PEGmQ!Zx5&SI5wVV24LsWC5o)^@G zt60OFUC>G$okYv<*^)pt-7g@g2bM~K;sQt1r5b3LmSO2ePJhTE?F>EB?`O*T(~otd zmyy6C7(oWWRD^-#S`n4l&@(E;Jvgq{Zl7FjT<^T@XFDRA_|8Xr-NveUwv-Pn$BSv7 z0}}$^X^+WX*w<2Bi(Dm>NZeLe@GYq?X?>r8(-}16)ABc7Og+WhYhQ&`Q=oDt9W54AzT=_YHx(<>G#j??ZfQ^X+tF8|TZ+KK zlh@usqMmakkAr#7Pv?mx28Dm#^REk1cJxr|Lx_s3kp5OD`l%w$7C>7d+t2+^0Xfi; zvm@rgXv5#}qqxe_tfuXMNrjNiOl!N*Q<;RrtI|BpYI5W#s`A-|Dc&RiT5>8k1-kW8 zxfV4R(E72x&o=xe+!;O}&D)ZE$=f7YA4vo+@>P`Yql8B@)*%ey_Y%@WH5$t8y2u!V zVq{;KL2z#Ot#V9I@kr*FW&+e@hVrKNwvUh3VTQ7y3(aJNPh#cM`;LDT4FC_+{B5gkIr|rcse4M$CFe%`m0Y`oS1l;e9=HjA>O8pPE znU59Aw5%I$ke>1hQ{OHgx;oXx_HuGlc+J1>te8$FCXPT`@n>%14YaG};mZUf*ook^ z3j`kPY3YF)++k!1Mz72!7IIqkJ6qqO@HVdpRRFy6H989ELxVY+F0jP2K6z9nZy@Mi z0I~y6n9?Y@#|AM%FnUQd&|rH#p<1Nux1)S;hEiX9ve1Z&I?W&njc}OuB(9cxUv2mN zfz#^wV{2!dlq(y)SoqA`L(xRPQ5xgKTSJ2&D##R>a?%2;mh%RjoBVgFmx;2aE>tGqvN=L4M0`1i? z;vL0Q&$TxYs5|fz9N*6#t&KR?x+RLwWWeT|+$)NaaMTly;Y7kbWgOZByr=qUg~@IPkeUCHX|2=>8Y;O6Y*yDv%SgXEi1+@Of$MQ zjm0xj76tTt#Yv2T=;rEyA^F@nHKG&f0ZA!F2VbkZTLKBgYL^e0g&ZRE?^qg(CKedu z*FrV%d1IK$ONdt$#4ND%$i8Ndr_G4)>LFs|B{*DmNoBFWC(WM$_)J za;hTC#`s=lh?u~tTfqBDVNV~Uh9EjZjMip4D=mUI5l-R>LFq@80|Apon43w+0o?NgvR&j9+lhR045EM>Q zYzz)&vy8JpybX;jdjq*m@nu-iI~e4Ii92}3ueEifO!c4fT*^EPfc96sW**=Ot4~Vi zbcE*XbgQz2BbiCFp$JVLK%SvY1KiA0JJ}>Ff~Rq{FN;ta6wHzfFq9-mNRiHMpP^aR zx^j@VpP_m|kG0Al`db8GYrCWNg;1NF8^roNu71ndHku@Y7p)UFp`o{V&WQQWMqG>Y ziyKmnXVs-n$LGM$`;1A z?9tv`kk368A*Md+=kQDmtp+TFubl_tl8LGrOsb<&N#jdu_Z%HC1O+n+?fH3sm1y40oa)P$ z{x7MqV6|l$wup&z#Yo@7eOhLb+vuGNlS8WEOgxC6BPqHi=(w&DGZkD&O>%i*UN`#| zTdwNBMcx&DUF+M6RJSp{lEH&)_uM>ILrxd>>OmyAfKxLV^fr4Wl;=NunpPZ7i zcNVWRk~MoJ@QgjTS`9ZL0;Rp3v4umb4@(QPoM!YSn8-V=-8+t`dHups=946VQXx2H zww$ibZip7QC!+{A$zGL-B{V2c|Hdqz;Zvj+O#?Yx=K%gYnI%ejiCY2;(ThvZ?IhXK zP@q#^>T<0nL71asRjh0RFG8`xD;ip>uDEFBJU2z{LqTD1k~X3GB6i26x?WBEN@4m< zn9>jo7KJb>L*yd`UwG>mB?+Rtv=#QZLK%g1@w3?~W%;A_uo8MmbYE3`5`&t-NOoms z)81k{?*i$%e6y0nnWC5z(Kzw>qG-Ubcy7Y&k3TM#cde)hR-G5Fv~&*2QVnB0&A0i9 zNK*sTw>aJhCJ)71z}U`I5u;=tQ3{vw!~7ul-nzywnI)E@Sqy}#w_q}`)xgMq7ksqS zHRD%?pJvlVe~^cK_w)4*f?pQPzD;u&-V#QHF`FS#!Dv9z^^H)cKrg}r>71W~#5HaOz8UrOEPL4g_@s|po4-|P;>bxU z_st0(8cYQNYz_}eTon%)s&PeVx5Y~$#qmw5BZrs|4N=g-D9x?k2KupwVYlUvtm;aX z4Ui?ln=qmc$_uhLAx{|(Z2y`VPL5<)7C~~|Qy2gM=MU;z7&!t>Rh=CzLFPX_AR|FZ zuA7~p?-;zAvvpJrQY~~|HllaO5TGxruLhP^($x)02>XvQ{0QhY9kIj=38i4%FrBii zsXfpk$V5=s>oxHkPp?-xYm13nb{$&4J1bJKhu^7sHGwgf0FL6}Gz5D1QlWAUG3!a zht|3Y1&dR9h=MjyhERUcP#fNoaD={Z1L;l6UbvR8H(`rF&}B+!36jx?evEHJ9G=kC zZEw!mTA3F|ooM@*m&(BeYO@jiZ&e5M!vpCi}0~MP% z>Ge{lmyC~wOLVXtj9q;Pr@~=tg20GS6LZl_oWU~DYsd1?H(sO1Y2J3%ex%umA@5E+e=Lr0xG5o(0BZTrcaRv~2^*dQE|5Z@+V7IQd;KB^WzQE1W@%mCPoFn!(Z< zN-!nOC++0x7r4=%1`rRX&x(Y3o>;umGr;ZQ{KDt%SO$lWb3+p!Y2KA=K@J8IcvT^c z`rwh$g)h!pl5I>KR&H-(9s|C=8>tHJphYJ^@0eDUaF&wgpgip4rWQd$LA7A-G)a5& zaEQ8A`{ZV*#+soWw9=P8=D;?ulF>8hb795ls+ZzY!yz@IbZXY>Nv5QbMA(GoU!3zH z_`Wh)9fUh@-?l(MMIN8Tcw$f%Yb@V?G5?__=m6(f&NEX`U{+3To2FfcLFRlhO+^YZ z@4O!R30`e8?Sp}u&%X*LsYl&(0)$tOkh2(n<(84X{r{*1;nW{nM*O%e69-P;HTam_ z)b53PoZO&9k8p!BC}+k85_khGIcfTHT>L(Cl%+Rgc*BUE*y4DjjE`7k7Bjhh&p1BW zPGQ4rK2KF(MEv5OQsv7AeD>8T}POvHBZEsjnwf0{X zozxdJe&se0AZL?>PByM!F8hMCn|wCCvzEuk&YNLf33ihdb*7$p+qouI0Cv;im>lX7 zA{-W*M+Ez!^>7sN)#h+g%D!38hK>@V`J$_8lg`|zjA@k-sm@5S9b36q_u0})M+)U^ zd;REk_v-Sgb?sw)O~FCAt91>_{U|hUNkpCKuxJJ0i%ebC0m{z;k%nu5h1tN9-{szr|ChkASe|euqS?=!!{@&~V z5Ae5R3gnW1>HPl+{I&o2C$t5UhyKzH{T2N8PST%H0HEU8Pw@Yzuk@>(U)u|RTG~MV ze>d@uX2Y*mel0KkX@wr^UyDt@8u&E>|I>gJ;ZFm9=HtIYf6ZY2go={<4*fNs`PIVT xmGVzK06Qxi diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 504cf45..52feff6 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -8,7 +8,7 @@ import os # BioyondCellWorkstation 默认配置(包含所有必需参数) API_CONFIG = { # API 连接配置 - "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.10.169:44388"), + "api_host": os.getenv("BIOYOND_API_HOST", "http://172.21.32.103:44388"), "api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"), "timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")), diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index cf06f70..e520628 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -766,7 +766,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): # self.success = True # return self.success - def func_pack_send_msg_cmd(self, elec_use_num, elec_vol, assembly_type, assembly_pressure) -> bool: + def func_pack_send_msg_cmd(self, elec_use_num, elec_vol, assembly_type) -> bool: """UNILAB写参数""" while (self.request_rec_msg_status) == False: print("wait for request_rec_msg_status to True") @@ -782,9 +782,6 @@ class CoinCellAssemblyWorkstation(WorkstationBase): #发送电解液组装类型 self._unilab_send_msg_assembly_type(assembly_type) time.sleep(1) - #发送电池压制力 - self._unilab_send_msg_assembly_pressure(assembly_pressure) - time.sleep(1) self._unilab_send_msg_succ_cmd(True) time.sleep(1) while (self.request_rec_msg_status) == True: @@ -892,10 +889,22 @@ class CoinCellAssemblyWorkstation(WorkstationBase): self.success = True return self.success + def qiming_coin_cell_code(self, fujipian_panshu:int, fujipian_juzhendianwei:int=0, gemopanshu:int=0, gemo_juzhendianwei:int=0, lvbodian:bool=True, battery_pressure_mode:bool=True, battery_pressure:int=4000, battery_clean_ignore:bool=False) -> bool: + self.success = False + self.client.use_node('REG_MSG_NE_PLATE_NUM').write(fujipian_panshu) + self.client.use_node('REG_MSG_NE_PLATE_MATRIX').write(fujipian_juzhendianwei) + self.client.use_node('REG_MSG_SEPARATOR_PLATE_NUM').write(gemopanshu) + self.client.use_node('REG_MSG_SEPARATOR_PLATE_MATRIX').write(gemo_juzhendianwei) + self.client.use_node('COIL_ALUMINUM_FOIL').write(lvbodian) + self.client.use_node('REG_MSG_PRESS_MODE').write(battery_pressure_mode) + # self.client.use_node('REG_MSG_ASSEMBLY_PRESSURE').write(battery_pressure) + self.client.use_node('REG_MSG_BATTERY_CLEAN_IGNORE').write(battery_clean_ignore) + self.success = True + + return self.success - - def func_allpack_cmd(self, elec_num, elec_use_num, elec_vol:int=50, assembly_type:int=7, assembly_pressure:int=4200, file_path: str="D:\\coin_cell_data") -> bool: - elec_num, elec_use_num, elec_vol, assembly_type, assembly_pressure = int(elec_num), int(elec_use_num), int(elec_vol), int(assembly_type), int(assembly_pressure) + def func_allpack_cmd(self, elec_num, elec_use_num, elec_vol:int=50, assembly_type:int=7, file_path: str="D:\\coin_cell_data") -> bool: + elec_num, elec_use_num, elec_vol, assembly_type = int(elec_num), int(elec_use_num), int(elec_vol), int(assembly_type) summary_csv_file = os.path.join(file_path, "duandian.csv") # 如果断点文件存在,先读取之前的进度 if os.path.exists(summary_csv_file): @@ -945,7 +954,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): print(f"开始第{last_i+i+1}瓶电解液的组装") #第一个循环从上次断点继续,后续循环从0开始 j_start = last_j if i == last_i else 0 - self.func_pack_send_msg_cmd(elec_use_num-j_start, elec_vol, assembly_type, assembly_pressure) + self.func_pack_send_msg_cmd(elec_use_num-j_start, elec_vol, assembly_type) for j in range(j_start, elec_use_num): print(f"开始第{last_i+i+1}瓶电解液的第{j+j_start+1}个电池组装") @@ -1217,8 +1226,10 @@ class CoinCellAssemblyWorkstation(WorkstationBase): ''' + if __name__ == "__main__": # 简单测试 workstation = CoinCellAssemblyWorkstation() + workstation.qiming_coin_cell_code(fujipian_panshu=1, fujipian_juzhendianwei=2, gemopanshu=3, gemo_juzhendianwei=4, lvbodian=False, battery_pressure_mode=False, battery_pressure=4200, battery_clean_ignore=False) print(f"工作站创建成功: {workstation.deck.name}") print(f"料盘数量: {len(workstation.deck.children)}") diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv index bb66a7a..98f9038 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_a.csv @@ -43,21 +43,22 @@ REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,10000, UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,8730, UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,8530, REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,10018,ASSEMBLY_TYPE7or8 -COIL_ALUMINUM_FOIL,BOOL,,,,coil,8340, +COIL_ALUMINUM_FOIL,BOOL,,使用铝箔垫,,coil,8340, REG_MSG_NE_PLATE_MATRIX,INT16,,负极片矩阵点位,,hold_register,440, REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,隔膜矩阵点位,,hold_register,450, REG_MSG_TIP_BOX_MATRIX,INT16,,移液枪头矩阵点位,,hold_register,480, REG_MSG_NE_PLATE_NUM,INT16,,负极片盘数,,hold_register,443, REG_MSG_SEPARATOR_PLATE_NUM,INT16,,隔膜盘数,,hold_register,453, +REG_MSG_PRESS_MODE,BOOL,,压制模式(false:压力检测模式,True:距离模式),,coil,8360,电池压制模式 ,,,,,,, ,BOOL,,视觉对位(false:使用,true:忽略),,coil,8300,视觉对位 ,BOOL,,复检(false:使用,true:忽略),,coil,8310,视觉复检 ,BOOL,,手套箱_左仓(false:使用,true:忽略),,coil,8320,手套箱左仓 ,BOOL,,手套箱_右仓(false:使用,true:忽略),,coil,8420,手套箱右仓 ,BOOL,,真空检知(false:使用,true:忽略),,coil,8350,真空检知 -,BOOL,,压制模式(false:压力检测模式,True:距离模式),,coil,8360,电池压制模式 ,BOOL,,电解液添加模式(false:单次滴液,true:二次滴液),,coil,8370,滴液模式 ,BOOL,,正极片称重(false:使用,true:忽略),,coil,8380,正极片称重 ,BOOL,,正负极片组装方式(false:正装,true:倒装),,coil,8390,正负极反装 ,BOOL,,压制清洁(false:使用,true:忽略),,coil,8400,压制清洁 ,BOOL,,物料盘摆盘方式(false:水平摆盘,true:堆叠摆盘),,coil,8410,负极片摆盘方式 +REG_MSG_BATTERY_CLEAN_IGNORE,BOOL,,忽略电池清洁(false:使用,true:忽略),,coil,8460, \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json index c96e6b2..3619085 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json +++ b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json @@ -22,7 +22,7 @@ ], "parent": null, "type": "device", - "class": "bettery_station_registry", + "class": "coincellassemblyworkstation_device", "position": { "x": 600, "y": 400, diff --git a/unilabos/registry/devices/bioyond_cell.yaml b/unilabos/registry/devices/bioyond_cell.yaml index c50a1a8..a54e702 100644 --- a/unilabos/registry/devices/bioyond_cell.yaml +++ b/unilabos/registry/devices/bioyond_cell.yaml @@ -137,7 +137,7 @@ bioyond_cell: WH4_x5_y1_z1_5_quantity: 0.0 WH4_x5_y2_z1_10_materialName: '' WH4_x5_y2_z1_10_quantity: 0.0 - xlsx_path: unilabos/devices/workstation/bioyond_cell/样品导入模板.xlsx + xlsx_path: unilabos\devices\workstation\bioyond_studio\bioyond_cell\样品导入模板.xlsx handles: {} placeholder_keys: {} result: {} @@ -463,7 +463,7 @@ bioyond_cell: default: 0.0 type: number xlsx_path: - default: unilabos/devices/workstation/bioyond_cell/样品导入模板.xlsx + default: unilabos\devices\workstation\bioyond_studio\bioyond_cell\样品导入模板.xlsx type: string required: [] type: object @@ -498,6 +498,63 @@ bioyond_cell: title: auto_feeding4to3_from_xlsx参数 type: object type: UniLabJsonCommand + auto-create_and_inbound_materials: + feedback: {} + goal: {} + goal_default: + material_names: null + type_id: 3a190ca0-b2f6-9aeb-8067-547e72c11469 + warehouse_name: 粉末加样头堆栈 + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + material_names: + type: string + type_id: + default: 3a190ca0-b2f6-9aeb-8067-547e72c11469 + type: string + warehouse_name: + default: 粉末加样头堆栈 + type: string + required: [] + type: object + result: {} + required: + - goal + title: create_and_inbound_materials参数 + type: object + type: UniLabJsonCommand + auto-create_materials: + feedback: {} + goal: {} + goal_default: + mappings: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + mappings: + type: object + required: + - mappings + type: object + result: {} + required: + - goal + title: create_materials参数 + type: object + type: UniLabJsonCommand auto-create_orders: feedback: {} goal: {} @@ -576,6 +633,84 @@ bioyond_cell: title: order_list_v2参数 type: object type: UniLabJsonCommand + auto-process_order_finish_report: + feedback: {} + goal: {} + goal_default: + report_request: null + used_materials: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + report_request: + type: string + used_materials: + type: string + required: + - report_request + type: object + result: {} + required: + - goal + title: process_order_finish_report参数 + type: object + type: UniLabJsonCommand + auto-process_sample_finish_report: + feedback: {} + goal: {} + goal_default: + report_request: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + report_request: + type: string + required: + - report_request + type: object + result: {} + required: + - goal + title: process_sample_finish_report参数 + type: object + type: UniLabJsonCommand + auto-process_step_finish_report: + feedback: {} + goal: {} + goal_default: + report_request: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + report_request: + type: string + required: + - report_request + type: object + result: {} + required: + - goal + title: process_step_finish_report参数 + type: object + type: UniLabJsonCommand auto-report_material_change: feedback: {} goal: {} @@ -622,6 +757,27 @@ bioyond_cell: title: scheduler_continue参数 type: object type: UniLabJsonCommand + auto-scheduler_reset: + feedback: {} + goal: {} + goal_default: {} + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: scheduler_reset参数 + type: object + type: UniLabJsonCommand auto-scheduler_start: feedback: {} goal: {} @@ -778,6 +934,62 @@ bioyond_cell: title: transfer_3_to_2_to_1参数 type: object type: UniLabJsonCommand + auto-update_push_ip: + feedback: {} + goal: {} + goal_default: + ip: null + port: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + ip: + type: string + port: + type: string + required: [] + type: object + result: {} + required: + - goal + title: update_push_ip参数 + type: object + type: UniLabJsonCommand + auto-wait_for_order_finish: + feedback: {} + goal: {} + goal_default: + order_code: null + timeout: 1800 + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + order_code: + type: string + timeout: + default: 1800 + type: integer + required: + - order_code + type: object + result: {} + required: + - goal + title: wait_for_order_finish参数 + type: object + type: UniLabJsonCommand auto-wait_for_transfer_task: feedback: {} goal: {} diff --git a/unilabos/registry/devices/coin_cell_workstation.yaml b/unilabos/registry/devices/coin_cell_workstation.yaml new file mode 100644 index 0000000..7e541eb --- /dev/null +++ b/unilabos/registry/devices/coin_cell_workstation.yaml @@ -0,0 +1,576 @@ +coincellassemblyworkstation_device: + category: + - coin_cell_workstation + class: + action_value_mappings: + auto-change_hole_sheet_to_2: + feedback: {} + goal: {} + goal_default: + hole: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + hole: + type: object + required: + - hole + type: object + result: {} + required: + - goal + title: change_hole_sheet_to_2参数 + type: object + type: UniLabJsonCommandAsync + auto-fill_plate: + feedback: {} + goal: {} + goal_default: {} + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: fill_plate参数 + type: object + type: UniLabJsonCommandAsync + auto-fun_wuliao_test: + feedback: {} + goal: {} + goal_default: {} + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: fun_wuliao_test参数 + type: object + type: UniLabJsonCommand + auto-func_allpack_cmd: + feedback: {} + goal: {} + goal_default: + assembly_type: 7 + elec_num: null + elec_use_num: null + elec_vol: 50 + file_path: D:\coin_cell_data + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + assembly_type: + default: 7 + type: integer + elec_num: + type: string + elec_use_num: + type: string + elec_vol: + default: 50 + type: integer + file_path: + default: D:\coin_cell_data + type: string + required: + - elec_num + - elec_use_num + type: object + result: {} + required: + - goal + title: func_allpack_cmd参数 + type: object + type: UniLabJsonCommand + auto-func_get_csv_export_status: + feedback: {} + goal: {} + goal_default: {} + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_get_csv_export_status参数 + type: object + type: UniLabJsonCommand + auto-func_pack_device_auto: + feedback: {} + goal: {} + goal_default: {} + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_pack_device_auto参数 + type: object + type: UniLabJsonCommand + auto-func_pack_device_init: + feedback: {} + goal: {} + goal_default: {} + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_pack_device_init参数 + type: object + type: UniLabJsonCommand + auto-func_pack_device_start: + feedback: {} + goal: {} + goal_default: {} + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_pack_device_start参数 + type: object + type: UniLabJsonCommand + auto-func_pack_device_stop: + feedback: {} + goal: {} + goal_default: {} + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_pack_device_stop参数 + type: object + type: UniLabJsonCommand + auto-func_pack_get_msg_cmd: + feedback: {} + goal: {} + goal_default: + file_path: D:\coin_cell_data + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + file_path: + default: D:\coin_cell_data + type: string + required: [] + type: object + result: {} + required: + - goal + title: func_pack_get_msg_cmd参数 + type: object + type: UniLabJsonCommand + auto-func_pack_send_bottle_num: + feedback: {} + goal: {} + goal_default: + bottle_num: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + bottle_num: + type: string + required: + - bottle_num + type: object + result: {} + required: + - goal + title: func_pack_send_bottle_num参数 + type: object + type: UniLabJsonCommand + auto-func_pack_send_finished_cmd: + feedback: {} + goal: {} + goal_default: {} + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_pack_send_finished_cmd参数 + type: object + type: UniLabJsonCommand + auto-func_pack_send_msg_cmd: + feedback: {} + goal: {} + goal_default: + assembly_type: null + elec_use_num: null + elec_vol: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + assembly_type: + type: string + elec_use_num: + type: string + elec_vol: + type: string + required: + - elec_use_num + - elec_vol + - assembly_type + type: object + result: {} + required: + - goal + title: func_pack_send_msg_cmd参数 + type: object + type: UniLabJsonCommand + auto-func_read_data_and_output: + feedback: {} + goal: {} + goal_default: + file_path: D:\coin_cell_data + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + file_path: + default: D:\coin_cell_data + type: string + required: [] + type: object + result: {} + required: + - goal + title: func_read_data_and_output参数 + type: object + type: UniLabJsonCommand + auto-func_stop_read_data: + feedback: {} + goal: {} + goal_default: {} + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: {} + required: [] + type: object + result: {} + required: + - goal + title: func_stop_read_data参数 + type: object + type: UniLabJsonCommand + auto-modify_deck_name: + feedback: {} + goal: {} + goal_default: + resource_name: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + resource_name: + type: string + required: + - resource_name + type: object + result: {} + required: + - goal + title: modify_deck_name参数 + type: object + type: UniLabJsonCommand + auto-post_init: + feedback: {} + goal: {} + goal_default: + ros_node: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + ros_node: + type: object + required: + - ros_node + type: object + result: {} + required: + - goal + title: post_init参数 + type: object + type: UniLabJsonCommand + auto-qiming_coin_cell_code: + feedback: {} + goal: {} + goal_default: + battery_clean_ignore: false + battery_pressure: 4000 + battery_pressure_mode: true + fujipian_juzhendianwei: 0 + fujipian_panshu: null + gemo_juzhendianwei: 0 + gemopanshu: 0 + lvbodian: true + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + battery_clean_ignore: + default: false + type: boolean + battery_pressure: + default: 4000 + type: integer + battery_pressure_mode: + default: true + type: boolean + fujipian_juzhendianwei: + default: 0 + type: integer + fujipian_panshu: + type: integer + gemo_juzhendianwei: + default: 0 + type: integer + gemopanshu: + default: 0 + type: integer + lvbodian: + default: true + type: boolean + required: + - fujipian_panshu + type: object + result: {} + required: + - goal + title: qiming_coin_cell_code参数 + type: object + type: UniLabJsonCommand + module: unilabos.devices.workstation.coin_cell_assembly.coin_cell_assembly:CoinCellAssemblyWorkstation + status_types: + data_assembly_coin_cell_num: int + data_assembly_pressure: int + data_assembly_time: float + data_axis_x_pos: float + data_axis_y_pos: float + data_axis_z_pos: float + data_coin_cell_code: str + data_coin_num: int + data_electrolyte_code: str + data_electrolyte_volume: int + data_glove_box_o2_content: float + data_glove_box_pressure: float + data_glove_box_water_content: float + data_open_circuit_voltage: float + data_pole_weight: float + request_rec_msg_status: bool + request_send_msg_status: bool + sys_mode: str + sys_status: str + type: python + config_info: [] + description: '' + handles: [] + icon: '' + init_param_schema: + config: + properties: + address: + default: 172.21.32.111 + type: string + debug_mode: + default: false + type: boolean + deck: + type: object + port: + default: '502' + type: string + required: [] + type: object + data: + properties: + data_assembly_coin_cell_num: + type: integer + data_assembly_pressure: + type: integer + data_assembly_time: + type: number + data_axis_x_pos: + type: number + data_axis_y_pos: + type: number + data_axis_z_pos: + type: number + data_coin_cell_code: + type: string + data_coin_num: + type: integer + data_electrolyte_code: + type: string + data_electrolyte_volume: + type: integer + data_glove_box_o2_content: + type: number + data_glove_box_pressure: + type: number + data_glove_box_water_content: + type: number + data_open_circuit_voltage: + type: number + data_pole_weight: + type: number + request_rec_msg_status: + type: boolean + request_send_msg_status: + type: boolean + sys_mode: + type: string + sys_status: + type: string + required: + - sys_status + - sys_mode + - request_rec_msg_status + - request_send_msg_status + - data_assembly_coin_cell_num + - data_assembly_time + - data_open_circuit_voltage + - data_axis_x_pos + - data_axis_y_pos + - data_axis_z_pos + - data_pole_weight + - data_assembly_pressure + - data_electrolyte_volume + - data_coin_num + - data_coin_cell_code + - data_electrolyte_code + - data_glove_box_pressure + - data_glove_box_o2_content + - data_glove_box_water_content + type: object + registry_type: device + version: 1.0.0 diff --git a/unilabos/registry/devices/reaction_station_bioyond.yaml b/unilabos/registry/devices/reaction_station_bioyond.yaml index f1a16ec..0967ef4 100644 --- a/unilabos/registry/devices/reaction_station_bioyond.yaml +++ b/unilabos/registry/devices/reaction_station_bioyond.yaml @@ -1,527 +1 @@ -reaction_station.bioyond: - category: - - work_station - - reaction_station_bioyond - class: - action_value_mappings: - auto-post_init: - feedback: {} - goal: {} - goal_default: - ros_node: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - ros_node: - type: object - required: - - ros_node - type: object - result: {} - required: - - goal - title: post_init参数 - type: object - type: UniLabJsonCommand - auto-process_web_workflows: - feedback: {} - goal: {} - goal_default: - json_str: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - json_str: - type: string - volume: - description: 分液公式(μL) - type: string - required: - - volume - - assign_material_name - - time - - torque_variation - - titration_type - - temperature - type: object - result: {} - required: - - goal - title: drip_back参数 - type: object - type: UniLabJsonCommand - drip_back: - feedback: {} - goal: - assign_material_name: assign_material_name - temperature: temperature - time: time - titration_type: titration_type - torque_variation: torque_variation - volume: volume - goal_default: - assign_material_name: '' - temperature: '' - time: '' - titration_type: '' - torque_variation: '' - volume: '' - handles: {} - result: {} - schema: - description: 滴回去 - properties: - feedback: {} - goal: - properties: - assign_material_name: - description: 物料名称(不能为空) - type: string - temperature: - description: 温度设定(°C) - type: string - time: - description: 观察时间(分钟) - type: string - required: - - file_path - type: object - result: {} - required: - - goal - title: load_bioyond_data_from_file参数 - type: object - type: UniLabJsonCommand - liquid_feeding_beaker: - feedback: {} - goal: - assign_material_name: assign_material_name - temperature: temperature - time: time - titration_type: titration_type - torque_variation: torque_variation - volume: volume - goal_default: - assign_material_name: '' - temperature: '' - time: '' - titration_type: '' - torque_variation: '' - volume: '' - handles: {} - result: {} - schema: - description: 液体进料烧杯 - properties: - feedback: {} - goal: - properties: - assign_material_name: - description: 物料名称 - type: string - temperature: - description: 温度设定(°C) - type: string - time: - description: 观察时间(分钟) - type: string - titration_type: - description: 是否滴定(1=否, 2=是) - type: string - torque_variation: - description: 是否观察 (1=否, 2=是) - type: string - volume: - description: 分液公式(μL) - type: string - required: - - volume - - assign_material_name - - time - - torque_variation - - titration_type - - temperature - type: object - result: {} - required: - - goal - title: liquid_feeding_beaker参数 - type: object - type: UniLabJsonCommand - liquid_feeding_solvents: - feedback: {} - goal: - assign_material_name: assign_material_name - solvents: solvents - temperature: temperature - time: time - titration_type: titration_type - torque_variation: torque_variation - volume: volume - goal_default: - assign_material_name: '' - solvents: '' - temperature: '25.00' - time: '360' - titration_type: '1' - torque_variation: '2' - volume: '' - handles: - input: - - data_key: solvents - data_source: handle - data_type: object - handler_key: solvents - io_type: source - label: Solvents Data From Calculation Node - result: {} - schema: - description: 液体投料-溶剂。可以直接提供volume(μL),或通过solvents对象自动从additional_solvent(mL)计算volume。 - properties: - feedback: {} - goal: - properties: - assign_material_name: - description: 物料名称 - type: string - solvents: - description: '溶剂信息对象(可选),包含: additional_solvent(溶剂体积mL), total_liquid_volume(总液体体积mL)。如果提供,将自动计算volume' - type: string - temperature: - default: '25.00' - description: 温度设定(°C),默认25.00 - type: string - time: - default: '360' - description: 观察时间(分钟),默认360 - type: string - titration_type: - default: '1' - description: 是否滴定(1=否, 2=是),默认1 - type: string - torque_variation: - default: '2' - description: 是否观察 (1=否, 2=是),默认2 - type: string - volume: - description: 分液量(μL)。可直接提供,或通过solvents参数自动计算 - type: string - required: - - assign_material_name - type: object - result: {} - required: - - goal - title: liquid_feeding_solvents参数 - type: object - type: UniLabJsonCommand - liquid_feeding_titration: - feedback: {} - goal: - assign_material_name: assign_material_name - temperature: temperature - time: time - titration_type: titration_type - torque_variation: torque_variation - volume_formula: volume_formula - goal_default: - assign_material_name: '' - temperature: '' - time: '' - titration_type: '' - torque_variation: '' - volume_formula: '' - handles: {} - result: {} - schema: - description: 液体进料(滴定) - properties: - feedback: {} - goal: - properties: - assign_material_name: - description: 物料名称 - type: string - temperature: - description: 温度设定(°C) - type: string - time: - description: 观察时间(分钟) - type: string - titration_type: - description: 是否滴定(1=否, 2=是) - type: string - torque_variation: - description: 是否观察 (1=否, 2=是) - type: string - volume_formula: - description: 分液公式(μL) - type: string - required: - - volume_formula - - assign_material_name - - time - - torque_variation - - titration_type - - temperature - type: object - result: {} - required: - - goal - title: liquid_feeding_titration参数 - type: object - type: UniLabJsonCommand - liquid_feeding_vials_non_titration: - feedback: {} - goal: - assign_material_name: assign_material_name - temperature: temperature - time: time - titration_type: titration_type - torque_variation: torque_variation - volume_formula: volume_formula - goal_default: - assign_material_name: '' - temperature: '' - time: '' - titration_type: '' - torque_variation: '' - volume_formula: '' - handles: {} - result: {} - schema: - description: 液体进料小瓶(非滴定) - properties: - feedback: {} - goal: - properties: - assign_material_name: - description: 物料名称 - type: string - temperature: - description: 温度设定(°C) - type: string - time: - description: 观察时间(分钟) - type: string - titration_type: - description: 是否滴定(1=否, 2=是) - type: string - torque_variation: - description: 是否观察 (1=否, 2=是) - type: string - volume_formula: - description: 分液公式(μL) - type: string - required: - - volume_formula - - assign_material_name - - time - - torque_variation - - titration_type - - temperature - type: object - result: {} - required: - - goal - title: liquid_feeding_vials_non_titration参数 - type: object - type: UniLabJsonCommand - process_and_execute_workflow: - feedback: {} - goal: - task_name: task_name - workflow_name: workflow_name - goal_default: - task_name: '' - workflow_name: '' - handles: {} - result: {} - schema: - description: 处理并执行工作流 - properties: - feedback: {} - goal: - properties: - task_name: - description: 任务名称 - type: string - workflow_name: - description: 工作流名称 - type: string - required: - - workflow_name - - task_name - type: object - result: {} - required: - - goal - title: process_and_execute_workflow参数 - type: object - type: UniLabJsonCommand - reactor_taken_in: - feedback: {} - goal: - assign_material_name: assign_material_name - cutoff: cutoff - temperature: temperature - goal_default: - assign_material_name: '' - cutoff: '' - temperature: '' - handles: {} - result: {} - schema: - description: 反应器放入 - 将反应器放入工作站,配置物料名称、粘度上限和温度参数 - properties: - feedback: {} - goal: - properties: - assign_material_name: - description: 物料名称 - type: string - cutoff: - description: 粘度上限 - type: string - temperature: - description: 温度设定(°C) - type: string - required: - - cutoff - - temperature - - assign_material_name - type: object - result: {} - required: - - goal - title: reactor_taken_in参数 - type: object - type: UniLabJsonCommand - reactor_taken_out: - feedback: {} - goal: {} - goal_default: {} - handles: {} - result: {} - schema: - description: 反应器取出 - 从工作站中取出反应器,无需参数的简单操作 - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: - properties: - code: - description: 操作结果代码(1表示成功,0表示失败) - type: integer - return_info: - description: 操作结果详细信息 - type: string - type: object - required: - - goal - title: reactor_taken_out参数 - type: object - type: UniLabJsonCommand - solid_feeding_vials: - feedback: {} - goal: - assign_material_name: assign_material_name - material_id: material_id - temperature: temperature - time: time - torque_variation: torque_variation - goal_default: - assign_material_name: '' - material_id: '' - temperature: '' - time: '' - torque_variation: '' - handles: {} - result: {} - schema: - description: 固体进料小瓶 - 通过小瓶向反应器中添加固体物料,支持多种粉末类型(盐、面粉、BTDA) - properties: - feedback: {} - goal: - properties: - assign_material_name: - description: 物料名称(用于获取试剂瓶位ID) - type: string - material_id: - description: 粉末类型ID,1=盐(21分钟),2=面粉(27分钟),3=BTDA(38分钟) - type: string - temperature: - description: 温度设定(°C) - type: string - time: - description: 观察时间(分钟) - type: string - torque_variation: - description: 是否观察 (1=否, 2=是) - type: string - required: - - assign_material_name - - material_id - - time - - torque_variation - - temperature - type: object - result: {} - required: - - goal - title: solid_feeding_vials参数 - type: object - type: UniLabJsonCommand - module: unilabos.devices.workstation.bioyond_studio.reaction_station:BioyondReactionStation - protocol_type: [] - status_types: - all_workflows: dict - bioyond_status: dict - station_info: dict - workstation_status: dict - type: python - config_info: [] - description: Bioyond反应站 - handles: [] - icon: reaction_station.webp - init_param_schema: - config: - properties: - config: - type: object - deck: - type: string - required: [] - type: object - data: - properties: - all_workflows: - type: object - bioyond_status: - type: object - station_info: - type: object - workstation_status: - type: object - required: - - bioyond_status - - all_workflows - - station_info - - workstation_status - type: object - version: 1.0.0 +{} diff --git a/unilabos/registry/devices/work_station.yaml b/unilabos/registry/devices/work_station.yaml deleted file mode 100644 index ba07691..0000000 --- a/unilabos/registry/devices/work_station.yaml +++ /dev/null @@ -1,6698 +0,0 @@ -bettery_station_registry: - category: - - work_station - class: - action_value_mappings: - auto-change_hole_sheet_to_2: - feedback: {} - goal: {} - goal_default: - hole: null - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - hole: - type: object - required: - - hole - type: object - result: {} - required: - - goal - title: change_hole_sheet_to_2参数 - type: object - type: UniLabJsonCommandAsync - auto-fill_plate: - feedback: {} - goal: {} - goal_default: {} - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: fill_plate参数 - type: object - type: UniLabJsonCommandAsync - auto-fun_wuliao_test: - feedback: {} - goal: {} - goal_default: {} - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: fun_wuliao_test参数 - type: object - type: UniLabJsonCommand - auto-func_allpack_cmd: - feedback: {} - goal: {} - goal_default: - assembly_pressure: 4200 - assembly_type: 7 - elec_num: null - elec_use_num: null - elec_vol: 50 - file_path: D:\coin_cell_data - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - assembly_pressure: - default: 4200 - type: integer - assembly_type: - default: 7 - type: integer - elec_num: - type: string - elec_use_num: - type: string - elec_vol: - default: 50 - type: integer - file_path: - default: D:\coin_cell_data - type: string - required: - - elec_num - - elec_use_num - type: object - result: {} - required: - - goal - title: func_allpack_cmd参数 - type: object - type: UniLabJsonCommand - auto-func_get_csv_export_status: - feedback: {} - goal: {} - goal_default: {} - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: func_get_csv_export_status参数 - type: object - type: UniLabJsonCommand - auto-func_pack_device_auto: - feedback: {} - goal: {} - goal_default: {} - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: func_pack_device_auto参数 - type: object - type: UniLabJsonCommand - auto-func_pack_device_init: - feedback: {} - goal: {} - goal_default: {} - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: func_pack_device_init参数 - type: object - type: UniLabJsonCommand - auto-func_pack_device_start: - feedback: {} - goal: {} - goal_default: {} - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: func_pack_device_start参数 - type: object - type: UniLabJsonCommand - auto-func_pack_device_stop: - feedback: {} - goal: {} - goal_default: {} - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: func_pack_device_stop参数 - type: object - type: UniLabJsonCommand - auto-func_pack_get_msg_cmd: - feedback: {} - goal: {} - goal_default: - file_path: D:\coin_cell_data - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - file_path: - default: D:\coin_cell_data - type: string - required: [] - type: object - result: {} - required: - - goal - title: func_pack_get_msg_cmd参数 - type: object - type: UniLabJsonCommand - auto-func_pack_send_bottle_num: - feedback: {} - goal: {} - goal_default: - bottle_num: null - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - bottle_num: - type: string - required: - - bottle_num - type: object - result: {} - required: - - goal - title: func_pack_send_bottle_num参数 - type: object - type: UniLabJsonCommand - auto-func_pack_send_finished_cmd: - feedback: {} - goal: {} - goal_default: {} - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: func_pack_send_finished_cmd参数 - type: object - type: UniLabJsonCommand - auto-func_pack_send_msg_cmd: - feedback: {} - goal: {} - goal_default: - assembly_pressure: null - assembly_type: null - elec_use_num: null - elec_vol: null - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - assembly_pressure: - type: string - assembly_type: - type: string - elec_use_num: - type: string - elec_vol: - type: string - required: - - elec_use_num - - elec_vol - - assembly_type - - assembly_pressure - type: object - result: {} - required: - - goal - title: func_pack_send_msg_cmd参数 - type: object - type: UniLabJsonCommand - auto-func_read_data_and_output: - feedback: {} - goal: {} - goal_default: - file_path: D:\coin_cell_data - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - file_path: - default: D:\coin_cell_data - type: string - required: [] - type: object - result: {} - required: - - goal - title: func_read_data_and_output参数 - type: object - type: UniLabJsonCommand - auto-func_stop_read_data: - feedback: {} - goal: {} - goal_default: {} - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: {} - required: [] - type: object - result: {} - required: - - goal - title: func_stop_read_data参数 - type: object - type: UniLabJsonCommand - auto-modify_deck_name: - feedback: {} - goal: {} - goal_default: - resource_name: null - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - resource_name: - type: string - required: - - resource_name - type: object - result: {} - required: - - goal - title: modify_deck_name参数 - type: object - type: UniLabJsonCommand - auto-post_init: - feedback: {} - goal: {} - goal_default: - ros_node: null - handles: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - ros_node: - type: object - required: - - ros_node - type: object - result: {} - required: - - goal - title: post_init参数 - type: object - type: UniLabJsonCommand - module: unilabos.devices.workstation.coin_cell_assembly.coin_cell_assembly:CoinCellAssemblyWorkstation - status_types: - data_assembly_coin_cell_num: int - data_assembly_pressure: int - data_assembly_time: float - data_axis_x_pos: float - data_axis_y_pos: float - data_axis_z_pos: float - data_coin_cell_code: str - data_coin_num: int - data_electrolyte_code: str - data_electrolyte_volume: int - data_glove_box_o2_content: float - data_glove_box_pressure: float - data_glove_box_water_content: float - data_open_circuit_voltage: float - data_pole_weight: float - request_rec_msg_status: bool - request_send_msg_status: bool - sys_mode: str - sys_status: str - type: python - config_info: [] - description: '' - handles: [] - icon: '' - init_param_schema: - config: - properties: - address: - default: 192.168.1.20 - type: string - debug_mode: - default: true - type: boolean - port: - default: '502' - type: string - station_resource: - type: object - required: - - station_resource - type: object - data: - properties: - data_assembly_coin_cell_num: - type: integer - data_assembly_pressure: - type: integer - data_assembly_time: - type: number - data_axis_x_pos: - type: number - data_axis_y_pos: - type: number - data_axis_z_pos: - type: number - data_coin_cell_code: - type: string - data_coin_num: - type: integer - data_electrolyte_code: - type: string - data_electrolyte_volume: - type: integer - data_glove_box_o2_content: - type: number - data_glove_box_pressure: - type: number - data_glove_box_water_content: - type: number - data_open_circuit_voltage: - type: number - data_pole_weight: - type: number - request_rec_msg_status: - type: boolean - request_send_msg_status: - type: boolean - sys_mode: - type: string - sys_status: - type: string - required: - - sys_status - - sys_mode - - request_rec_msg_status - - request_send_msg_status - - data_assembly_coin_cell_num - - data_assembly_time - - data_open_circuit_voltage - - data_axis_x_pos - - data_axis_y_pos - - data_axis_z_pos - - data_pole_weight - - data_assembly_pressure - - data_electrolyte_volume - - data_coin_num - - data_coin_cell_code - - data_electrolyte_code - - data_glove_box_pressure - - data_glove_box_o2_content - - data_glove_box_water_content - type: object - version: 1.0.0 -workstation: - category: - - work_station - class: - action_value_mappings: - AGVTransferProtocol: - feedback: {} - goal: - from_repo: from_repo - from_repo_position: from_repo_position - to_repo: to_repo - to_repo_position: to_repo_position - goal_default: - from_repo: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - from_repo_position: '' - to_repo: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - to_repo_position: '' - handles: {} - result: {} - schema: - description: '' - properties: - feedback: - properties: - status: - type: string - required: - - status - title: AGVTransfer_Feedback - type: object - goal: - properties: - from_repo: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: from_repo - type: object - from_repo_position: - type: string - to_repo: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: to_repo - type: object - to_repo_position: - type: string - required: - - from_repo - - from_repo_position - - to_repo - - to_repo_position - title: AGVTransfer_Goal - type: object - result: - properties: - return_info: - type: string - success: - type: boolean - required: - - return_info - - success - title: AGVTransfer_Result - type: object - required: - - goal - title: AGVTransfer - type: object - type: AGVTransfer - AddProtocol: - feedback: {} - goal: - amount: amount - equiv: equiv - event: event - mass: mass - mol: mol - purpose: purpose - rate_spec: rate_spec - ratio: ratio - reagent: reagent - stir: stir - stir_speed: stir_speed - time: time - vessel: vessel - viscous: viscous - volume: volume - goal_default: - amount: '' - equiv: '' - event: '' - mass: '' - mol: '' - purpose: '' - rate_spec: '' - ratio: '' - reagent: '' - stir: false - stir_speed: 0.0 - time: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - viscous: false - volume: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - - data_key: reagent - data_source: handle - data_type: resource - handler_key: reagent - label: Reagent - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_status: - type: string - progress: - type: number - required: - - progress - - current_status - title: Add_Feedback - type: object - goal: - properties: - amount: - type: string - equiv: - type: string - event: - type: string - mass: - type: string - mol: - type: string - purpose: - type: string - rate_spec: - type: string - ratio: - type: string - reagent: - type: string - stir: - type: boolean - stir_speed: - type: number - time: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - viscous: - type: boolean - volume: - type: string - required: - - vessel - - reagent - - volume - - mass - - amount - - time - - stir - - stir_speed - - viscous - - purpose - - event - - mol - - rate_spec - - equiv - - ratio - title: Add_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Add_Result - type: object - required: - - goal - title: Add - type: object - type: Add - AdjustPHProtocol: - feedback: {} - goal: - ph_value: ph_value - reagent: reagent - settling_time: settling_time - stir: stir - stir_speed: stir_speed - stir_time: stir_time - vessel: vessel - volume: volume - goal_default: - ph_value: 0.0 - reagent: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - - data_key: reagent - data_source: handle - data_type: resource - handler_key: reagent - label: Reagent - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: AdjustPH_Feedback - type: object - goal: - properties: - ph_value: - type: number - reagent: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - - ph_value - - reagent - title: AdjustPH_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: AdjustPH_Result - type: object - required: - - goal - title: AdjustPH - type: object - type: AdjustPH - CentrifugeProtocol: - feedback: {} - goal: - speed: speed - temp: temp - time: time - vessel: vessel - goal_default: - speed: 0.0 - temp: 0.0 - time: 0.0 - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_speed: - type: number - current_status: - type: string - current_temp: - type: number - progress: - type: number - required: - - progress - - current_speed - - current_temp - - current_status - title: Centrifuge_Feedback - type: object - goal: - properties: - speed: - type: number - temp: - type: number - time: - type: number - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - - speed - - time - - temp - title: Centrifuge_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Centrifuge_Result - type: object - required: - - goal - title: Centrifuge - type: object - type: Centrifuge - CleanProtocol: - feedback: {} - goal: - repeats: repeats - solvent: solvent - temp: temp - vessel: vessel - volume: volume - goal_default: - repeats: 0 - solvent: '' - temp: 0.0 - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - volume: 0.0 - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Solvent - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_device: - type: string - status: - type: string - time_remaining: - properties: - nanosec: - maximum: 4294967295 - minimum: 0 - type: integer - sec: - maximum: 2147483647 - minimum: -2147483648 - type: integer - required: - - sec - - nanosec - title: time_remaining - type: object - time_spent: - properties: - nanosec: - maximum: 4294967295 - minimum: 0 - type: integer - sec: - maximum: 2147483647 - minimum: -2147483648 - type: integer - required: - - sec - - nanosec - title: time_spent - type: object - required: - - status - - current_device - - time_spent - - time_remaining - title: Clean_Feedback - type: object - goal: - properties: - repeats: - maximum: 2147483647 - minimum: -2147483648 - type: integer - solvent: - type: string - temp: - type: number - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - volume: - type: number - required: - - vessel - - solvent - - volume - - temp - - repeats - title: Clean_Goal - type: object - result: - properties: - return_info: - type: string - success: - type: boolean - required: - - return_info - - success - title: Clean_Result - type: object - required: - - goal - title: Clean - type: object - type: Clean - CleanVesselProtocol: - feedback: {} - goal: - repeats: repeats - solvent: solvent - temp: temp - vessel: vessel - volume: volume - goal_default: - repeats: 0 - solvent: '' - temp: 0.0 - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - volume: 0.0 - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Solvent - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: CleanVessel_Feedback - type: object - goal: - properties: - repeats: - maximum: 2147483647 - minimum: -2147483648 - type: integer - solvent: - type: string - temp: - type: number - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - volume: - type: number - required: - - vessel - - solvent - - volume - - temp - - repeats - title: CleanVessel_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: CleanVessel_Result - type: object - required: - - goal - title: CleanVessel - type: object - type: CleanVessel - DissolveProtocol: - feedback: {} - goal: - amount: amount - event: event - mass: mass - mol: mol - reagent: reagent - solvent: solvent - stir_speed: stir_speed - temp: temp - time: time - vessel: vessel - volume: volume - goal_default: - amount: '' - event: '' - mass: '' - mol: '' - reagent: '' - solvent: '' - stir_speed: 0.0 - temp: '' - time: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - volume: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Solvent - - data_key: reagent - data_source: handle - data_type: resource - handler_key: reagent - label: Reagent - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: Dissolve_Feedback - type: object - goal: - properties: - amount: - type: string - event: - type: string - mass: - type: string - mol: - type: string - reagent: - type: string - solvent: - type: string - stir_speed: - type: number - temp: - type: string - time: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - volume: - type: string - required: - - vessel - - solvent - - volume - - amount - - temp - - time - - stir_speed - - mass - - mol - - reagent - - event - title: Dissolve_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Dissolve_Result - type: object - required: - - goal - title: Dissolve - type: object - type: Dissolve - DryProtocol: - feedback: {} - goal: - compound: compound - vessel: vessel - goal_default: - compound: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: Dry_Feedback - type: object - goal: - properties: - compound: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - compound - - vessel - title: Dry_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Dry_Result - type: object - required: - - goal - title: Dry - type: object - type: Dry - EvacuateAndRefillProtocol: - feedback: {} - goal: - gas: gas - vessel: vessel - goal_default: - gas: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_device: - type: string - status: - type: string - time_remaining: - properties: - nanosec: - maximum: 4294967295 - minimum: 0 - type: integer - sec: - maximum: 2147483647 - minimum: -2147483648 - type: integer - required: - - sec - - nanosec - title: time_remaining - type: object - time_spent: - properties: - nanosec: - maximum: 4294967295 - minimum: 0 - type: integer - sec: - maximum: 2147483647 - minimum: -2147483648 - type: integer - required: - - sec - - nanosec - title: time_spent - type: object - required: - - status - - current_device - - time_spent - - time_remaining - title: EvacuateAndRefill_Feedback - type: object - goal: - properties: - gas: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - - gas - title: EvacuateAndRefill_Goal - type: object - result: - properties: - return_info: - type: string - success: - type: boolean - required: - - return_info - - success - title: EvacuateAndRefill_Result - type: object - required: - - goal - title: EvacuateAndRefill - type: object - type: EvacuateAndRefill - EvaporateProtocol: - feedback: {} - goal: - pressure: pressure - solvent: solvent - stir_speed: stir_speed - temp: temp - time: time - vessel: vessel - goal_default: - pressure: 0.0 - solvent: '' - stir_speed: 0.0 - temp: 0.0 - time: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Evaporation Vessel - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Eluting Solvent - output: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: VesselOut - label: Evaporation Vessel - placeholder_keys: - vessel: unilabos_nodes - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_device: - type: string - status: - type: string - time_remaining: - properties: - nanosec: - maximum: 4294967295 - minimum: 0 - type: integer - sec: - maximum: 2147483647 - minimum: -2147483648 - type: integer - required: - - sec - - nanosec - title: time_remaining - type: object - time_spent: - properties: - nanosec: - maximum: 4294967295 - minimum: 0 - type: integer - sec: - maximum: 2147483647 - minimum: -2147483648 - type: integer - required: - - sec - - nanosec - title: time_spent - type: object - required: - - status - - current_device - - time_spent - - time_remaining - title: Evaporate_Feedback - type: object - goal: - properties: - pressure: - type: number - solvent: - type: string - stir_speed: - type: number - temp: - type: number - time: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - - pressure - - temp - - time - - stir_speed - - solvent - title: Evaporate_Goal - type: object - result: - properties: - return_info: - type: string - success: - type: boolean - required: - - return_info - - success - title: Evaporate_Result - type: object - required: - - goal - title: Evaporate - type: object - type: Evaporate - FilterProtocol: - feedback: {} - goal: - continue_heatchill: continue_heatchill - filtrate_vessel: filtrate_vessel - stir: stir - stir_speed: stir_speed - temp: temp - vessel: vessel - volume: volume - goal_default: - continue_heatchill: false - filtrate_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - stir: false - stir_speed: 0.0 - temp: 0.0 - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - volume: 0.0 - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - - data_key: filtrate_vessel - data_source: handle - data_type: resource - handler_key: FiltrateVessel - label: Filtrate Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - - data_key: filtrate_vessel - data_source: executor - data_type: resource - handler_key: FiltrateOut - label: Filtrate Vessel - placeholder_keys: - filtrate_vessel: unilabos_resources - vessel: unilabos_nodes - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_status: - type: string - current_temp: - type: number - filtered_volume: - type: number - progress: - type: number - required: - - progress - - current_temp - - filtered_volume - - current_status - title: Filter_Feedback - type: object - goal: - properties: - continue_heatchill: - type: boolean - filtrate_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: filtrate_vessel - type: object - stir: - type: boolean - stir_speed: - type: number - temp: - type: number - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - volume: - type: number - required: - - vessel - - filtrate_vessel - - stir - - stir_speed - - temp - - continue_heatchill - - volume - title: Filter_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Filter_Result - type: object - required: - - goal - title: Filter - type: object - type: Filter - FilterThroughProtocol: - feedback: {} - goal: - eluting_repeats: eluting_repeats - eluting_solvent: eluting_solvent - eluting_volume: eluting_volume - filter_through: filter_through - from_vessel: from_vessel - residence_time: residence_time - to_vessel: to_vessel - goal_default: - eluting_repeats: 0 - eluting_solvent: '' - eluting_volume: 0.0 - filter_through: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - from_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - residence_time: 0.0 - to_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVessel - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVessel - label: To Vessel - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Eluting Solvent - output: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVesselOut - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVesselOut - label: To Vessel - placeholder_keys: - from_vessel: unilabos_resources - to_vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: FilterThrough_Feedback - type: object - goal: - properties: - eluting_repeats: - maximum: 2147483647 - minimum: -2147483648 - type: integer - eluting_solvent: - type: string - eluting_volume: - type: number - filter_through: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: filter_through - type: object - from_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: from_vessel - type: object - residence_time: - type: number - to_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: to_vessel - type: object - required: - - from_vessel - - to_vessel - - filter_through - - eluting_solvent - - eluting_volume - - eluting_repeats - - residence_time - title: FilterThrough_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: FilterThrough_Result - type: object - required: - - goal - title: FilterThrough - type: object - type: FilterThrough - HeatChillProtocol: - feedback: {} - goal: - pressure: pressure - purpose: purpose - reflux_solvent: reflux_solvent - stir: stir - stir_speed: stir_speed - temp: temp - temp_spec: temp_spec - time: time - time_spec: time_spec - vessel: vessel - goal_default: - pressure: '' - purpose: '' - reflux_solvent: '' - stir: false - stir_speed: 0.0 - temp: 0.0 - temp_spec: '' - time: '' - time_spec: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - status: - type: string - required: - - status - title: HeatChill_Feedback - type: object - goal: - properties: - pressure: - type: string - purpose: - type: string - reflux_solvent: - type: string - stir: - type: boolean - stir_speed: - type: number - temp: - type: number - temp_spec: - type: string - time: - type: string - time_spec: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - - temp - - time - - temp_spec - - time_spec - - pressure - - reflux_solvent - - stir - - stir_speed - - purpose - title: HeatChill_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: HeatChill_Result - type: object - required: - - goal - title: HeatChill - type: object - type: HeatChill - HeatChillStartProtocol: - feedback: {} - goal: - purpose: purpose - temp: temp - vessel: vessel - goal_default: - purpose: '' - temp: 0.0 - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - status: - type: string - required: - - status - title: HeatChillStart_Feedback - type: object - goal: - properties: - purpose: - type: string - temp: - type: number - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - - temp - - purpose - title: HeatChillStart_Goal - type: object - result: - properties: - return_info: - type: string - success: - type: boolean - required: - - return_info - - success - title: HeatChillStart_Result - type: object - required: - - goal - title: HeatChillStart - type: object - type: HeatChillStart - HeatChillStopProtocol: - feedback: {} - goal: - vessel: vessel - goal_default: - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - status: - type: string - required: - - status - title: HeatChillStop_Feedback - type: object - goal: - properties: - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - title: HeatChillStop_Goal - type: object - result: - properties: - return_info: - type: string - success: - type: boolean - required: - - return_info - - success - title: HeatChillStop_Result - type: object - required: - - goal - title: HeatChillStop - type: object - type: HeatChillStop - HydrogenateProtocol: - feedback: {} - goal: - temp: temp - time: time - vessel: vessel - goal_default: - temp: '' - time: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: Hydrogenate_Feedback - type: object - goal: - properties: - temp: - type: string - time: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - temp - - time - - vessel - title: Hydrogenate_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Hydrogenate_Result - type: object - required: - - goal - title: Hydrogenate - type: object - type: Hydrogenate - PumpTransferProtocol: - feedback: {} - goal: - amount: amount - event: event - flowrate: flowrate - from_vessel: from_vessel - rate_spec: rate_spec - rinsing_repeats: rinsing_repeats - rinsing_solvent: rinsing_solvent - rinsing_volume: rinsing_volume - solid: solid - through: through - time: time - to_vessel: to_vessel - transfer_flowrate: transfer_flowrate - viscous: viscous - volume: volume - goal_default: - amount: '' - event: '' - flowrate: 0.0 - from_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - rate_spec: '' - rinsing_repeats: 0 - rinsing_solvent: '' - rinsing_volume: 0.0 - solid: false - through: '' - time: 0.0 - to_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - transfer_flowrate: 0.0 - viscous: false - volume: 0.0 - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVessel - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVessel - label: To Vessel - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Rinsing Solvent - output: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVesselOut - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVesselOut - label: To Vessel - placeholder_keys: - from_vessel: unilabos_nodes - to_vessel: unilabos_nodes - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_device: - type: string - status: - type: string - time_remaining: - properties: - nanosec: - maximum: 4294967295 - minimum: 0 - type: integer - sec: - maximum: 2147483647 - minimum: -2147483648 - type: integer - required: - - sec - - nanosec - title: time_remaining - type: object - time_spent: - properties: - nanosec: - maximum: 4294967295 - minimum: 0 - type: integer - sec: - maximum: 2147483647 - minimum: -2147483648 - type: integer - required: - - sec - - nanosec - title: time_spent - type: object - required: - - status - - current_device - - time_spent - - time_remaining - title: PumpTransfer_Feedback - type: object - goal: - properties: - amount: - type: string - event: - type: string - flowrate: - type: number - from_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: from_vessel - type: object - rate_spec: - type: string - rinsing_repeats: - maximum: 2147483647 - minimum: -2147483648 - type: integer - rinsing_solvent: - type: string - rinsing_volume: - type: number - solid: - type: boolean - through: - type: string - time: - type: number - to_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: to_vessel - type: object - transfer_flowrate: - type: number - viscous: - type: boolean - volume: - type: number - required: - - from_vessel - - to_vessel - - volume - - amount - - time - - viscous - - rinsing_solvent - - rinsing_volume - - rinsing_repeats - - solid - - flowrate - - transfer_flowrate - - rate_spec - - event - - through - title: PumpTransfer_Goal - type: object - result: - properties: - return_info: - type: string - success: - type: boolean - required: - - return_info - - success - title: PumpTransfer_Result - type: object - required: - - goal - title: PumpTransfer - type: object - type: PumpTransfer - RecrystallizeProtocol: - feedback: {} - goal: - ratio: ratio - solvent1: solvent1 - solvent2: solvent2 - vessel: vessel - volume: volume - goal_default: - ratio: '' - solvent1: '' - solvent2: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - volume: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - - data_key: solvent1 - data_source: handle - data_type: resource - handler_key: solvent1 - label: Solvent 1 - - data_key: solvent2 - data_source: handle - data_type: resource - handler_key: solvent2 - label: Solvent 2 - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: Recrystallize_Feedback - type: object - goal: - properties: - ratio: - type: string - solvent1: - type: string - solvent2: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - volume: - type: string - required: - - ratio - - solvent1 - - solvent2 - - vessel - - volume - title: Recrystallize_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Recrystallize_Result - type: object - required: - - goal - title: Recrystallize - type: object - type: Recrystallize - ResetHandlingProtocol: - feedback: {} - goal: - solvent: solvent - goal_default: - solvent: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Solvent - output: [] - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: ResetHandling_Feedback - type: object - goal: - properties: - solvent: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - solvent - - vessel - title: ResetHandling_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: ResetHandling_Result - type: object - required: - - goal - title: ResetHandling - type: object - type: ResetHandling - RunColumnProtocol: - feedback: {} - goal: - column: column - from_vessel: from_vessel - to_vessel: to_vessel - goal_default: - column: '' - from_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - pct1: '' - pct2: '' - ratio: '' - rf: '' - solvent1: '' - solvent2: '' - to_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVessel - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVessel - label: To Vessel - output: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVesselOut - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVesselOut - label: To Vessel - placeholder_keys: - column: unilabos_devices - from_vessel: unilabos_resources - to_vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: RunColumn_Feedback - type: object - goal: - properties: - column: - type: string - from_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: from_vessel - type: object - pct1: - type: string - pct2: - type: string - ratio: - type: string - rf: - type: string - solvent1: - type: string - solvent2: - type: string - to_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: to_vessel - type: object - required: - - from_vessel - - to_vessel - - column - - rf - - pct1 - - pct2 - - solvent1 - - solvent2 - - ratio - title: RunColumn_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: RunColumn_Result - type: object - required: - - goal - title: RunColumn - type: object - type: RunColumn - SeparateProtocol: - feedback: {} - goal: - from_vessel: from_vessel - product_phase: product_phase - purpose: purpose - repeats: repeats - separation_vessel: separation_vessel - settling_time: settling_time - solvent: solvent - solvent_volume: solvent_volume - stir_speed: stir_speed - stir_time: stir_time - through: through - to_vessel: to_vessel - waste_phase_to_vessel: waste_phase_to_vessel - goal_default: - from_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - product_phase: '' - product_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - purpose: '' - repeats: 0 - separation_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - settling_time: 0.0 - solvent: '' - solvent_volume: '' - stir_speed: 0.0 - stir_time: 0.0 - through: '' - to_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - volume: '' - waste_phase_to_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - waste_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVessel - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVessel - label: To Vessel - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Solvent - output: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVesselOut - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVesselOut - label: To Vessel - placeholder_keys: - from_vessel: unilabos_resources - to_vessel: unilabos_resources - waste_phase_to_vessel: unilabos_resources - waste_vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: Separate_Feedback - type: object - goal: - properties: - from_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: from_vessel - type: object - product_phase: - type: string - product_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: product_vessel - type: object - purpose: - type: string - repeats: - maximum: 2147483647 - minimum: -2147483648 - type: integer - separation_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: separation_vessel - type: object - settling_time: - type: number - solvent: - type: string - solvent_volume: - type: string - stir_speed: - type: number - stir_time: - type: number - through: - type: string - to_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: to_vessel - type: object - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - volume: - type: string - waste_phase_to_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: waste_phase_to_vessel - type: object - waste_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: waste_vessel - type: object - required: - - vessel - - purpose - - product_phase - - from_vessel - - separation_vessel - - to_vessel - - waste_phase_to_vessel - - product_vessel - - waste_vessel - - solvent - - solvent_volume - - volume - - through - - repeats - - stir_time - - stir_speed - - settling_time - title: Separate_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Separate_Result - type: object - required: - - goal - title: Separate - type: object - type: Separate - StartStirProtocol: - feedback: {} - goal: - purpose: purpose - stir_speed: stir_speed - vessel: vessel - goal_default: - purpose: '' - stir_speed: 0.0 - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_speed: - type: number - current_status: - type: string - progress: - type: number - required: - - progress - - current_speed - - current_status - title: StartStir_Feedback - type: object - goal: - properties: - purpose: - type: string - stir_speed: - type: number - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - - stir_speed - - purpose - title: StartStir_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: StartStir_Result - type: object - required: - - goal - title: StartStir - type: object - type: StartStir - StirProtocol: - feedback: {} - goal: - event: event - settling_time: settling_time - stir_speed: stir_speed - stir_time: stir_time - time: time - time_spec: time_spec - vessel: vessel - goal_default: - event: '' - settling_time: '' - stir_speed: 0.0 - stir_time: 0.0 - time: '' - time_spec: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - status: - type: string - required: - - status - title: Stir_Feedback - type: object - goal: - properties: - event: - type: string - settling_time: - type: string - stir_speed: - type: number - stir_time: - type: number - time: - type: string - time_spec: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - - time - - event - - time_spec - - stir_time - - stir_speed - - settling_time - title: Stir_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Stir_Result - type: object - required: - - goal - title: Stir - type: object - type: Stir - StopStirProtocol: - feedback: {} - goal: - vessel: vessel - goal_default: - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - output: - - data_key: vessel - data_source: executor - data_type: resource - handler_key: VesselOut - label: Vessel - placeholder_keys: - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_status: - type: string - progress: - type: number - required: - - progress - - current_status - title: StopStir_Feedback - type: object - goal: - properties: - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - required: - - vessel - title: StopStir_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: StopStir_Result - type: object - required: - - goal - title: StopStir - type: object - type: StopStir - TransferProtocol: - feedback: {} - goal: - amount: amount - from_vessel: from_vessel - rinsing_repeats: rinsing_repeats - rinsing_solvent: rinsing_solvent - rinsing_volume: rinsing_volume - solid: solid - time: time - to_vessel: to_vessel - viscous: viscous - volume: volume - goal_default: - amount: '' - from_vessel: '' - rinsing_repeats: 0 - rinsing_solvent: '' - rinsing_volume: 0.0 - solid: false - time: 0.0 - to_vessel: '' - viscous: false - volume: 0.0 - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVessel - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVessel - label: To Vessel - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Rinsing Solvent - output: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: FromVesselOut - label: From Vessel - - data_key: vessel - data_source: executor - data_type: resource - handler_key: ToVesselOut - label: To Vessel - placeholder_keys: - from_vessel: unilabos_nodes - to_vessel: unilabos_nodes - result: {} - schema: - description: '' - properties: - feedback: - properties: - current_status: - type: string - progress: - type: number - transferred_volume: - type: number - required: - - progress - - transferred_volume - - current_status - title: Transfer_Feedback - type: object - goal: - properties: - amount: - type: string - from_vessel: - type: string - rinsing_repeats: - maximum: 2147483647 - minimum: -2147483648 - type: integer - rinsing_solvent: - type: string - rinsing_volume: - type: number - solid: - type: boolean - time: - type: number - to_vessel: - type: string - viscous: - type: boolean - volume: - type: number - required: - - from_vessel - - to_vessel - - volume - - amount - - time - - viscous - - rinsing_solvent - - rinsing_volume - - rinsing_repeats - - solid - title: Transfer_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: Transfer_Result - type: object - required: - - goal - title: Transfer - type: object - type: Transfer - WashSolidProtocol: - feedback: {} - goal: - filtrate_vessel: filtrate_vessel - repeats: repeats - solvent: solvent - stir: stir - stir_speed: stir_speed - temp: temp - time: time - vessel: vessel - volume: volume - goal_default: - event: '' - filtrate_vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - mass: '' - repeats: 0 - repeats_spec: '' - solvent: '' - stir: false - stir_speed: 0.0 - temp: 0.0 - time: '' - vessel: - category: '' - children: [] - config: '' - data: '' - id: '' - name: '' - parent: '' - pose: - orientation: - w: 1.0 - x: 0.0 - y: 0.0 - z: 0.0 - position: - x: 0.0 - y: 0.0 - z: 0.0 - sample_id: '' - type: '' - volume: '' - volume_spec: '' - handles: - input: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: Vessel - label: Vessel - - data_key: solvent - data_source: handle - data_type: resource - handler_key: solvent - label: Solvent - - data_key: filtrate_vessel - data_source: handle - data_type: resource - handler_key: filtrate_vessel - label: Filtrate Vessel - output: - - data_key: vessel - data_source: handle - data_type: resource - handler_key: VesselOut - label: Vessel Out - - data_key: filtrate_vessel - data_source: executor - data_type: resource - handler_key: filtrate_vessel_out - label: Filtrate Vessel - placeholder_keys: - filtrate_vessel: unilabos_resources - vessel: unilabos_resources - result: {} - schema: - description: '' - properties: - feedback: - properties: - progress: - type: number - status: - type: string - required: - - status - - progress - title: WashSolid_Feedback - type: object - goal: - properties: - event: - type: string - filtrate_vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: filtrate_vessel - type: object - mass: - type: string - repeats: - maximum: 2147483647 - minimum: -2147483648 - type: integer - repeats_spec: - type: string - solvent: - type: string - stir: - type: boolean - stir_speed: - type: number - temp: - type: number - time: - type: string - vessel: - properties: - category: - type: string - children: - items: - type: string - type: array - config: - type: string - data: - type: string - id: - type: string - name: - type: string - parent: - type: string - pose: - properties: - orientation: - properties: - w: - type: number - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - - w - title: orientation - type: object - position: - properties: - x: - type: number - y: - type: number - z: - type: number - required: - - x - - y - - z - title: position - type: object - required: - - position - - orientation - title: pose - type: object - sample_id: - type: string - type: - type: string - required: - - id - - name - - sample_id - - children - - parent - - type - - category - - pose - - config - - data - title: vessel - type: object - volume: - type: string - volume_spec: - type: string - required: - - vessel - - solvent - - volume - - filtrate_vessel - - temp - - stir - - stir_speed - - time - - repeats - - volume_spec - - repeats_spec - - mass - - event - title: WashSolid_Goal - type: object - result: - properties: - message: - type: string - return_info: - type: string - success: - type: boolean - required: - - success - - message - - return_info - title: WashSolid_Result - type: object - required: - - goal - title: WashSolid - type: object - type: WashSolid - auto-create_ros_action_server: - feedback: {} - goal: {} - goal_default: - action_name: null - action_value_mapping: null - handles: {} - result: {} - schema: - description: create_ros_action_server的参数schema - properties: - feedback: {} - goal: - properties: - action_name: - type: string - action_value_mapping: - type: string - required: - - action_name - - action_value_mapping - type: object - result: {} - required: - - goal - title: create_ros_action_server参数 - type: object - type: UniLabJsonCommand - auto-execute_single_action: - feedback: {} - goal: {} - goal_default: - action_kwargs: null - action_name: null - device_id: null - handles: {} - result: {} - schema: - description: execute_single_action的参数schema - properties: - feedback: {} - goal: - properties: - action_kwargs: - type: string - action_name: - type: string - device_id: - type: string - required: - - device_id - - action_name - - action_kwargs - type: object - result: {} - required: - - goal - title: execute_single_action参数 - type: object - type: UniLabJsonCommandAsync - auto-initialize_device: - feedback: {} - goal: {} - goal_default: - device_config: null - device_id: null - handles: {} - result: {} - schema: - description: initialize_device的参数schema - properties: - feedback: {} - goal: - properties: - device_config: - type: string - device_id: - type: string - required: - - device_id - - device_config - type: object - result: {} - required: - - goal - title: initialize_device参数 - type: object - type: UniLabJsonCommand - module: unilabos.ros.nodes.presets.workstation:ROS2WorkstationNode - status_types: {} - type: ros2 - config_info: [] - description: Workstation - handles: [] - icon: '' - init_param_schema: - config: - properties: - action_value_mappings: - type: object - children: - type: object - device_id: - type: string - driver_instance: - type: string - hardware_interface: - type: object - print_publish: - default: true - type: string - protocol_type: - items: - type: string - type: array - resource_tracker: - type: string - status_types: - type: object - required: - - protocol_type - - children - - driver_instance - - device_id - - status_types - - action_value_mappings - - hardware_interface - type: object - data: - properties: {} - required: [] - type: object - version: 1.0.0 -workstation.example: - category: - - work_station - class: - action_value_mappings: {} - module: unilabos.devices.workstation.workstation_base:WorkstationExample - status_types: {} - type: python - config_info: [] - description: '' - handles: [] - icon: '' - init_param_schema: - config: - properties: - station_resource: - type: object - required: - - station_resource - type: object - data: - properties: {} - required: [] - type: object - version: 1.0.0 diff --git a/unilabos/registry/resources/bioyond/YB_bottle.yaml b/unilabos/registry/resources/bioyond/YB_bottle.yaml index 51ea6e4..6d8a380 100644 --- a/unilabos/registry/resources/bioyond/YB_bottle.yaml +++ b/unilabos/registry/resources/bioyond/YB_bottle.yaml @@ -1,58 +1,7 @@ -YB_fen_ye_5ml_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.YB_bottles:YB_fen_ye_5ml_Bottle - type: pylabrobot - description: YB_fen_ye_5ml_Bottle - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 - -YB_fen_ye_20ml_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.YB_bottles:YB_fen_ye_20ml_Bottle - type: pylabrobot - description: YB_fen_ye_20ml_Bottle - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 - -YB_pei_ye_xiao_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.YB_bottles:YB_pei_ye_xiao_Bottle - type: pylabrobot - description: YB_pei_ye_xiao_Bottle - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 - -YB_pei_ye_da_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.YB_bottles:YB_pei_ye_da_Bottle - type: pylabrobot - description: YB_pei_ye_da_Bottle - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 - YB_Pipette_Tip: category: - yb3 + - YB_bottle class: module: unilabos.resources.bioyond.YB_bottles:YB_Pipette_Tip type: pylabrobot @@ -62,4 +11,55 @@ YB_Pipette_Tip: init_param_schema: {} registry_type: resource version: 1.0.0 - +YB_fen_ye_20ml_Bottle: + category: + - yb3 + - YB_bottle + class: + module: unilabos.resources.bioyond.YB_bottles:YB_fen_ye_20ml_Bottle + type: pylabrobot + description: YB_fen_ye_20ml_Bottle + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 +YB_fen_ye_5ml_Bottle: + category: + - yb3 + - YB_bottle + class: + module: unilabos.resources.bioyond.YB_bottles:YB_fen_ye_5ml_Bottle + type: pylabrobot + description: YB_fen_ye_5ml_Bottle + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 +YB_pei_ye_da_Bottle: + category: + - yb3 + - YB_bottle + class: + module: unilabos.resources.bioyond.YB_bottles:YB_pei_ye_da_Bottle + type: pylabrobot + description: YB_pei_ye_da_Bottle + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 +YB_pei_ye_xiao_Bottle: + category: + - yb3 + - YB_bottle + class: + module: unilabos.resources.bioyond.YB_bottles:YB_pei_ye_xiao_Bottle + type: pylabrobot + description: YB_pei_ye_xiao_Bottle + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 diff --git a/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml b/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml index 1c5b029..eb2ff6f 100644 --- a/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml +++ b/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml @@ -1,59 +1,7 @@ - -YB_6StockCarrier: - category: - - yb3 - class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6StockCarrier - type: pylabrobot - description: YB_6StockCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 - -YB_6VialCarrier: - category: - - yb3 - class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6VialCarrier - type: pylabrobot - description: YB_6VialCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 - -YB_1BottleCarrier: - category: - - yb3 - class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_1BottleCarrier - type: pylabrobot - description: YB_1BottleCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 - -YB_1GaoNianYeBottleCarrier: - category: - - yb3 - class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_1GaoNianYeBottleCarrier - type: pylabrobot - description: YB_1GaoNianYeBottleCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 - YB_1Bottle100mlCarrier: category: - yb3 + - YB_bottle_carriers class: module: unilabos.resources.bioyond.YB_bottle_carriers:YB_1Bottle100mlCarrier type: pylabrobot @@ -63,49 +11,36 @@ YB_1Bottle100mlCarrier: init_param_schema: {} registry_type: resource version: 1.0.0 - -YB_6x5ml_DispensingVialCarrier: +YB_1BottleCarrier: category: - yb3 + - YB_bottle_carriers class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6x5ml_DispensingVialCarrier + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_1BottleCarrier type: pylabrobot - description: YB_6x5ml_DispensingVialCarrier + description: YB_1BottleCarrier handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 - -YB_6x20ml_DispensingVialCarrier: +YB_1GaoNianYeBottleCarrier: category: - yb3 + - YB_bottle_carriers class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6x20ml_DispensingVialCarrier + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_1GaoNianYeBottleCarrier type: pylabrobot - description: YB_6x20ml_DispensingVialCarrier + description: YB_1GaoNianYeBottleCarrier handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 - -YB_6x_SmallSolutionBottleCarrier: - category: - - yb3 - class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6x_SmallSolutionBottleCarrier - type: pylabrobot - description: YB_6x_SmallSolutionBottleCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 - YB_4x_LargeSolutionBottleCarrier: category: - yb3 + - YB_bottle_carriers class: module: unilabos.resources.bioyond.YB_bottle_carriers:YB_4x_LargeSolutionBottleCarrier type: pylabrobot @@ -115,23 +50,75 @@ YB_4x_LargeSolutionBottleCarrier: init_param_schema: {} registry_type: resource version: 1.0.0 - -YB_jia_yang_tou_da_1X1_carrier: +YB_6StockCarrier: category: - yb3 + - YB_bottle_carriers class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_jia_yang_tou_da_1X1_carrier + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6StockCarrier type: pylabrobot - description: YB_jia_yang_tou_da_1X1_carrier + description: YB_6StockCarrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 +YB_6VialCarrier: + category: + - yb3 + - YB_bottle_carriers + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6VialCarrier + type: pylabrobot + description: YB_6VialCarrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 +YB_6x20ml_DispensingVialCarrier: + category: + - yb3 + - YB_bottle_carriers + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6x20ml_DispensingVialCarrier + type: pylabrobot + description: YB_6x20ml_DispensingVialCarrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 +YB_6x5ml_DispensingVialCarrier: + category: + - yb3 + - YB_bottle_carriers + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6x5ml_DispensingVialCarrier + type: pylabrobot + description: YB_6x5ml_DispensingVialCarrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 +YB_6x_SmallSolutionBottleCarrier: + category: + - yb3 + - YB_bottle_carriers + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6x_SmallSolutionBottleCarrier + type: pylabrobot + description: YB_6x_SmallSolutionBottleCarrier handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 - YB_AdapterBlock: category: - yb3 + - YB_bottle_carriers class: module: unilabos.resources.bioyond.YB_bottle_carriers:YB_AdapterBlock type: pylabrobot @@ -141,10 +128,10 @@ YB_AdapterBlock: init_param_schema: {} registry_type: resource version: 1.0.0 - YB_TipBox: category: - yb3 + - YB_bottle_carriers class: module: unilabos.resources.bioyond.YB_bottle_carriers:YB_TipBox type: pylabrobot @@ -154,36 +141,10 @@ YB_TipBox: init_param_schema: {} registry_type: resource version: 1.0.0 - -YB_ye_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.YB_bottles:YB_ye_Bottle - type: pylabrobot - description: YB_ye_Bottle - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 - -YB_ye_100ml_Bottle: - category: - - yb3 - class: - module: unilabos.resources.bioyond.YB_bottles:YB_ye_100ml_Bottle - type: pylabrobot - description: YB_ye_100ml_Bottle - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 - YB_gao_nian_ye_Bottle: category: - yb3 + - YB_bottle_carriers class: module: unilabos.resources.bioyond.YB_bottles:YB_gao_nian_ye_Bottle type: pylabrobot @@ -193,10 +154,10 @@ YB_gao_nian_ye_Bottle: init_param_schema: {} registry_type: resource version: 1.0.0 - YB_jia_yang_tou_da: category: - yb3 + - YB_bottle_carriers class: module: unilabos.resources.bioyond.YB_bottles:YB_jia_yang_tou_da type: pylabrobot @@ -206,3 +167,42 @@ YB_jia_yang_tou_da: init_param_schema: {} registry_type: resource version: 1.0.0 +YB_jia_yang_tou_da_1X1_carrier: + category: + - yb3 + - YB_bottle_carriers + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_jia_yang_tou_da_1X1_carrier + type: pylabrobot + description: YB_jia_yang_tou_da_1X1_carrier + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 +YB_ye_100ml_Bottle: + category: + - yb3 + - YB_bottle_carriers + class: + module: unilabos.resources.bioyond.YB_bottles:YB_ye_100ml_Bottle + type: pylabrobot + description: YB_ye_100ml_Bottle + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 +YB_ye_Bottle: + category: + - yb3 + - YB_bottle_carriers + class: + module: unilabos.resources.bioyond.YB_bottles:YB_ye_Bottle + type: pylabrobot + description: YB_ye_Bottle + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 From fe4e49e56d69cb7257ef1b597add40c671a17283 Mon Sep 17 00:00:00 2001 From: calvincao Date: Fri, 31 Oct 2025 13:53:58 +0800 Subject: [PATCH 035/104] =?UTF-8?q?feat(workstation):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=20Bioyond=20=E5=92=8C=20Coin=20Cell=20=E7=BB=84=E8=A3=85?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E7=AB=99=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改 Bioyond Studio 配置文件中的 API 主机地址 - 更新 bioyond_cell_workstation.py 中的默认模板路径 - 新增物料模板文件 material_template.xlsx - 扩展 func_pack_send_msg_cmd 函数以支持 assembly_pressure 参数 - 更新 coin_cell_workstation.yaml 文件以包含 assembly_pressure 的默认值和类型定义 --- .../bioyond_cell/bioyond_cell_workstation.py | 2 +- ...品导入模板.xlsx => material_template.xlsx} | Bin 22129 -> 22168 bytes .../workstation/bioyond_studio/config.py | 2 +- .../coin_cell_assembly/coin_cell_assembly.py | 15 +++++++++------ .../devices/coin_cell_workstation.yaml | 8 ++++++++ 5 files changed, 19 insertions(+), 8 deletions(-) rename unilabos/devices/workstation/bioyond_studio/bioyond_cell/{样品导入模板.xlsx => material_template.xlsx} (88%) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index ff29574..5ce49e0 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -253,7 +253,7 @@ class BioyondCellWorkstation(BioyondWorkstation): def auto_feeding4to3( self, # ★ 修改点:默认模板路径 - xlsx_path: Optional[str] = "unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\样品导入模板.xlsx", + xlsx_path: Optional[str] = "/Users/calvincao/Desktop/work/uni-lab-all/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx", # ---------------- WH4 - 加样头面 (Z=1, 12个点位) ---------------- WH4_x1_y1_z1_1_materialName: str = "", WH4_x1_y1_z1_1_quantity: float = 0.0, WH4_x2_y1_z1_2_materialName: str = "", WH4_x2_y1_z1_2_quantity: float = 0.0, diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/样品导入模板.xlsx b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx similarity index 88% rename from unilabos/devices/workstation/bioyond_studio/bioyond_cell/样品导入模板.xlsx rename to unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx index 0d8d0e297d6ff5c2d304bc438c499a27da375b01..844fc84d932f618891abbdf6efdefc4bcf16289f 100644 GIT binary patch delta 128 zcmeykhH=JP#tr?de40E$H#?*l7(iHpVe*9#smFu?W$XZCM#I-ZC+quEyD>{GWkhJ vIHSa5n@}6Zvy&@g-0L> diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 52feff6..504cf45 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -8,7 +8,7 @@ import os # BioyondCellWorkstation 默认配置(包含所有必需参数) API_CONFIG = { # API 连接配置 - "api_host": os.getenv("BIOYOND_API_HOST", "http://172.21.32.103:44388"), + "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.10.169:44388"), "api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"), "timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")), diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index e520628..d8bb597 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -766,7 +766,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): # self.success = True # return self.success - def func_pack_send_msg_cmd(self, elec_use_num, elec_vol, assembly_type) -> bool: + def func_pack_send_msg_cmd(self, elec_use_num, elec_vol, assembly_type, assembly_pressure) -> bool: """UNILAB写参数""" while (self.request_rec_msg_status) == False: print("wait for request_rec_msg_status to True") @@ -782,6 +782,9 @@ class CoinCellAssemblyWorkstation(WorkstationBase): #发送电解液组装类型 self._unilab_send_msg_assembly_type(assembly_type) time.sleep(1) + #发送电池压制力 + self._unilab_send_msg_assembly_pressure(assembly_pressure) + time.sleep(1) self._unilab_send_msg_succ_cmd(True) time.sleep(1) while (self.request_rec_msg_status) == True: @@ -895,16 +898,16 @@ class CoinCellAssemblyWorkstation(WorkstationBase): self.client.use_node('REG_MSG_NE_PLATE_MATRIX').write(fujipian_juzhendianwei) self.client.use_node('REG_MSG_SEPARATOR_PLATE_NUM').write(gemopanshu) self.client.use_node('REG_MSG_SEPARATOR_PLATE_MATRIX').write(gemo_juzhendianwei) - self.client.use_node('COIL_ALUMINUM_FOIL').write(lvbodian) - self.client.use_node('REG_MSG_PRESS_MODE').write(battery_pressure_mode) + self.client.use_node('COIL_ALUMINUM_FOIL').write(not lvbodian) + self.client.use_node('REG_MSG_PRESS_MODE').write(not battery_pressure_mode) # self.client.use_node('REG_MSG_ASSEMBLY_PRESSURE').write(battery_pressure) self.client.use_node('REG_MSG_BATTERY_CLEAN_IGNORE').write(battery_clean_ignore) self.success = True return self.success - def func_allpack_cmd(self, elec_num, elec_use_num, elec_vol:int=50, assembly_type:int=7, file_path: str="D:\\coin_cell_data") -> bool: - elec_num, elec_use_num, elec_vol, assembly_type = int(elec_num), int(elec_use_num), int(elec_vol), int(assembly_type) + def func_allpack_cmd(self, elec_num, elec_use_num, elec_vol:int=50, assembly_type:int=7, assembly_pressure:int=4200, file_path: str="D:\\coin_cell_data") -> bool: + elec_num, elec_use_num, elec_vol, assembly_type, assembly_pressure = int(elec_num), int(elec_use_num), int(elec_vol), int(assembly_type), int(assembly_pressure) summary_csv_file = os.path.join(file_path, "duandian.csv") # 如果断点文件存在,先读取之前的进度 if os.path.exists(summary_csv_file): @@ -954,7 +957,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): print(f"开始第{last_i+i+1}瓶电解液的组装") #第一个循环从上次断点继续,后续循环从0开始 j_start = last_j if i == last_i else 0 - self.func_pack_send_msg_cmd(elec_use_num-j_start, elec_vol, assembly_type) + self.func_pack_send_msg_cmd(elec_use_num-j_start, elec_vol, assembly_type, assembly_pressure) for j in range(j_start, elec_use_num): print(f"开始第{last_i+i+1}瓶电解液的第{j+j_start+1}个电池组装") diff --git a/unilabos/registry/devices/coin_cell_workstation.yaml b/unilabos/registry/devices/coin_cell_workstation.yaml index 7e541eb..4738946 100644 --- a/unilabos/registry/devices/coin_cell_workstation.yaml +++ b/unilabos/registry/devices/coin_cell_workstation.yaml @@ -74,6 +74,7 @@ coincellassemblyworkstation_device: feedback: {} goal: {} goal_default: + assembly_pressure: 4200 assembly_type: 7 elec_num: null elec_use_num: null @@ -88,6 +89,9 @@ coincellassemblyworkstation_device: feedback: {} goal: properties: + assembly_pressure: + default: 4200 + type: integer assembly_type: default: 7 type: integer @@ -291,6 +295,7 @@ coincellassemblyworkstation_device: feedback: {} goal: {} goal_default: + assembly_pressure: null assembly_type: null elec_use_num: null elec_vol: null @@ -303,6 +308,8 @@ coincellassemblyworkstation_device: feedback: {} goal: properties: + assembly_pressure: + type: string assembly_type: type: string elec_use_num: @@ -313,6 +320,7 @@ coincellassemblyworkstation_device: - elec_use_num - elec_vol - assembly_type + - assembly_pressure type: object result: {} required: From 2eb9986edb662c73d7f91369d24df48a0b456f75 Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Fri, 31 Oct 2025 13:54:58 +0800 Subject: [PATCH 036/104] 123 --- .../workstation/coin_cell_assembly.zip | Bin 0 -> 19794 bytes .../{coin_cell_assembly => }/__init__.py | 0 .../button_battery_station.py | 0 .../coin_cell_assembly.py | 14 +- .../coin_cell_assembly_a.csv | 0 .../new_cellconfig3c.json | 0 .../coin_cell_assembly/workstation_base.py | 489 ------------------ 7 files changed, 7 insertions(+), 496 deletions(-) create mode 100644 unilabos/devices/workstation/coin_cell_assembly.zip rename unilabos/devices/workstation/coin_cell_assembly/{coin_cell_assembly => }/__init__.py (100%) rename unilabos/devices/workstation/coin_cell_assembly/{coin_cell_assembly => }/button_battery_station.py (100%) rename unilabos/devices/workstation/coin_cell_assembly/{coin_cell_assembly => }/coin_cell_assembly.py (99%) rename unilabos/devices/workstation/coin_cell_assembly/{coin_cell_assembly => }/coin_cell_assembly_a.csv (100%) rename unilabos/devices/workstation/coin_cell_assembly/{coin_cell_assembly => }/new_cellconfig3c.json (100%) delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/workstation_base.py diff --git a/unilabos/devices/workstation/coin_cell_assembly.zip b/unilabos/devices/workstation/coin_cell_assembly.zip new file mode 100644 index 0000000000000000000000000000000000000000..b95b7f4a3f731c9d33a00a977343af3c5962ded5 GIT binary patch literal 19794 zcmZsiLv$|;?51nmwr#t;wQbwBZQHhO+s3c$EpBZa^Ua+9IkT9_BFQG}H_4MI%7B8Q z0sU{Yh||^iKgs_%)c>N1gO$C3iJ7gffsu=gnVqq%7o+0;6hHx;RO;yHw%|Ae;{XAL zhX4W5{r?M$-CSKA>=_)ruJNs0w#A-&EItTBu^@2qOioGW zh|IYl%{#+!S+L`bX`=?)`PxC8c=Dhn0HMj{LJd!W7}_9Dk}zd*E{g$!1w_qx3SZ!V z36|?ylWWNs=w+AXjc@z;|895O_W9k`khsFSZ?0u7<*nU9*QcD4U#X-%k zVolzl@$}UqK3^=PUT2#sp8HWD-If|^t+Fg5j_yC8Eq>i5BJJIx9vjS4+UtS*o*~9a%NOlcXMX&^E$Ox= z7Fb7RSRng+y-C`qbg=w`&(&{$!;qOxE`=x^$I5vnD3F0NW}G~zSt5QjlB{3nEcBAJ z@-m%(r>OL}%IWac>SZY}9oxCa6j_9>3TwW|s8$CPTwTB`?Qzf%@vEOxZ}468b4?i_ z=fB*0kN@82^`ZH=uk~iwuxmeyCqmfYCo5pq9vLrI#6E-lc;Q!c*{V3 zOM(%UJt)4EkAnJ=HRZX5wX%TGKDlj3BpyYU5Q5txH4x7lB>GH2jS;S5QzK(KnPf?7 zO4kj&y_u)pl=Y`sTWBk^gAS69%~;*q{#UhsN+pC&Ixol9M4!Y9MwY{lP>|(5noi;=F7S9ektRt$W4GJMX+^V!r_4uJvR2xR}#FiWPeXrZh3~p%B|KkP?fcY z$g-&5hv=P*TDXj0HEUA2d3VQ=K)B3wS?iaKU3X^$9oAVY$;BLda-KE0MGbwFhu~^5 z1G0?M!0%87i;wMdB+*WPH$2`N9*Wr76Sj@^s^>jT>t-pBH^nLihH~WAtG$Z7_g3`DHqn z-w(wdGRGc$1^ym3hcz27r@`SQgxMk%j1}%yc1KWt88(Z6_EgCU2}LTCyG1oS!r!W1 z7or;V;NumLflF(^&cKMGvZ&9~>2#Xw_l%}LDYeQjw7Q_*w7_7u^JDMw;QBaET)EBn zMD7|?o#Qdj#^}4HtJ{V+4_evx6UWaAyWQt>Rq@d!GY2ck&QOn3PedxzGWXBp%H}dB zLtzUO^Qw%Ss;MDBr$9IGWNwOaDs#p2!k5zC<#RvP@0tUMQyr0G;B9ry@2R3V1X_Do zRe0z8Sk~uQ)8#&y|5fI(<@uvY5a>3*$=PU9i@oPByIgl(T1%ZJhI741f!H zLQS_5-fUulRBBhObMhB(dSF%-@y<(UTlH`3E#)d(|u$q@-I{ z(U|xcwUtjgt1H%89q!oguGF4$; z-CrAi5=x#x_QQkUM!~vrr`+dv(fj)i<=f9{_s6#W=Mp@TNfL}ffuo`!5bdufZ=cu6 z{`iAhac<=Iyp#_jbRtU`*(O9aab}UcS*x6|%s-dOv9vhBeND%1uj_>I4|bN4xMTE6 zf~}s@{*9cT><7VO>U(i3x>|t2m)H}r?0kN9`>Fh9g!5JB1X zfgUke`|s6azb6SUDimkoIwoylMshwX@-oUYI6(nhcazf6V(*hjXpJ#65iSl#B5>A- zZ@b*QpD`i z4&T}aeSx7XQ;*Mk@hfAY=lXnd8En)rUxwqZ0;RefGF`r-dEl3)4jUE?+;4$I*0p(o ztOAoLB-xQ%?=}xA31UXLOAf}RLL5iWPPS4ddFTIr{Ehy3opA@^nv`g?)*H|cMj){g z$|`0`Ys~~v0@&R_tF4XGHi|k?9|;wh4m^Gl?!pfW9chRV8gK6H6)8fY7athsy9|P$ zoA1Ut%0dv3@{?{#QbHz9XOz;mkn*E=?HL0Q8~t@na3T!-1{pi7YE5LY={MGO9nUN_ zsnB$Vzk1axFP3h&n`iHLT(DGK7n;g|Xh$x`UK*e`iS&Vf4~by^fFW^$G7%=F7!|&) z1|M8>)W_&STu*ykQrd4z43PNtc|C|fu6JS7AxYnkS*zjDA{2R{zOT8TnjbMIN>NBb zqi86pD)IEJWT~Qoix79Bl7k>AL^k8OR*o$tp0mDOz)0dJzJ)f@(}OR975*kAIikTG z-VPnaXE&a!l9SU-{v#cXkhG@x%2$B4k=l-@-7RusFJcg^C!3TPCQqP6UwX#6pv*#JPeAnal#n^1NEb84Bz@IgeYS< z7pg;#b{&SRPQ%M!%52WDB` zgMK=)OGLry7P%ng7OOvTNgCO4vTQL~-S1rFWR7jy{i%|E5O*crTJJebnJ;Fdw{hr! zFH?VDf1`7bL9-)bmZQ+hYyp+841=|U#XP7#DiftkiYm?>)I}AC$(bZ8^;f0DqsNC= zk~bGtwB#0aOSmIa5FPok*iai<;RMp7Ux+v&%nijkkX$?h>dEgN7OHad6mht#7%>_M z#w!m){Qwlc-Io=XTH?7S1qh}bQxi(D?Yr?qk4tfID_irH%osHFBBuF`xl)@x(+Tuu z38GJC!;^y_;u-Knf+1vghg+PVz!tCqMCi+`j;8XOQ$|$BVHs zXQu=XP8>`Y%UVe3WeoCoADn4kB7UNBEh5kNW#@4GqU@tiE%97vKC`}` z@@wVf^dL`EI(Q-Yp75}?;*pE_Rz3_{Jkd@<{P}RoMK7`;vJvgspa`9x=p9eORQHfe zMD%l=a?~u8l4vxEuH{Mx1o1*3Hb)}yc9N7&Y;_D>Nd9oPohjl?`rj$KRwU!W&Tyzj zxhkAWX=AG#8ULF$AmRf9nL_`SDs=aU?1J`5d2N8hq-=l9Nhh+hTW$DdYDm4Vy0B)I z#WXw6eg_~x;)K*%NO=;a9Y_RW3GG<+$pq1d4917*0*YeLgB57JGKbp*bbH{hJWR46 zVhP>afGKX?qkHpu>x1wD)L>UYjdh&ShHM8?EXOr{8=lV5S;Iy^5y2u1*0eQ#iZPf8Bd$S6Rijl&ugX3eu4X|3|L zMMxGyUl>^oi+D*MS!={<)}4P)k@j%tgu20uBT(cSb)kfN?}Q=;Mi7!t0fG(o8*f5s zs1w*J|82d(YRlrO#vg@WoNtOH0pXpQ3@5%kAkt(P1XSwZHyGR-oc(mD&M_nT3;{mv za-tbU94=;=7&ju54z!MX4=@e~pRTd2x-+#OcOKv}r-8yd?*smd(n$Ha6Sy=>+=wUIsIex@F3!^}bzt6ar%Lz3I;uCZXzJJlI{}a=F!5<% z3yPpEkhy$GAWcKGKa#EKq^m96Y;ukD^Payo!&hL7g$409lAFkLn@hlIfyM|LM^{|* zjm8joZ4Kphl^GRWZ_I>&wavmf)J-SLB76&D?5drT8v8IS#5i@LtQ8%QZRSRiZjECl04N z5$ucjzb{G;>I@T!hfS^g#IPsE6O2)t86C(k^)$2vjZo1VLU|mEA=$X4U^)k1Poshf z2O6j#yY@e@5ggMk4Q4D_+~Ot&L<`U2%H;>jEozRrzi+VGuwR^$3PxFHmGYD#2V?qS_i}H|2Ajk@ZT!%oY+u!`d>+_SW#Zr%lMGLef}{fD3kW-n`HC+X#ZLaj4g`%@h396p(c`lWc}) zV7Z`lNS=#0^Y_8QfY`fvC?N0L#S^52UQXQS7U&1wttD#=f<}2+or_bW`MS-(wL{yu z%dwqj>F6F#$37ACESt z@agF=%Pq9FRCbUu$Q*AP24n)YzTB~U?znRX)K{28_G zyYY{D2~^@yA$+?jMTm*r)}ai^U89i}m0td6!$&AWm7gLr--$6rFo3Gm!a3-K7$L@d_>*m@vRU?jXW7s8%J2NShJo) zVh@&!>t-dtlSBfLV&?Ve=7yR9fpge}`j-CtJbF`bbQv#P!Z-?drZc%0m%JRrHgG8? z(}9#E0s~$UeM~_q2RtUV($usZ6T;QGmyi8=Ka^FAMQ&ukuQ%=6u=eo*>0V5WRM>$L(KQVJS?_jDCT|Jo$anC zckRsH0D7a&N=CWg99pQlLMm1AEQ9IhTB>U_ATe!YCuDaE7ijX*Byl1f9A|R^+R1C2}sZoIM}X#=V~t zXpvBY4F=K(O|ZFE)x*=~$D`(}iyX~gn@hYv85%>K6JdTynQr(pdO(1G<+PdT@7B_* zIVg<$f(k2)6u`Y^NJm(246e^5A#t24mZm#;ZC5Kf;ut?En7)+i_PhnfLzeS)#YI|Y3rjfR z1>th&JrPdB=twzVl0x^2SPh0Ylu~7FLJxwBLsNjc@HnTDk+E+|Eh*vfHLxyOcq@$a~8&j91DZ`RU6M^u>)_fO(MP}O4P z1{QX6R};*Y$BYzRLJG*GY2C+h|3Cxf`5Cp9ra#1h$Xs4kYk?|0PHaMLO&aNPN05)Txx&WAq`(uzZj08vEbOVTY) z!C#7>_)ebYxah{ZdZOSIksn(*3z5}Lp>2@uA{uYJ@Y>-Kpg<|rE<&0pV1Vubh?f}| zArgPc`RmBs*gxDF!9YJ@qq}W7^jr65R-NQw1S`ertc!_mV7bi^aI<<5d8Y+um5<~d zCgKYCdZ%e0w7EhPi|||~C5cwXhsyriU*>7(EcHROX6h$IK@ALvFD0ATpjc|(04)AN z5yl|t+QRnMs?+-zL~nvelHM_TX4(3S8%~78n06-Tyo*4tN5SPSm-n%s9gjI-P|s~V4ZlFY@uQe9tgg}6 zhBG@vD-O~7an@A{Ii}EU$L-X!M_KQz8gTFa)5KxbMhxh%$}kZ}*E8WZ5NM$^QFY0G z)98%|S1y$2E+EuLzcNK=+>VG0YdA)K}hux|J3#$v4na`+$ z%(L*sM!2{|-~6hevBk2|ehRJAfjHkyxS>ya*k)y*R5Rui&}X75gf0pRCIcuT5JuhX zqs@C_{`lew2C>J{^%O&ZLMzWLN$6vTmumc>jXR67@90x`7d9l{#`(ca3L@He;LGM` z>i-3Oqo*9hSh}@QGK8G49(iG~8e@09L)>n!6y;+!cj!iBC5wsM0P>4dj>*K;d(VLH zY&qO~M@*;PUy)(b{1Ou{{zjcz7i!}YLWMg6EwQyESDNy?)Tt9Ku#j91_uwWcy_70= za>_6o56Zd7oC+1-;A?gl*~*w=3eX)|oMjo`kt3+!4OyRrODEe*_5s~ZD11d3#14}b z0};+C3`6fgzC_7_LLeYY|2*r!`B_gfzCD+-@FWeMFa8DGP?BbteKv}p7-Tcud)^NU~F<11;w zy!?hJx;@Z?z2Rz?W~0a{rd!mt^tZzhbWr9;5TqdY0;IB%xIH@L1NhF!B=pDW!&AEk zr^`YT*?u&ml%0(9_{LX3{&wvteuB5Bo44Di=f&;G&FvEZv-{)a8W)hU68`EoWBiZC zekbL8$^+i472!_&41bsZvo+rDlZ78&%a4nbFL$NqgNI!!m%;zT_`}4H3VtCNG^2=h zPicmxrgX9>Jw^ZyQ#g4PsQAH98pjduXOiF{o9pv9IZFa@+$^u0Y%{eCQE6*%KM0LI zCw!c4hOVolyc$Da(_S+e!a?gG5Jy($oNR;#WU{1V2LTE$f#FVgsnzvK684HiSZ(cR zf*>S`XHju88J9(Zc3cw~qw6hGorB;LN)Sx!DkeU}=O4s3O-^K&=&r{Fo8{ztqXSQXOU88`rJm0!DdL4cH7^d@!ZHgSO zR7z$G&z)Ojz%8nPR(uI)!v~^8CrfR?1cB#~EpsvXQbh zEjs#ubZex)#aP-o2^$-%&*Mto^30ZMnFMB8OLKKrc4c?g-UfUjjX)Lw}^MGc6v;g zDqY%4^?UTzhnzA~HqA@Y#EsB&Q|dhlj$9_qec~# zX2?^%x{T=C4C1qLn618iRab6p>deKFracdbdYvj1JuP}08$B+&oNd0N2>sCSy9mxc z$+MY>?As_xR8bNaJ{XZL-0;sVt~v6Na^0rm$&jeX*wzV+`}m!=!dUfR*Mk$cTF@9sll z9Y~WkCoh!B%*1h+Fg#UkdNsBBaa3um-r0Zv<5RR(sPU1+28=ajGMtvUnD5YMSQo%r z$%!b(uxU2@Y@JR*z0|Aw^4O-QA=y<-3C0WL@moY%s0eYD|7)%PQL_6!@AU(*-3^$4 z33_ao^;tBT3M%rE*s`GnWswI;fYT(pj8-LPa}bmpK<;s1bbV}^Ta8})aGdH2&8ll9IT;-PqdmE%`nycI2<@C{)s9>(mFi&fYJo~9 z#T9a+E1NcF)q_dp_fgUB-sXE6{QBifu;cVXW?;`TNnF zlupnQM|Ko2$8W5L`dSK0 zBBwiV=@9CFGKttwU$C^OU%k!O-V?0wbBaz!wT0SJMwRy0O{0V<1e->!hH)t+nt;bS zTnT-mDAbsy>Dt)GmEcmwNulo`n5wZyl%wgPDs;AlRHU81$XNZ4J!hm+lj|g9OA-vX zi~k$NHrGSw)wdM7J8~jXIJOUeQuhV&)O?CA8*~n8nsc}X3ve zqLk)cmD<2eYA033Swb4CVWKy*J!2Cgn-1(kLUbSujsd4I|NdE1r_WiyB*{j)gOp1G z;sWQ^Bsus=xf>-Vd*XoS3%Ox&iOw6AiZUWllG`2I-H)=@jQtj*zvnYvs$mp2kz{aT zC)Qv{JtJ$-o2hA5e3#A8uMO>vBLMGxZ<#J-Gb>`g!bKGnD-Z$uDk#t0Qud&leh2aH zP6NyUMvV=125DqFf8@F)%!*>gpw|(k$W(gNQ&1yvM_omQZI38zf9g6TQRx=?2F9$IK>}!02w9mi3Th4zn@kDJ%+EvQvWC;fuFHZ7>#w!a-NOqMtXKnEl-x;8Uiq3L= zeHP9%1_C_a1zXp`z7fU++2CIIv*DXCOF({3U;i}bM1(wYQVHh3`E>@A?jc?KOm)26 zK_}Tw@PS2;?dB?cToEr0s?HN|73g2$`+AkwvTTHDFxLJ0HqoN_G0Bc5wFX54Gmtho9HG@^1lWqv7rpoC04$xyf+g_48*wzE-Ob4})&g-p z6fDKtxq90W9dJ+mrE>vY{T7U&F;A!+tzq1L_m_~PoYC=o%ZT2`b4eP8q-S?jov+g8m`naCL7<{+5r6HMpCyX3o8A5X z6l^0B{&e{ncK)h)Wqd7MYb8*?m-l6d+&Nbz=G0eWDYA`%3$SN?8{~peR~h^W58Q79 zE_MO;v~4DwLdLzvUMnrp`6%@3y&!0LGPBI|^ zQfMMxG3vJ?j+Ys(_`Pf=J>IXjpUbeqUWrNENP(0dB9^?(Cg9CYx|21TOZ<+t<%ZoU7y6`$v=_#eO%v% z3i7rRPGVbNWETwrl3brjQGfSdezwMIxpB#}PL*LfCXzTQRx8q%=|RQYvlFh+Bw{a7 z3TQkjh6GZMMl)B}!e8PvV58o2A}~BfSZCw88{SXKfo}(<6-1yCoJfE$+q}n z2JHUS{RedNDGm4UJf7CHR(7{PP(3_eVvh2wOn1#jD>V_2zWtui%WOSifBC2^O3e{m_Q-Y?mer-~H#*?0!r= zeAyM@7H^5R}${ow&|eb?)Kc>LUrOIpBl%SZkUJ=c=DeSA`z zm`Z=C9Ajdt%k5EaTaE_lvW%!G=!+vJ_+g0H%NGi$0i_?79o(RP6!}jQWfr-en7`$v zxZ^}^6i_Oq6{?*m0V@oLn3p0oa3bd`cNK)y0+OLUI4J{@0YHDfOULq z>AVH&3fXBY*=ABw6P!r8+GNcf6PaARh76g)(rETEleQf#dFEDQE(cA{0%?yZ6nkpp zD+}r|50mjr)}qOOn-({Ss6ev#>3lH@k=i^Ree+iY${^YCeoZ>`{ZvV_Y$edmt0Sg( z{-I01iF1U6$;K|2o|mk&OhS~R$PX$26rPGWv3>w=A$qhx5b}RCY&N^mUx~D34K|xQ z=nI6^0&(~eH4^dM!XIKq4qj}M9d`0{^*AL6!0Nz^NU_-KCKY%eCvY^>itQ(hWCiT^ z`u^u+)-P9Y(mOz8;&E3kl3xEUWP+6>5($^ScM9rRu)6vx z%%Yt}edOE0?vfCj{dI1eR1ZRH(z+hJd+`}hE~XF~88MrulgB!VFCCJ;9QCTFsoi<_ zq0pwK<0&izFy!*Qpe_dq*p9k6JG&zj#ETqtk^gVz->*|X$op%=CrIS+iQ5sRKXM?` z_V08(oe}q(!DR;ik+K&;+Ff#U9g(*al$Mpl)zJV?xR9LmZiH|vnM4OdqNl)0ZnS$- zz`a z@I3;pO7Evy1?T1#-*bA?8Ng(Oge?dOB(`jOO_(mx(b}3LxkTj0S%Sj{c0QG^yk-mD zkxe`8iF&yESH(aZ?C{{`Ab(rAR4+An9u(Qy(nc_HKK-EIPDkA7pIhte8vG)Rw}Uv1 znMOODjUQL`My3#Ce<0Rn&-2=U5@7bS zo^MsS=zVomI_>)u%~SAIKK+h};kn&&xj?(`JxR^RFQ{ZALnMzj8mRvfQb62b2qET` zm9@sD{?i04R$C|9$oeY(o33GiYnP|px+g(+Z*{5p7qaE^ub2ro;dS$;`7b?o04wqE z_D#-RPByc|u&UcK=AYQ>#*owJy@u8$Y!I;E#Sz}1${uoFGlKP3(ICicsN&xar;UWJ zLF&`PzXJ9=hi%7QfN4TUM|GY24TRj#I&^9l79P0%e!sW0ZZ~s8Mpeyt-a0U^QtfN>T>>hF>S>;u_CE z06z!wn<`^C8ORy15(WX6O;^>=uQ|h7ZQWhR!NvPz@yoYG+ls-31cOgQKCaB9XZ;=X z3ClH$oyEUn1c+mXyjpr)$Q7xplS~jyyw;hSJN(2(va_|Iq7G*Gv)#B-)Q6#%@_B4h zO=5)Nqy`}KAIWlq9yrcEq<_kE^6d%Mgd@9ug39qp0g8AhyZe^E`w+TpSN&C@!<8Ii z9v)!iJA{b12Y=7;uLbi6;cE>u}jOgxW}BCcPPYN zP*)uG@}?aGTkgJXpr6kpdyhI~!*R~W=tef{{$zxrM~b4o&T&kW{+zm_CK8l1rjU$#(`eGkTTM~wDe)umeUde!O=Z}IbVd^#XMi~~xFk_LM1~WP1;`AM2!HU1 zO_Mis7Y^Uwk_mTVaR(-6d-v}eaHwF*iiPBO9A`9}H=+<`3go~c1)}l<;H7eDo z+eti&#jv3$y1$G2NJ9HVMkPcckNJzH6U#OtEBw+%G&*5%6)eE`LSOL9o7)|1X>C45Nwfu1?d_gWq5S(>^ZC+J#;;qT`Ihl3LV&uxgKSAC$#*2Qd^-R5 zS}>GAh(IA?lxW^5tQW_>`Ik{1T3s!hjnwbgPsj=~K}+$xWHXPFkRv@^g<q`j*qJ}@_Hdwie-wKi920A^DdG2peSMn=(XK~J(d$yK969r=!0BGHPI2C;2?!G+!VhTpt{=?o5nu06Yyrn5zHdUg0yzIrnPGh0_+T6PU=ooBxG!}B;1j>gRs={-ghduFip!6?k;@{8HL zXPyQ>-2GEnjK|;J>F=?v*5>PeDMJ2w7Zsoc1==3ewY*t>BS3FB{i@+M(5^pEV%g(C zuvd4lI z2a9honwowaTJ<-(%r%VI;+3dEr0R_xEf?ASl(5Fr2la zH2~56xaVy&-8cyt6`iga(CYC@RO8eq1dn_|r5VDKxWoB`%CDC(7ZLC3-M*Jj!Y)+c z{*0ozm39}j^UXCU6qFh-5uu#tWEZOFDN+t8Kw9AO{#l5l&5JAYl#~!jL@*fCwQw1B z;ouCTly43F6tjVm%G7|WuA*@J%tHQ?h9B=LIUJ8gl#RbIQwoxz9de#`2NHpnWbd8$ zH*l7NLU0zNMOpyi7HpVPj4x0llZ&$8jtBK+7kk}$%nEL!e%{*S#p_*fbPMXD=q;we zXvCd6<7+8&~;xnv9YfQiDhiE|hU1-mLlr`&f@t2f*`swcs6dUE zVh2&qRJ=8L%XwEk6-AXWYwgVg3>!*p1QLr>4}B^G$FJCHL& zb`IYuAKlvpaOk@V1Y5-g1>zs_EBGbVhFW}9#kX1eUmh_kUR_I+`fm`KEA)?-1OTGX z5GE2$7F?v6^V);hD2I!~Z4mz5$F3Tb)Z&yVe!+h&0zHWICCPrlw}7U@+yjJu=b5p~ z%ZAVS(J{HtN58(J9P*u`@sx?v_d#lq(3i)ZzX`3+##-M*nUo!rxcA->Dm+S(D;q+m z|LeW7aL0Cu_Pp%_flJ^Zs?D~tdF*Q?0NDjlxhOmvJ(<^Obb`*K0OXg19waFHciut;fGv&z{^?N2`+rCWj>>ctt6<_)Z_j9giEdr*I38>N>s%cSjH_1F&7FcBmvcpb1T0oFN zTN3^9??<=%g=JjEOyXpZMzRW?wcu9nM?fBig^bGn=s5(3Z#?i^=?^H7y}mv*x1CXInb z!|0faBMk)9h7xf5p30dOOyzxJ9rWzM@Hr;y-rjWMk1*JEwI8t$3v<#2qj)x%B)f5P zStDqC_Wh(~urG;G2HoJ4O)zR%2nk5@f6_3Nt8l;9`H=d(9tJ!Vjq^9V$}Efq=LS1O zr6=z^xuN-6qBXx?atl&3oveD#saFdL9l5VyD3}*qC3E2R zg}=;cIkPKnB4WESB-fEion}nKmk|rtghZJY(+AeZ=hj23{QlKoJN4^u)+!ck$&%%D zHqG<7m2eVX@;!{`=f_gf4_@{nj(E>2M<8Jj?_caX2r&oIfR0uQS#H$FtgSCxRsD8$ z^q;nTR58xA`Bj0NT}P;+3x!ebyk z!o|8}mR_7)G5y(z`O_Sa@6Y?H{n8w-74@Fy2AJwJya9IGw-jozNpo`mzTB6+yOovL$&cNf#~z~R&;e@c%PKM6#VNEx%4)<@ z&;f$+%}AhdX;kCs3<(LIi93kO&j+b_>1AVh{i>y8Na-^-GHO%M55Jd=d92O1M}6%Zfnfr!cx(Mo!I$5J=v*VwFwbI+q%q0y+>1 zGS=ZQQzp(!@8vE>z;P9gIp`$%Vo!Y5j7mbiNz$E|G*NY7!8cwz!qD0H&{okDb>pie z;<6E68%Qp>LgpV^-Jlf1W?#SzE8X=mHu1IDNEIWS6FDrDkk6!Aq{mn{j~`nWk&YTL zPUmrf={Fl%M>ELNMe=3}bYCwz^-gbFQvP9Hak3pZbr|#nznmy^ezeH=bsyEq&{R7IxOhpxzPwFLM-k=SS6hi-af$N@OpGnv zx47U66u4?g#O2->L_6Nibr2u?WBfx*A<-lP=Cg;bFx+WAiz7%wyxm>0+-7*8Yv2#P zt8GWfw!8&?H&~EdH>Q$7FWA-(;9?_?NzT_YU35rY)jhQFZ)vlNoP~@oIif_U`yZe1 z#bmWSE{b;k)b|3T>!a=j2ugTbB3Cj+XT5@L*cxXZN34|g0Q6B%(TR#*Abh=nFVarW z)r#eT9zz!bXiVM5e$@Fe?abh^K21+UMof(L8-K{o2ifu=FdDknBKT~~9QYFQC%JF6 z?3k$d`z_WR-smBeYTKcvK`k}Y39ij)p4x)m(gN;NS*`4xHHIHB5@_n&c-wr)9E!Dk zv*rQSDxWllvGM`ZvJ3?qD+_afo2~aci!%p4nF0K)58;+)&g`avq*}jf-Su+~oetjjr6;S!_mkK(pgjo)k>Z)-gzlLle-wuB|rMv}D# z{Z$74dcqLc;DyCMNaTV$6$&0MgREM6F1muAR^M;b)~P}CimmM*?7Gdjf#NmiKJm4? zJcamtF+vFW5+mL~CBnVv<%2_uA?;xl&trdZc$OFTK=R~yi)9xr0s;+D#^kP&)+@?X z&8-Zm*eCqk)!hT901wUWu2R1bu$srh24B9z4H+8yE&eYSo1417wrYZ`GDS*wg~4{t zct7}maTtmI3B|q0mPMmrC67l=cv$=W=9V-+=i0Yp+co$>*x|EZ)(n6&w}OoAazD3? z=`ht1mo{7rE=;^9LD5QBqRrtTj4j~6^0vX*tU8O+V+WGHF%-)S&)n5`+2_q6W5!Vu zsSfxLDWz$ozj>1*DP4^;)w-|4aQlG$3m<}uEirY<0(o2j1_V;8#)@_DMzn-L&bOnJ zVTHRtQ$5^6^fo_X9aHSm00BqfgLf9g{kZ&hQ5f!eu6&)LlFDvRW_0O1ax!(STXZOo z=F?}7D;L^o>8Zu)mXQl5zB?8S$GD_`+@F4L0*3bn@nD}*6bcn8!^^6eNOtMqoiU0m z4gdW>)P=2$1MZ4;Ip7=8rC}T*WtL%dlCrC39&vZxunl^bZSDGZ zl*1965&$UOzQNxiZI)_|vH5y)EZF?Z}uL z^4^+#HSQ8MrVKXiT22h4F%d&LYJ>3yKbnxzgJ$?D`gOu69gOtL%_{p_5Cd0j?z;rE zK~KKxQ%l8bg?|RO9fG$gun4#@c)1G1EYD1qpNS_R1%UwT?QabRJO>hA5*d=%Vw7oq zz`MIp!z|ka8^-o#A~LswrQ;~W_R7V;Y^faDF=60I2Qy5LHb%xa4$p034NTa^!rI2} zS#XSR&yBx{PL9vLX2#`!bJ+JU*zWAu{}+F4j{seto}OY`&N@qj_&vh(*}^!yYl&yEn~l~t;hKIA*Oie?;g)Y%zv5-HYduKgqcJ()3vb&< zwwoUc`#2Cz4gM|th#MAzq!w5#wgEBLTJKocN_DC*UNceb9vsfoY_^<W$2^9>MRpsGFUsG!St@ehEpNcRV6| zfcTkQtu2%)X`g~9yW7OIB=5yo%$Z038E7uT`I}jb1AkToMkyI=!5-z7X?HwQ_ z9HR0AO97JJ`<1eO`OFU<<2OfBb?)l6&(ZTfZZ|-FO2zQh2oWWc-?9BWSJZ`!Du*Ku zduQewT2gzSY@bJk%C@ISTOVbb75XU!_04&H*RX+%-8JY6zw)J!@rWnecCh;qwbZ(Q zkt&rIyQOzS0{)+boVL!NZ@c(p2;usmaZ~biD4|EMD{N0THrwl~5!6!(PaB^H)5uL~ zJnyFVV>_RRpoq(aX!sRq8q`fFL7-4~O!cNmL=4_6J8EFc1@W{D%9?i_Pk z8TB13CBbH}{_HWR3r5W=Lp1jjG&jX)A{KmT4IDQf94s&no0w7$jzZBg*Kid@d) zspgTXChQbTe}gp{lV?~=Yl#8NXEV{1x0{UG z%h{6D=#|Dc4VqZt(sk*j~@T_ z-A1aj^53B#n-_{4vVgQY@b$~UG&1%EDFmhWKuNeX&8DHEa?h3N6K2$=@(Hh0_f;1; zVMJK?P#a(l4{3~iSG{qXo`KCteO^=3*~4gA%?ZV0p$)HDphWskRjILOvRh)KD0Xp+ zHd%CQD7UTWgHB>$-e6E|SHV~cfVIKgJ#uwp664l2!3&d2-2oQjA$-k5-GyfXCb+$b zu~*hEZ|%a-^W{NfT(Y)vSJ;&ZzNPug_0~(i}G%ZaBP7w?%w7?yi}503@k5 zQ+5v-FYNoKIU*Hq%%dt7b$2bzHv$j0LH=Yn3zLnB;Yu8Bw~9Ik{A4MW)>4`cUDqlTt@1lSi2z(Y>QC6$R9X7bQf~IQ84rB(KoGeln)xF ziThy1E9wL;BpaC~rhjrp%8@z~;gdWZrg=PuPOG*?-H8gYhPnxap>vX~1V}ud@#f5u z4I`hkWS5yd{9csT<1%Euff5#Ea93aNUH!hR%NU_7v(prPRsWznRB(VOjX&3ej89k_yIS47YI)=!4)e14x+IczSd!kY(Kh~gn}laafIf-bVII`1b!?~|41%m2GD zT8<3(@0(RRbbPg`Ue!avT!ruF>M1#kcP`Rrqr+(LrcI0t{97Pe2lgr9ruY1U05Zntz3R*v!n+}`}CN3k;X z^iz^lT{xXNS(L4WTStis;AWpZZN}BD8EXrlq~4fnC@^?`By~4xq5U@VoXuQev5OTw znuoNV!>AEwnZ-Zzs1RTK+_gItY^>P%u&SakLT(e>9&BtJ2DQxA4JDKJJyrRCHF6$M zO(kmpjsYox^aX@~^db=i3lM_z-jO1RB3(j>XbeRGqf%C+gh(jDLu%+nKna0GlqyXS zfzYG~p+$-W1O;C3xx0AOw|nN?JN^Ie%$+kc=gdEU$61PeMm|IPY=rt0WSi%2MX)`a z0wXv`Lm4dq;KvF8$oyMHFh?g@m+Qy`tO5LuI#W2U4GdCg#PnV} zlGA1mPOWY&U*c2cPIJo9!AYoHxqTI_tGc>reO&D$}nZ^Nyf zZ7i*iy2Y63E!^^;{Yh(_nYHa{iI)M6oXH zbQYh4ufp%NUJ-1S%|lAK{i0ea zY}`sdUYYx{4KvLEt_ssC_DGYVTpwaAohXu@Jke+873+Cg;95)sR0k7eB*mmHuh3Lv zoy;}qp_kY1E3jf5l#&QXJ`r_#RNOmy(Tj|du@yDpJ$^-9Ao-WC zENR7Q!+??Qet)DS!A3DpeAZZq&*>1&Qi+M6J@QPp!JvKkgx;kx0>ZxN z0DY6*7jPOT;)^rRg;aJ~lx)nXN5KyRGOiru*pWd6o4;A08B96esgtVJFOTK#FUubt zD|WqrORsBT>GG3P9!&@q;z1G(w%obJO*!SGVNsF^b!&9lgf;hOyzsIsg~>)?JzmnI z634{PdUT{d2)~`L=BqH>nMG@E@!mk<5Y&zlvyFlum%ZqKN02Bp-RqyLhEexegt*M# z8$ll$O=LU9nzBx3*MX$Cic8iu2WInH1DUtwVJva&Gvo|2n~_jQHZ_huXT0w;|A_ z{s;&5;q=nF50CWplExywT!q#6kv5`jy`FtOU)YS3P3HDp>Aq_#$kkrsJP^FF)%z^K zBa>9wh#2XDZ8XvMZ3k9yvW}nEgSTx*MpKRpS<2pNm3IF4X0$U0p2k$Z0o>PB`xvJmZ-iT+Z>w=iT#x^WA9q+fJ{EvS>Y z-J4dAH=ZJX8BX(8A|G7ETtimG%*{|fKqp@7-dy$D^zjBsW2xeAl3TWUVr@z9pqD7k z%ZsmGJ)xAOV-m@s>!NPr=RJHV#aW`e2fm;9(sJfK0;m%ov-U4FV13>EzuIGQf&039 zdYp8Tgi-=_{>@h}G!Swt>GG)h4EmpA8M$|+uw8-wnwt?VRv_#YFk#c0g7 zK#xf}N@%m;1zEKQNMq7o$tpIQIOeO;fGj&+>;&R$=fK+L-1DioIht58JNJ%{*cj-YA8|c(slF%$zl;_wmThGe4w#%e9VT#D; z52QbwKLs`p2!;%BVhp-?kUUXV0o0kVB)wGa610Xw?&Vavq%_=Aw-J@-&y%=xE9A!Y zmC1EdVrhM7_4*vSsnqsAp+TQ_+TUy}SBU*5(r$6Zc@5{!dZ^6$BEH;yJvHX-$4;Ul z%sL3{Mja9k8l$zKkxcJwzQG;(<<++COd(en^q>x-*!mInbgHEYLMmqg%B*=}M6kP; zYhBH~>d9ag^~x>HMgqk)MAnLi2c}Xe!16JpChCcnN_q5btL_b(-I6%R#D)7g78(33KYX2y8jDuc6HR``VLw@X4;vtp zAwK4M{S-EEFJO4(_q}x0L6C{-pONBw5^wu^w~aFT*to_x#EVwFvJmM$pDc%_JHyGS zTUCd>9SHUzW5EzmkW@CqfUP@ed;EIN<3-7jxra1%qma5WXsRnfTxFUY-_?O=!LnVlvyKV$ zYC_(JqFEYrA-WJ)ug38XmU$dexGzZ{E6ubr3yw5b>txAjUgVua2nz&P+}-j-GMsjA z(Z?~(%{~(7Gpo?blULD&kaO-{ND>&KMg?4u50H?am_LXO+ox3e%Ciy3rR`qd7dO7nkJh~{Um+0&aNw$J7UmX^Ntp1 z8(wW(e68j)U;Hx7-t2XVxmveOV^z}qII#RXv&5aHK)d0V; zJSFx`4%;{XC$bI!guZ4T0Pq6(3E_H6dLswEmHx~78}c)2RewVEpFn None: - """从给定的状态加载工作台信息。""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - data = super().serialize_state() - data.update( - self._unilabos_state - ) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - - -def get_workstation_plate_resource(name: str) -> PLRResource: # 要给定一个返回plr的方法 - """ - 用于获取一些模板,例如返回一个带有特定信息/子物料的 Plate,这里需要到注册表注册,例如unilabos/registry/resources/organic/workstation.yaml - 可以直接运行该函数或者利用注册表补全机制,来检查是否资源出错 - :param name: 资源名称 - :return: Resource对象 - """ - plate = WorkStationContainer( - name, size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict() - ) - tip_rack = WorkStationContainer( - "tip_rack_inside_plate", - size_x=50, - size_y=50, - size_z=10, - category="tip_rack", - ordering=collections.OrderedDict(), - ) - plate.assign_child_resource(tip_rack, Coordinate.zero()) - return plate - - -class ResourceSynchronizer(ABC): - """资源同步器基类 - - 负责与外部物料系统的同步,并对 self.deck 做修改 - """ - - def __init__(self, workstation: "WorkstationBase"): - self.workstation = workstation - - @abstractmethod - async def sync_from_external(self) -> bool: - """从外部系统同步物料到本地deck""" - pass - - @abstractmethod - async def sync_to_external(self, plr_resource: PLRResource) -> bool: - """将本地物料同步到外部系统""" - pass - - @abstractmethod - async def handle_external_change(self, change_info: Dict[str, Any]) -> bool: - """处理外部系统的变更通知""" - pass - - -class WorkstationBase(ABC): - """工作站基类 - 简化版 - - 核心功能: - 1. 基于 PLR Deck 的物料系统,支持格式转换 - 2. 可选的资源同步器支持外部物料系统 - 3. 简化的工作流管理 - """ - - _ros_node: ROS2WorkstationNode - - @property - def _children(self) -> Dict[str, Any]: # 不要删除这个下划线,不然会自动导入注册表,后面改成装饰器识别 - return self._ros_node.children - - async def update_resource_example(self): - return await self._ros_node.update_resource([get_workstation_plate_resource("test")]) - - def __init__( - self, - station_resource: PLRResource, - *args, - **kwargs, # 必须有kwargs - ): - # 基本配置 - print(station_resource) - self.deck_config = station_resource - - # PLR 物料系统 - self.deck: Optional[Deck] = None - self.plr_resources: Dict[str, PLRResource] = {} - - # 资源同步器(可选) - # self.resource_synchronizer = ResourceSynchronizer(self) # 要在driver中自行初始化,只有workstation用 - - # 硬件接口 - self.hardware_interface: Union[Any, str] = None - - # 工作流状态 - self.current_workflow_status = WorkflowStatus.IDLE - self.current_workflow_info = None - self.workflow_start_time = None - self.workflow_parameters = {} - - # 支持的工作流(静态预定义) - self.supported_workflows: Dict[str, WorkflowInfo] = {} - - # 初始化物料系统 - self._initialize_material_system() - - # 注册支持的工作流 - # self._register_supported_workflows() - - # logger.info(f"工作站 {device_id} 初始化完成(简化版)") - - def _initialize_material_system(self): - """初始化物料系统 - 使用 graphio 转换""" - try: - from unilabos.resources.graphio import resource_ulab_to_plr - - # # 1. 合并 deck_config 和 children 创建完整的资源树 - # complete_resource_config = self._create_complete_resource_config() - - # # 2. 使用 graphio 转换为 PLR 资源 - # self.deck = resource_ulab_to_plr(complete_resource_config, plr_model=True) - - # # 3. 建立资源映射 - # self._build_resource_mappings(self.deck) - - # # 4. 如果有资源同步器,执行初始同步 - # if self.resource_synchronizer: - # # 这里可以异步执行,暂时跳过 - # pass - - # logger.info(f"工作站 {self.device_id} 物料系统初始化成功,创建了 {len(self.plr_resources)} 个资源") - pass - except Exception as e: - # logger.error(f"工作站 {self.device_id} 物料系统初始化失败: {e}") - raise - - def _create_complete_resource_config(self) -> Dict[str, Any]: - """创建完整的资源配置 - 合并 deck_config 和 children""" - # 创建主 deck 配置 - deck_resource = { - "id": f"{self.device_id}_deck", - "name": f"{self.device_id}_deck", - "type": "deck", - "position": {"x": 0, "y": 0, "z": 0}, - "config": { - "size_x": self.deck_config.get("size_x", 1000.0), - "size_y": self.deck_config.get("size_y", 1000.0), - "size_z": self.deck_config.get("size_z", 100.0), - **{k: v for k, v in self.deck_config.items() if k not in ["size_x", "size_y", "size_z"]}, - }, - "data": {}, - "children": [], - "parent": None, - } - - # 添加子资源 - if self._children: - children_list = [] - for child_id, child_config in self._children.items(): - child_resource = self._normalize_child_resource(child_id, child_config, deck_resource["id"]) - children_list.append(child_resource) - deck_resource["children"] = children_list - - return deck_resource - - def _normalize_child_resource(self, resource_id: str, config: Dict[str, Any], parent_id: str) -> Dict[str, Any]: - """标准化子资源配置""" - return { - "id": resource_id, - "name": config.get("name", resource_id), - "type": config.get("type", "container"), - "position": self._normalize_position(config.get("position", {})), - "config": config.get("config", {}), - "data": config.get("data", {}), - "children": [], # 简化版本:只支持一层子资源 - "parent": parent_id, - } - - def _normalize_position(self, position: Any) -> Dict[str, float]: - """标准化位置信息""" - if isinstance(position, dict): - return { - "x": float(position.get("x", 0)), - "y": float(position.get("y", 0)), - "z": float(position.get("z", 0)), - } - elif isinstance(position, (list, tuple)) and len(position) >= 2: - return { - "x": float(position[0]), - "y": float(position[1]), - "z": float(position[2]) if len(position) > 2 else 0.0, - } - else: - return {"x": 0.0, "y": 0.0, "z": 0.0} - - def _build_resource_mappings(self, deck: Deck): - """递归构建资源映射""" - - def add_resource_recursive(resource: PLRResource): - if hasattr(resource, "name"): - self.plr_resources[resource.name] = resource - - if hasattr(resource, "children"): - for child in resource.children: - add_resource_recursive(child) - - add_resource_recursive(deck) - - # ============ 硬件接口管理 ============ - - def set_hardware_interface(self, hardware_interface: Union[Any, str]): - """设置硬件接口""" - self.hardware_interface = hardware_interface - logger.info(f"工作站 {self.device_id} 硬件接口设置: {type(hardware_interface).__name__}") - - def set_workstation_node(self, workstation_node: "ROS2WorkstationNode"): - """设置协议节点引用(用于代理模式)""" - self._ros_node = workstation_node - logger.info(f"工作站 {self.device_id} 关联协议节点") - - # ============ 设备操作接口 ============ - - def call_device_method(self, method: str, *args, **kwargs) -> Any: - """调用设备方法的统一接口""" - # 1. 代理模式:通过协议节点转发 - if isinstance(self.hardware_interface, str) and self.hardware_interface.startswith("proxy:"): - if not self._ros_node: - raise RuntimeError("代理模式需要设置workstation_node") - - device_id = self.hardware_interface[6:] # 移除 "proxy:" 前缀 - return self._ros_node.call_device_method(device_id, method, *args, **kwargs) - - # 2. 直接模式:直接调用硬件接口方法 - elif self.hardware_interface and hasattr(self.hardware_interface, method): - return getattr(self.hardware_interface, method)(*args, **kwargs) - - else: - raise AttributeError(f"硬件接口不支持方法: {method}") - - def get_device_status(self) -> Dict[str, Any]: - """获取设备状态""" - try: - return self.call_device_method("get_status") - except AttributeError: - # 如果设备不支持get_status方法,返回基础状态 - return { - "status": "unknown", - "interface_type": type(self.hardware_interface).__name__, - "timestamp": time.time(), - } - - def is_device_available(self) -> bool: - """检查设备是否可用""" - try: - self.get_device_status() - return True - except: - return False - - # ============ 物料系统接口 ============ - - def get_deck(self) -> Deck: - """获取主 Deck""" - return self.deck - - def get_all_resources(self) -> Dict[str, PLRResource]: - """获取所有 PLR 资源""" - return self.plr_resources.copy() - - def find_resource_by_name(self, name: str) -> Optional[PLRResource]: - """按名称查找资源""" - return self.plr_resources.get(name) - - def find_resources_by_type(self, resource_type: type) -> List[PLRResource]: - """按类型查找资源""" - return [res for res in self.plr_resources.values() if isinstance(res, resource_type)] - - async def sync_with_external_system(self) -> bool: - """与外部物料系统同步""" - if not self.resource_synchronizer: - logger.info(f"工作站 {self.device_id} 没有配置资源同步器") - return True - - try: - success = await self.resource_synchronizer.sync_from_external() - if success: - logger.info(f"工作站 {self.device_id} 外部同步成功") - else: - logger.warning(f"工作站 {self.device_id} 外部同步失败") - return success - except Exception as e: - logger.error(f"工作站 {self.device_id} 外部同步异常: {e}") - return False - - # ============ 简化的工作流控制 ============ - - def execute_workflow(self, workflow_name: str, parameters: Dict[str, Any]) -> bool: - """执行工作流""" - try: - # 设置工作流状态 - self.current_workflow_status = WorkflowStatus.INITIALIZING - self.workflow_parameters = parameters - self.workflow_start_time = time.time() - - # 委托给子类实现 - success = self._execute_workflow_impl(workflow_name, parameters) - - if success: - self.current_workflow_status = WorkflowStatus.RUNNING - logger.info(f"工作站 {self.device_id} 工作流 {workflow_name} 启动成功") - else: - self.current_workflow_status = WorkflowStatus.ERROR - logger.error(f"工作站 {self.device_id} 工作流 {workflow_name} 启动失败") - - return success - - except Exception as e: - self.current_workflow_status = WorkflowStatus.ERROR - logger.error(f"工作站 {self.device_id} 执行工作流失败: {e}") - return False - - def stop_workflow(self, emergency: bool = False) -> bool: - """停止工作流""" - try: - if self.current_workflow_status in [WorkflowStatus.IDLE, WorkflowStatus.STOPPED]: - logger.warning(f"工作站 {self.device_id} 没有正在运行的工作流") - return True - - self.current_workflow_status = WorkflowStatus.STOPPING - - # 委托给子类实现 - success = self._stop_workflow_impl(emergency) - - if success: - self.current_workflow_status = WorkflowStatus.STOPPED - logger.info(f"工作站 {self.device_id} 工作流停止成功 (紧急: {emergency})") - else: - self.current_workflow_status = WorkflowStatus.ERROR - logger.error(f"工作站 {self.device_id} 工作流停止失败") - - return success - - except Exception as e: - self.current_workflow_status = WorkflowStatus.ERROR - logger.error(f"工作站 {self.device_id} 停止工作流失败: {e}") - return False - - # ============ 状态属性 ============ - - @property - def workflow_status(self) -> WorkflowStatus: - """获取当前工作流状态""" - return self.current_workflow_status - - @property - def is_busy(self) -> bool: - """检查工作站是否忙碌""" - return self.current_workflow_status in [ - WorkflowStatus.INITIALIZING, - WorkflowStatus.RUNNING, - WorkflowStatus.STOPPING, - ] - - @property - def workflow_runtime(self) -> float: - """获取工作流运行时间(秒)""" - if self.workflow_start_time is None: - return 0.0 - return time.time() - self.workflow_start_time - - # ============ 抽象方法 - 子类必须实现 ============ - - # @abstractmethod - # def _register_supported_workflows(self): - # """注册支持的工作流 - 子类必须实现""" - # pass - - # @abstractmethod - # def _execute_workflow_impl(self, workflow_name: str, parameters: Dict[str, Any]) -> bool: - # """执行工作流的具体实现 - 子类必须实现""" - # pass - - # @abstractmethod - # def _stop_workflow_impl(self, emergency: bool = False) -> bool: - # """停止工作流的具体实现 - 子类必须实现""" - # pass - -class WorkstationExample(WorkstationBase): - """工作站示例实现""" - - def _register_supported_workflows(self): - """注册支持的工作流""" - self.supported_workflows["example_workflow"] = WorkflowInfo( - name="example_workflow", - description="这是一个示例工作流", - estimated_duration=300.0, - required_materials=["sample_plate"], - output_product="processed_plate", - parameters_schema={"param1": "string", "param2": "integer"}, - ) - - def _execute_workflow_impl(self, workflow_name: str, parameters: Dict[str, Any]) -> bool: - """执行工作流的具体实现""" - if workflow_name not in self.supported_workflows: - logger.error(f"工作站 {self.device_id} 不支持工作流: {workflow_name}") - return False - - # 这里添加实际的工作流逻辑 - logger.info(f"工作站 {self.device_id} 正在执行工作流: {workflow_name} with parameters {parameters}") - return True - - def _stop_workflow_impl(self, emergency: bool = False) -> bool: - """停止工作流的具体实现""" - # 这里添加实际的停止逻辑 - logger.info(f"工作站 {self.device_id} 正在停止工作流 (紧急: {emergency})") - return True \ No newline at end of file From eb93b83415589aff5eb05234a8cf656263d97b5a Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Fri, 31 Oct 2025 15:05:21 +0800 Subject: [PATCH 037/104] =?UTF-8?q?fix:=20=E6=9B=B4=E6=96=B0=E7=89=A9?= =?UTF-8?q?=E6=96=99=E7=B1=BB=E5=9E=8B=E9=85=8D=E7=BD=AE=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../devices/workstation/bioyond_studio/config.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 504cf45..2eb3dbb 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -154,19 +154,19 @@ MATERIAL_TYPE_MAPPINGS = { "100ml液体": ("YB_1Bottle100mlCarrier", "d37166b3-ecaa-481e-bd84-3032b795ba07"), "液": ("YB_1BottleCarrier", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), "高粘液": ("YB_1GaoNianYeBottleCarrier", "abe8df30-563d-43d2-85e0-cabec59ddc16"), - "加样头(大)": ("YB_jia_yang_tou_da_1X1_carrier", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + "加样头(大)": ("YB_jia_yang_tou_da", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), "加样头(大)板": ("YB_jia_yang_tou_da_1X1_carrier", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"), "5ml分液瓶板": ("YB_6x5ml_DispensingVialCarrier", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"), - "5ml分液瓶": ("YB_6x5ml_DispensingVialCarrier", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"), + "5ml分液瓶": ("YB_fen_ye_5ml_Bottle", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"), "20ml分液瓶板": ("YB_6x20ml_DispensingVialCarrier", "3a192fa4-47db-3449-162a-eaf8aba57e27"), - "20ml分液瓶": ("YB_6x20ml_DispensingVialCarrier", "3a192c2b-19e8-f0a3-035e-041ca8ca1035"), + "20ml分液瓶": ("YB_fen_ye_20ml_Bottler", "3a192c2b-19e8-f0a3-035e-041ca8ca1035"), "配液瓶(小)板": ("YB_6x_SmallSolutionBottleCarrier", "3a190c8b-3284-af78-d29f-9a69463ad047"), - "配液瓶(小)": ("YB_6x_SmallSolutionBottleCarrier", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"), + "配液瓶(小)": ("YB_pei_ye_xiao_Bottler", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"), "配液瓶(大)板": ("YB_4x_LargeSolutionBottleCarrier", "53e50377-32dc-4781-b3c0-5ce45bc7dc27"), - "配液瓶(大)": ("YB_4x_LargeSolutionBottleCarrier", "19c52ad1-51c5-494f-8854-576f4ca9c6ca"), + "配液瓶(大)": ("YB_pei_ye_da_Bottle", "19c52ad1-51c5-494f-8854-576f4ca9c6ca"), "适配器块": ("YB_AdapterBlock", "efc3bb32-d504-4890-91c0-b64ed3ac80cf"), "枪头盒": ("YB_TipBox", "3a192c2e-20f3-a44a-0334-c8301839d0b3"), - "枪头": ("YB_TipBox", "b6196971-1050-46da-9927-333e8dea062d"), + "枪头": ("YB_Pipette_Tip", "b6196971-1050-46da-9927-333e8dea062d"), } SOLID_LIQUID_MAPPINGS = { From 6d7c39da9e9e5ddb2b1727aec4bfa0a8055d3d64 Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Fri, 31 Oct 2025 15:29:59 +0800 Subject: [PATCH 038/104] 1031 --- test/resources/test.json | 191 ++++++++++++++++++ .../bioyond_cell/bioyond_cell_workstation.py | 132 +++++++++++- .../workstation/bioyond_studio/config.py | 2 +- unilabos/registry/devices/laiyu_liquid.yaml | 11 +- unilabos/registry/devices/liquid_handler.yaml | 33 ++- 5 files changed, 333 insertions(+), 36 deletions(-) create mode 100644 test/resources/test.json diff --git a/test/resources/test.json b/test/resources/test.json new file mode 100644 index 0000000..9fa9237 --- /dev/null +++ b/test/resources/test.json @@ -0,0 +1,191 @@ +{ + "data": [ + { + "orderCode": "BSO2025103100006", + "orderName": "DP20250927001", + "errorMessage": null, + "usedMaterials": [ + { + "id": "3a1d4b13-25a6-cfb2-7315-159f14b32425", + "destinationType": "TempOrder", + "destinationId": "3a1d4b13-23cb-63e5-10df-6a1d38335163", + "materialId": "3a1d4b13-2467-e64d-d8bc-3957fb6e3240", + "materialName": "适配器块", + "materialCode": "0018-00065", + "quantity": "1块", + "materialTypeId": "efc3bb32-d504-4890-91c0-b64ed3ac80cf", + "materialTypeCode": "0018", + "materialTypeMode": "Consumables", + "materialTypeName": "适配器块", + "locationId": "3a1abd46-18fe-1f56-6ced-a1f7fe08e36c", + "locationCode": "0014-0001", + "locationShowName": "0014-0001" + }, + { + "id": "3a1d4b13-2420-8cfe-17f1-5f77a6ff6dc3", + "destinationType": "TempOrder", + "destinationId": "3a1d4b13-23cb-63e5-10df-6a1d38335163", + "materialId": "3a1d4b11-e448-bf90-d0bd-b20758425370", + "materialName": "test1", + "materialCode": "0001-00063", + "quantity": "1块", + "materialTypeId": "3a190c8b-3284-af78-d29f-9a69463ad047", + "materialTypeCode": "0001", + "materialTypeMode": "Sample", + "materialTypeName": "配液瓶(小)板", + "locationId": "3a19deae-2c7a-2426-6d71-e9de3cb158b1", + "locationCode": "4", + "locationShowName": "4" + }, + { + "id": "3a1d4b13-2420-73a1-2b4d-7bf6dd993c36", + "destinationType": "TempOrder", + "destinationId": "3a1d4b13-23cb-63e5-10df-6a1d38335163", + "materialId": "3a1d4b11-e448-fea3-8291-0b66ecd06d72", + "materialName": "test1", + "materialCode": "0002-00282", + "quantity": "1块", + "materialTypeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb", + "materialTypeCode": "0002", + "materialTypeMode": "Sample", + "materialTypeName": "配液瓶(小)", + "locationId": "3a19deae-2c7a-2426-6d71-e9de3cb158b1", + "locationCode": "4-1/1", + "locationShowName": "4-1/1" + }, + { + "id": "3a1d4b13-2420-e45f-192d-639887ad73b7", + "destinationType": "TempOrder", + "destinationId": "3a1d4b13-23cb-63e5-10df-6a1d38335163", + "materialId": "3a1d4b12-67fc-5f91-13ed-c223d0155399", + "materialName": "test2", + "materialCode": "0010-00059", + "quantity": "1块", + "materialTypeId": "3a192fa4-007d-ec7b-456e-2a8be7a13f23", + "materialTypeCode": "0010", + "materialTypeMode": "Sample", + "materialTypeName": "5ml分液瓶板", + "locationId": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3", + "locationCode": "5", + "locationShowName": "5" + }, + { + "id": "3a1d4b13-2420-c052-93cc-002f0aae79fc", + "destinationType": "TempOrder", + "destinationId": "3a1d4b13-23cb-63e5-10df-6a1d38335163", + "materialId": "3a1d4b12-67fc-60f7-1129-3d1ef2a2d1f8", + "materialName": "test2", + "materialCode": "0007-00211", + "quantity": "1块", + "materialTypeId": "3a192c2a-ebb7-58a1-480d-8b3863bf74f4", + "materialTypeCode": "0007", + "materialTypeMode": "Sample", + "materialTypeName": "5ml分液瓶", + "locationId": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3", + "locationCode": "5-1/1", + "locationShowName": "5-1/1" + } + ] + }, + { + "orderCode": "BSO2025103100007", + "orderName": "DP20250927002", + "errorMessage": null, + "usedMaterials": [ + { + "id": "3a1d4b13-264b-aca7-9e97-ab4df186d5c2", + "destinationType": "TempOrder", + "destinationId": "3a1d4b13-260c-9239-5c8a-ecb6fd96dc86", + "materialId": "3a1d4b13-2467-e64d-d8bc-3957fb6e3240", + "materialName": "适配器块", + "materialCode": "0018-00065", + "quantity": "1块", + "materialTypeId": "efc3bb32-d504-4890-91c0-b64ed3ac80cf", + "materialTypeCode": "0018", + "materialTypeMode": "Consumables", + "materialTypeName": "适配器块", + "locationId": "3a1abd46-18fe-1f56-6ced-a1f7fe08e36c", + "locationCode": "0014-0001", + "locationShowName": "0014-0001" + }, + { + "id": "3a1d4b13-263e-873e-1331-7e668b411e98", + "destinationType": "TempOrder", + "destinationId": "3a1d4b13-260c-9239-5c8a-ecb6fd96dc86", + "materialId": "3a1d4b11-e448-bf90-d0bd-b20758425370", + "materialName": "test1", + "materialCode": "0001-00063", + "quantity": "1块", + "materialTypeId": "3a190c8b-3284-af78-d29f-9a69463ad047", + "materialTypeCode": "0001", + "materialTypeMode": "Sample", + "materialTypeName": "配液瓶(小)板", + "locationId": "3a19deae-2c7a-2426-6d71-e9de3cb158b1", + "locationCode": "4", + "locationShowName": "4" + }, + { + "id": "3a1d4b13-263e-7884-d9e0-b010478b7448", + "destinationType": "TempOrder", + "destinationId": "3a1d4b13-260c-9239-5c8a-ecb6fd96dc86", + "materialId": "3a1d4b11-e448-82e0-6a64-6230ee1bf0a9", + "materialName": "test1", + "materialCode": "0002-00283", + "quantity": "1块", + "materialTypeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb", + "materialTypeCode": "0002", + "materialTypeMode": "Sample", + "materialTypeName": "配液瓶(小)", + "locationId": "3a19deae-2c7a-2426-6d71-e9de3cb158b1", + "locationCode": "4-1/2", + "locationShowName": "4-1/2" + }, + { + "id": "3a1d4b13-263e-6e99-b513-66047191643f", + "destinationType": "TempOrder", + "destinationId": "3a1d4b13-260c-9239-5c8a-ecb6fd96dc86", + "materialId": "3a1d4b12-67fc-5f91-13ed-c223d0155399", + "materialName": "test2", + "materialCode": "0010-00059", + "quantity": "1块", + "materialTypeId": "3a192fa4-007d-ec7b-456e-2a8be7a13f23", + "materialTypeCode": "0010", + "materialTypeMode": "Sample", + "materialTypeName": "5ml分液瓶板", + "locationId": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3", + "locationCode": "5", + "locationShowName": "5" + }, + { + "id": "3a1d4b13-263e-5b21-2c41-53e4ea7fe947", + "destinationType": "TempOrder", + "destinationId": "3a1d4b13-260c-9239-5c8a-ecb6fd96dc86", + "materialId": "3a1d4b12-67fc-131a-82ff-87e9e7708f9f", + "materialName": "test2", + "materialCode": "0007-00212", + "quantity": "1块", + "materialTypeId": "3a192c2a-ebb7-58a1-480d-8b3863bf74f4", + "materialTypeCode": "0007", + "materialTypeMode": "Sample", + "materialTypeName": "5ml分液瓶", + "locationId": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3", + "locationCode": "5-1/2", + "locationShowName": "5-1/2" + } + ] + } + ], + "code": 1, + "message": "", + "timestamp": 1761891208109 +} + +25-10-31 [14:27:52,203] [ERROR] 从Bioyond同步物料数据失败: 'BottleCarrier' object has no attribute 'tracker' [sync_from_external:83] [unilabos.utils.log.station] +Traceback (most recent call last): + File "C:\ML\GitHub\Uni-Lab-OS\unilabos\devices\workstation\bioyond_studio\station.py", line 73, in sync_from_external + unilab_resources = resource_bioyond_to_plr( + ^^^^^^^^^^^^^^^^^^^^^^^^ + File "C:\ML\GitHub\Uni-Lab-OS\unilabos\resources\graphio.py", line 661, in resource_bioyond_to_plr + bottle.tracker.liquids = [ + ^^^^^^^^^^^^^^ +AttributeError: 'BottleCarrier' object has no attribute 'tracker' \ No newline at end of file diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index 5ce49e0..c40945e 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -253,7 +253,7 @@ class BioyondCellWorkstation(BioyondWorkstation): def auto_feeding4to3( self, # ★ 修改点:默认模板路径 - xlsx_path: Optional[str] = "/Users/calvincao/Desktop/work/uni-lab-all/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx", + xlsx_path: Optional[str] = "unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\material_template.xlsx", # ---------------- WH4 - 加样头面 (Z=1, 12个点位) ---------------- WH4_x1_y1_z1_1_materialName: str = "", WH4_x1_y1_z1_1_quantity: float = 0.0, WH4_x2_y1_z1_2_materialName: str = "", WH4_x2_y1_z1_2_quantity: float = 0.0, @@ -630,7 +630,12 @@ class BioyondCellWorkstation(BioyondWorkstation): response = self._post_lims("/api/lims/order/orders", orders) print(response) # 等待任务报送成功 - order_code = response.get("data", {}).get("orderCode") + data_list = response.get("data", []) + if data_list: + order_code = data_list[0].get("orderCode") + else: + order_code = None + if not order_code: logger.error("上料任务未返回有效 orderCode!") return response @@ -963,6 +968,119 @@ class BioyondCellWorkstation(BioyondWorkstation): logger.error(f"✗ 执行失败: {e}") return {"success": False, "error": str(e)} +def create_material( + self, + material_name: str, + type_id: str, + warehouse_name: str, + location_name_or_id: Optional[str] = None + ) -> Dict[str, Any]: + """创建单个物料并可选入库。 + Args: + material_name: 物料名称(会优先匹配配置模板)。 + type_id: 物料类型 ID(若为空则尝试从配置推断)。 + warehouse_name: 需要入库的仓库名称;若为空则仅创建不入库。 + location_name_or_id: 具体库位名称(如 A01)或库位 UUID,由用户指定。 + Returns: + 包含创建结果、物料ID以及入库结果的字典。 + """ + material_name = (material_name or "").strip() + if not material_name: + raise ValueError("material_name 不能为空") + resolved_type_id = (type_id or "").strip() + # 优先从 SOLID_LIQUID_MAPPINGS 中获取模板数据 + template = SOLID_LIQUID_MAPPINGS.get(material_name) + if not template: + raise ValueError(f"在配置中未找到物料 {material_name} 的模板,请检查 SOLID_LIQUID_MAPPINGS。") + material_data: Dict[str, Any] + material_data = deepcopy(template) + # 最终确保 typeId 为调用方传入的值 + if resolved_type_id: + material_data["typeId"] = resolved_type_id + material_data["name"] = material_name + # 生成唯一编码 + def _generate_code(prefix: str) -> str: + normalized = re.sub(r"\W+", "_", prefix) + normalized = normalized.strip("_") or "material" + return f"{normalized}_{datetime.now().strftime('%Y%m%d%H%M%S')}" + if not material_data.get("code"): + material_data["code"] = _generate_code(material_name) + if not material_data.get("barCode"): + material_data["barCode"] = "" + # 处理数量字段类型 + def _to_number(value: Any, default: float = 0.0) -> float: + try: + if value is None: + return default + if isinstance(value, (int, float)): + return float(value) + if isinstance(value, str) and value.strip() == "": + return default + return float(value) + except (TypeError, ValueError): + return default + material_data["quantity"] = _to_number(material_data.get("quantity"), 1.0) + material_data["warningQuantity"] = _to_number(material_data.get("warningQuantity"), 0.0) + unit = material_data.get("unit") or "个" + material_data["unit"] = unit + if not material_data.get("parameters"): + material_data["parameters"] = json.dumps({"unit": unit}, ensure_ascii=False) + # 补充子物料信息 + details = material_data.get("details") or [] + if not isinstance(details, list): + logger.warning("details 字段不是列表,已忽略。") + details = [] + else: + for idx, detail in enumerate(details, start=1): + if not isinstance(detail, dict): + continue + if not detail.get("code"): + detail["code"] = f"{material_data['code']}_{idx:02d}" + if not detail.get("name"): + detail["name"] = f"{material_name}_detail_{idx:02d}" + if not detail.get("unit"): + detail["unit"] = unit + if not detail.get("parameters"): + detail["parameters"] = json.dumps({"unit": detail.get("unit", unit)}, ensure_ascii=False) + if "quantity" in detail: + detail["quantity"] = _to_number(detail.get("quantity"), 1.0) + material_data["details"] = details + create_result = self._post_lims("/api/lims/storage/material", material_data) + # 解析创建结果中的物料 ID + material_id: Optional[str] = None + if isinstance(create_result, dict): + data_field = create_result.get("data") + if isinstance(data_field, str): + material_id = data_field + elif isinstance(data_field, dict): + material_id = data_field.get("id") or data_field.get("materialId") + inbound_result: Optional[Dict[str, Any]] = None + location_id: Optional[str] = None + # 按用户指定位置入库 + if warehouse_name and material_id and location_name_or_id: + try: + location_ids, position_names = self._load_warehouse_locations(warehouse_name) + position_to_id = {name: loc_id for name, loc_id in zip(position_names, location_ids)} + target_location_id = position_to_id.get(location_name_or_id, location_name_or_id) + if target_location_id: + location_id = target_location_id + inbound_result = self.storage_inbound(material_id, target_location_id) + else: + inbound_result = {"error": f"未找到匹配的库位: {location_name_or_id}"} + except Exception as exc: + logger.error(f"获取仓库 {warehouse_name} 位置失败: {exc}") + inbound_result = {"error": str(exc)} + return { + "success": bool(isinstance(create_result, dict) and create_result.get("code") == 1 and material_id), + "material_name": material_name, + "material_id": material_id, + "warehouse": warehouse_name, + "location_id": location_id, + "location_name_or_id": location_name_or_id, + "create_result": create_result, + "inbound_result": inbound_result, + } + # -------------------------------- @@ -971,7 +1089,7 @@ if __name__ == "__main__": lab_registry.setup() ws = BioyondCellWorkstation() # logger.info(ws.scheduler_stop()) - # logger.info(ws.scheduler_start()) + logger.info(ws.scheduler_start()) # results = ws.create_materials(SOLID_LIQUID_MAPPINGS) # for r in results: @@ -980,11 +1098,11 @@ if __name__ == "__main__": # result = ws.create_and_inbound_materials() # 继续后续流程 - # logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 + logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 # # 使用正斜杠或 Path 对象来指定文件路径 - # excel_path = Path("unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025092701.xlsx") - # logger.info(ws.create_orders(excel_path)) - # logger.info(ws.transfer_3_to_2_to_1()) + excel_path = Path("unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025092701.xlsx") + logger.info(ws.create_orders(excel_path)) + logger.info(ws.transfer_3_to_2_to_1()) # logger.info(ws.transfer_1_to_2()) # logger.info(ws.scheduler_start()) diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 504cf45..519e686 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -16,7 +16,7 @@ API_CONFIG = { "report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"), # HTTP 服务配置 - "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.21.32.210"), # HTTP服务监听地址,监听计算机飞连ip地址 + "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.21.33.174"), # HTTP服务监听地址,监听计算机飞连ip地址 "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), "debug_mode": False,# 调试模式 } diff --git a/unilabos/registry/devices/laiyu_liquid.yaml b/unilabos/registry/devices/laiyu_liquid.yaml index 98201a7..64c0c18 100644 --- a/unilabos/registry/devices/laiyu_liquid.yaml +++ b/unilabos/registry/devices/laiyu_liquid.yaml @@ -1361,8 +1361,7 @@ laiyu_liquid: mix_liquid_height: 0.0 mix_rate: 0 mix_stage: '' - mix_times: - - 0 + mix_times: 0 mix_vol: 0 none_keys: - '' @@ -1492,11 +1491,9 @@ laiyu_liquid: mix_stage: type: string mix_times: - items: - maximum: 2147483647 - minimum: -2147483648 - type: integer - type: array + maximum: 2147483647 + minimum: -2147483648 + type: integer mix_vol: maximum: 2147483647 minimum: -2147483648 diff --git a/unilabos/registry/devices/liquid_handler.yaml b/unilabos/registry/devices/liquid_handler.yaml index b21ccd7..99c9233 100644 --- a/unilabos/registry/devices/liquid_handler.yaml +++ b/unilabos/registry/devices/liquid_handler.yaml @@ -3994,8 +3994,7 @@ liquid_handler: mix_liquid_height: 0.0 mix_rate: 0 mix_stage: '' - mix_times: - - 0 + mix_times: 0 mix_vol: 0 none_keys: - '' @@ -4151,11 +4150,9 @@ liquid_handler: mix_stage: type: string mix_times: - items: - maximum: 2147483647 - minimum: -2147483648 - type: integer - type: array + maximum: 2147483647 + minimum: -2147483648 + type: integer mix_vol: maximum: 2147483647 minimum: -2147483648 @@ -5015,8 +5012,7 @@ liquid_handler.biomek: mix_liquid_height: 0.0 mix_rate: 0 mix_stage: '' - mix_times: - - 0 + mix_times: 0 mix_vol: 0 none_keys: - '' @@ -5159,11 +5155,9 @@ liquid_handler.biomek: mix_stage: type: string mix_times: - items: - maximum: 2147483647 - minimum: -2147483648 - type: integer - type: array + maximum: 2147483647 + minimum: -2147483648 + type: integer mix_vol: maximum: 2147483647 minimum: -2147483648 @@ -7807,8 +7801,7 @@ liquid_handler.prcxi: mix_liquid_height: 0.0 mix_rate: 0 mix_stage: '' - mix_times: - - 0 + mix_times: 0 mix_vol: 0 none_keys: - '' @@ -7937,11 +7930,9 @@ liquid_handler.prcxi: mix_stage: type: string mix_times: - items: - maximum: 2147483647 - minimum: -2147483648 - type: integer - type: array + maximum: 2147483647 + minimum: -2147483648 + type: integer mix_vol: maximum: 2147483647 minimum: -2147483648 From a62896eda298eccd61f971c0fb2e5b835584331b Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Fri, 31 Oct 2025 18:57:38 +0800 Subject: [PATCH 039/104] 1031_byxinyu --- test/resources/test copy.json | 99 ++++++ test/resources/test.json | 305 +++++++----------- .../bioyond_cell/bioyond_cell_workstation.py | 73 ++++- .../bioyond_cell/material_template.xlsx | Bin 22168 -> 22207 bytes 4 files changed, 276 insertions(+), 201 deletions(-) create mode 100644 test/resources/test copy.json diff --git a/test/resources/test copy.json b/test/resources/test copy.json new file mode 100644 index 0000000..f9e9aa0 --- /dev/null +++ b/test/resources/test copy.json @@ -0,0 +1,99 @@ + { + "typeId": "3a190c8b-3284-af78-d29f-9a69463ad047", + "code": "", + "barCode": "", + "name": "test", + "unit": "", + "parameters": "{}", + "quantity": "", + "details": [ + { + "typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb", + "code": "", + "name": "配液瓶(小)11", + "quantity": "1", + "x": 1, + "y": 1, + "z": 1, + "unit": "", + "parameters": "{}" + }, + { + "typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb", + "code": "", + "name": "配液瓶(小)21", + "quantity": "1", + "x": 2, + "y": 1, + "z": 1, + "unit": "", + "parameters": "{}" + }, + { + "typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb", + "code": "", + "name": "配液瓶(小)12", + "quantity": "1", + "x": 1, + "y": 2, + "z": 1, + "unit": "", + "parameters": "{}" + }, + { + "typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb", + "code": "", + "name": "配液瓶(小)22", + "quantity": "1", + "x": 2, + "y": 2, + "z": 1, + "unit": "", + "parameters": "{}" + }, + { + "typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb", + "code": "", + "name": "配液瓶(小)13", + "quantity": "1", + "x": 1, + "y": 3, + "z": 1, + "unit": "", + "parameters": "{}" + }, + { + "typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb", + "code": "", + "name": "配液瓶(小)23", + "quantity": "1", + "x": 2, + "y": 3, + "z": 1, + "unit": "", + "parameters": "{}" + }, + { + "typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb", + "code": "", + "name": "配液瓶(小)14", + "quantity": "1", + "x": 1, + "y": 4, + "z": 1, + "unit": "", + "parameters": "{}" + }, + { + "typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb", + "code": "", + "name": "配液瓶(小)24", + "quantity": "1", + "x": 2, + "y": 4, + "z": 1, + "unit": "", + "parameters": "{}" + } + ] + } \ No newline at end of file diff --git a/test/resources/test.json b/test/resources/test.json index 9fa9237..ee1be0f 100644 --- a/test/resources/test.json +++ b/test/resources/test.json @@ -1,191 +1,114 @@ -{ - "data": [ - { - "orderCode": "BSO2025103100006", - "orderName": "DP20250927001", - "errorMessage": null, - "usedMaterials": [ - { - "id": "3a1d4b13-25a6-cfb2-7315-159f14b32425", - "destinationType": "TempOrder", - "destinationId": "3a1d4b13-23cb-63e5-10df-6a1d38335163", - "materialId": "3a1d4b13-2467-e64d-d8bc-3957fb6e3240", - "materialName": "适配器块", - "materialCode": "0018-00065", - "quantity": "1块", - "materialTypeId": "efc3bb32-d504-4890-91c0-b64ed3ac80cf", - "materialTypeCode": "0018", - "materialTypeMode": "Consumables", - "materialTypeName": "适配器块", - "locationId": "3a1abd46-18fe-1f56-6ced-a1f7fe08e36c", - "locationCode": "0014-0001", - "locationShowName": "0014-0001" - }, - { - "id": "3a1d4b13-2420-8cfe-17f1-5f77a6ff6dc3", - "destinationType": "TempOrder", - "destinationId": "3a1d4b13-23cb-63e5-10df-6a1d38335163", - "materialId": "3a1d4b11-e448-bf90-d0bd-b20758425370", - "materialName": "test1", - "materialCode": "0001-00063", - "quantity": "1块", - "materialTypeId": "3a190c8b-3284-af78-d29f-9a69463ad047", - "materialTypeCode": "0001", - "materialTypeMode": "Sample", - "materialTypeName": "配液瓶(小)板", - "locationId": "3a19deae-2c7a-2426-6d71-e9de3cb158b1", - "locationCode": "4", - "locationShowName": "4" - }, - { - "id": "3a1d4b13-2420-73a1-2b4d-7bf6dd993c36", - "destinationType": "TempOrder", - "destinationId": "3a1d4b13-23cb-63e5-10df-6a1d38335163", - "materialId": "3a1d4b11-e448-fea3-8291-0b66ecd06d72", - "materialName": "test1", - "materialCode": "0002-00282", - "quantity": "1块", - "materialTypeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb", - "materialTypeCode": "0002", - "materialTypeMode": "Sample", - "materialTypeName": "配液瓶(小)", - "locationId": "3a19deae-2c7a-2426-6d71-e9de3cb158b1", - "locationCode": "4-1/1", - "locationShowName": "4-1/1" - }, - { - "id": "3a1d4b13-2420-e45f-192d-639887ad73b7", - "destinationType": "TempOrder", - "destinationId": "3a1d4b13-23cb-63e5-10df-6a1d38335163", - "materialId": "3a1d4b12-67fc-5f91-13ed-c223d0155399", - "materialName": "test2", - "materialCode": "0010-00059", - "quantity": "1块", - "materialTypeId": "3a192fa4-007d-ec7b-456e-2a8be7a13f23", - "materialTypeCode": "0010", - "materialTypeMode": "Sample", - "materialTypeName": "5ml分液瓶板", - "locationId": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3", - "locationCode": "5", - "locationShowName": "5" - }, - { - "id": "3a1d4b13-2420-c052-93cc-002f0aae79fc", - "destinationType": "TempOrder", - "destinationId": "3a1d4b13-23cb-63e5-10df-6a1d38335163", - "materialId": "3a1d4b12-67fc-60f7-1129-3d1ef2a2d1f8", - "materialName": "test2", - "materialCode": "0007-00211", - "quantity": "1块", - "materialTypeId": "3a192c2a-ebb7-58a1-480d-8b3863bf74f4", - "materialTypeCode": "0007", - "materialTypeMode": "Sample", - "materialTypeName": "5ml分液瓶", - "locationId": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3", - "locationCode": "5-1/1", - "locationShowName": "5-1/1" - } - ] - }, - { - "orderCode": "BSO2025103100007", - "orderName": "DP20250927002", - "errorMessage": null, - "usedMaterials": [ - { - "id": "3a1d4b13-264b-aca7-9e97-ab4df186d5c2", - "destinationType": "TempOrder", - "destinationId": "3a1d4b13-260c-9239-5c8a-ecb6fd96dc86", - "materialId": "3a1d4b13-2467-e64d-d8bc-3957fb6e3240", - "materialName": "适配器块", - "materialCode": "0018-00065", - "quantity": "1块", - "materialTypeId": "efc3bb32-d504-4890-91c0-b64ed3ac80cf", - "materialTypeCode": "0018", - "materialTypeMode": "Consumables", - "materialTypeName": "适配器块", - "locationId": "3a1abd46-18fe-1f56-6ced-a1f7fe08e36c", - "locationCode": "0014-0001", - "locationShowName": "0014-0001" - }, - { - "id": "3a1d4b13-263e-873e-1331-7e668b411e98", - "destinationType": "TempOrder", - "destinationId": "3a1d4b13-260c-9239-5c8a-ecb6fd96dc86", - "materialId": "3a1d4b11-e448-bf90-d0bd-b20758425370", - "materialName": "test1", - "materialCode": "0001-00063", - "quantity": "1块", - "materialTypeId": "3a190c8b-3284-af78-d29f-9a69463ad047", - "materialTypeCode": "0001", - "materialTypeMode": "Sample", - "materialTypeName": "配液瓶(小)板", - "locationId": "3a19deae-2c7a-2426-6d71-e9de3cb158b1", - "locationCode": "4", - "locationShowName": "4" - }, - { - "id": "3a1d4b13-263e-7884-d9e0-b010478b7448", - "destinationType": "TempOrder", - "destinationId": "3a1d4b13-260c-9239-5c8a-ecb6fd96dc86", - "materialId": "3a1d4b11-e448-82e0-6a64-6230ee1bf0a9", - "materialName": "test1", - "materialCode": "0002-00283", - "quantity": "1块", - "materialTypeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb", - "materialTypeCode": "0002", - "materialTypeMode": "Sample", - "materialTypeName": "配液瓶(小)", - "locationId": "3a19deae-2c7a-2426-6d71-e9de3cb158b1", - "locationCode": "4-1/2", - "locationShowName": "4-1/2" - }, - { - "id": "3a1d4b13-263e-6e99-b513-66047191643f", - "destinationType": "TempOrder", - "destinationId": "3a1d4b13-260c-9239-5c8a-ecb6fd96dc86", - "materialId": "3a1d4b12-67fc-5f91-13ed-c223d0155399", - "materialName": "test2", - "materialCode": "0010-00059", - "quantity": "1块", - "materialTypeId": "3a192fa4-007d-ec7b-456e-2a8be7a13f23", - "materialTypeCode": "0010", - "materialTypeMode": "Sample", - "materialTypeName": "5ml分液瓶板", - "locationId": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3", - "locationCode": "5", - "locationShowName": "5" - }, - { - "id": "3a1d4b13-263e-5b21-2c41-53e4ea7fe947", - "destinationType": "TempOrder", - "destinationId": "3a1d4b13-260c-9239-5c8a-ecb6fd96dc86", - "materialId": "3a1d4b12-67fc-131a-82ff-87e9e7708f9f", - "materialName": "test2", - "materialCode": "0007-00212", - "quantity": "1块", - "materialTypeId": "3a192c2a-ebb7-58a1-480d-8b3863bf74f4", - "materialTypeCode": "0007", - "materialTypeMode": "Sample", - "materialTypeName": "5ml分液瓶", - "locationId": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3", - "locationCode": "5-1/2", - "locationShowName": "5-1/2" - } - ] - } - ], - "code": 1, - "message": "", - "timestamp": 1761891208109 -} - -25-10-31 [14:27:52,203] [ERROR] 从Bioyond同步物料数据失败: 'BottleCarrier' object has no attribute 'tracker' [sync_from_external:83] [unilabos.utils.log.station] -Traceback (most recent call last): - File "C:\ML\GitHub\Uni-Lab-OS\unilabos\devices\workstation\bioyond_studio\station.py", line 73, in sync_from_external - unilab_resources = resource_bioyond_to_plr( - ^^^^^^^^^^^^^^^^^^^^^^^^ - File "C:\ML\GitHub\Uni-Lab-OS\unilabos\resources\graphio.py", line 661, in resource_bioyond_to_plr - bottle.tracker.liquids = [ - ^^^^^^^^^^^^^^ -AttributeError: 'BottleCarrier' object has no attribute 'tracker' \ No newline at end of file +[ + { + "id": "3a1d4b7e-4bdc-16bf-7169-f60350d03c7e", + "typeName": "配液瓶(小)板", + "code": "0001-00088", + "barCode": "", + "name": "test1", + "quantity": 1.0, + "lockQuantity": 0.0, + "unit": "块", + "status": 1, + "isUse": false, + "locations": [ + { + "id": "3a19deae-2c7a-2426-6d71-e9de3cb158b1", + "whid": "3a19deae-2c79-05a3-9c76-8e6760424841", + "whName": "手动堆栈", + "code": "4", + "x": 2, + "y": 1, + "z": 1, + "quantity": 0 + } + ], + "detail": [ + { + "id": "3a1d4b7e-4bdc-12e8-4d26-dddc77b03f63", + "detailMaterialId": "3a1d4b7e-4bdc-4e9e-8a3c-e9ba4a26457e", + "code": null, + "name": "test1", + "quantity": "1", + "lockQuantity": "0", + "unit": "块", + "x": 1, + "y": 2, + "z": 1, + "associateId": null, + "typeName": "配液瓶(小)", + "typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb" + }, + { + "id": "3a1d4b7e-4bdc-35b6-22d4-e6f3235e1c27", + "detailMaterialId": "3a1d4b7e-4bdc-ce0f-1fbb-b88de76fce98", + "code": null, + "name": "test1", + "quantity": "1", + "lockQuantity": "0", + "unit": "块", + "x": 1, + "y": 1, + "z": 1, + "associateId": null, + "typeName": "配液瓶(小)", + "typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb" + } + ] + }, + { + "id": "3a1d4b7e-ee61-ae87-9cd0-31c7e6621b18", + "typeName": "5ml分液瓶板", + "code": "0010-00089", + "barCode": "", + "name": "test2", + "quantity": 1.0, + "lockQuantity": 0.0, + "unit": "块", + "status": 1, + "isUse": false, + "locations": [ + { + "id": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3", + "whid": "3a19deae-2c79-05a3-9c76-8e6760424841", + "whName": "手动堆栈", + "code": "5", + "x": 2, + "y": 2, + "z": 1, + "quantity": 0 + } + ], + "detail": [ + { + "id": "3a1d4b7e-ee61-8fb3-9a39-2c2841c3c8d0", + "detailMaterialId": "3a1d4b7e-ee61-305c-fe30-2620017ca1bd", + "code": null, + "name": "test2", + "quantity": "1", + "lockQuantity": "0", + "unit": "块", + "x": 1, + "y": 1, + "z": 1, + "associateId": null, + "typeName": "5ml分液瓶", + "typeId": "3a192c2a-ebb7-58a1-480d-8b3863bf74f4" + }, + { + "id": "3a1d4b7e-ee61-ef5f-a7d1-f9399a4d3145", + "detailMaterialId": "3a1d4b7e-ee61-2f1d-6969-202ad3cbe226", + "code": null, + "name": "test2", + "quantity": "1", + "lockQuantity": "0", + "unit": "块", + "x": 1, + "y": 2, + "z": 1, + "associateId": null, + "typeName": "5ml分液瓶", + "typeId": "3a192c2a-ebb7-58a1-480d-8b3863bf74f4" + } + ] + } +] \ No newline at end of file diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index c40945e..092a87f 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -968,7 +968,7 @@ class BioyondCellWorkstation(BioyondWorkstation): logger.error(f"✗ 执行失败: {e}") return {"success": False, "error": str(e)} -def create_material( + def create_material( self, material_name: str, type_id: str, @@ -985,8 +985,7 @@ def create_material( 包含创建结果、物料ID以及入库结果的字典。 """ material_name = (material_name or "").strip() - if not material_name: - raise ValueError("material_name 不能为空") + resolved_type_id = (type_id or "").strip() # 优先从 SOLID_LIQUID_MAPPINGS 中获取模板数据 template = SOLID_LIQUID_MAPPINGS.get(material_name) @@ -1082,14 +1081,68 @@ def create_material( } -# -------------------------------- + def create_sample( + self, + name: str, + board_type: str, + bottle_type: str, + location_code: str + ) -> Dict[str, Any]: + """创建配液板物料并自动入库。 + Args: + material_name: 物料名称,支持 "5ml分液瓶板"/"5ml分液瓶"、"配液瓶(小)板"/"配液瓶(小)"。 + quantity: 主物料与明细的数量,默认 1。 + location_code: 库位编号,例如 "A01",将自动映射为 "手动堆栈" 下的 UUID。 + """ + carrier_type_id = MATERIAL_TYPE_MAPPINGS[board_type][1] + bottle_type_id = MATERIAL_TYPE_MAPPINGS[bottle_type][1] + location_id = WAREHOUSE_MAPPING["手动堆栈"]["site_uuids"][location_code] + + # 新建小瓶 + details = [] + for y in range(1, 5): + for x in range(1, 3): + details.append({ + "typeId": bottle_type_id, + "code": "", + "name": str(bottle_type) + str(x) + str(y), + "quantity": "1", + "x": x, + "y": y, + "z": 1, + "unit": "个", + "parameters": json.dumps({"unit": "个"}, ensure_ascii=False), + }) + + data = { + "typeId": carrier_type_id, + "code": "", + "barCode": "", + "name": name, + "unit": "块", + "parameters": json.dumps({"unit": "块"}, ensure_ascii=False), + "quantity": "1", + "details": details, + } + # print("xxx:",data) + create_result = self._post_lims("/api/lims/storage/material", data) + sample_uuid = create_result.get("data") + + final_result = self._post_lims("/api/lims/storage/inbound", { + "materialId": sample_uuid, + "locationId": location_id, + }) + return final_result + + if __name__ == "__main__": lab_registry.setup() ws = BioyondCellWorkstation() + ws.create_sample(name="test", board_type="配液瓶(小)板", bottle_type="配液瓶(小)", location_code="B01") # logger.info(ws.scheduler_stop()) - logger.info(ws.scheduler_start()) + # logger.info(ws.scheduler_start()) # results = ws.create_materials(SOLID_LIQUID_MAPPINGS) # for r in results: @@ -1098,11 +1151,11 @@ if __name__ == "__main__": # result = ws.create_and_inbound_materials() # 继续后续流程 - logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 - # # 使用正斜杠或 Path 对象来指定文件路径 - excel_path = Path("unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025092701.xlsx") - logger.info(ws.create_orders(excel_path)) - logger.info(ws.transfer_3_to_2_to_1()) + # logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 + # # # 使用正斜杠或 Path 对象来指定文件路径 + # excel_path = Path("unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025092701.xlsx") + # logger.info(ws.create_orders(excel_path)) + # logger.info(ws.transfer_3_to_2_to_1()) # logger.info(ws.transfer_1_to_2()) # logger.info(ws.scheduler_start()) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx index 844fc84d932f618891abbdf6efdefc4bcf16289f..abaf145e68e8910ddf877637d20ff10748c489ad 100644 GIT binary patch delta 109 zcmbQSmT~`D#tr?d0{b^wimsPpU;tqW1_p-727wZrx2viNO%~8*V=^+ETx_Ac`L+26 z8BVy$$xlMU8Pg`)gxWAkyG*VMwPx&^ye-s%@&Dxap|*_ilXb)F7+*}z2~%U!3jvzQ F006IYBS-)M delta 100 zcmdnLmT|^f#tr?de40E$H#?*l7(iHpVX|DP)aLD~YC@Cit$8;KT6~n@ge#cL9U9JP vKRG$nhUts* Date: Fri, 31 Oct 2025 19:02:06 +0800 Subject: [PATCH 040/104] Delete button_battery_station.py --- .../button_battery_station.py | 1006 ----------------- 1 file changed, 1006 deletions(-) delete mode 100644 unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py diff --git a/unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py b/unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py deleted file mode 100644 index eae09b8..0000000 --- a/unilabos/devices/workstation/coin_cell_assembly/button_battery_station.py +++ /dev/null @@ -1,1006 +0,0 @@ -""" -纽扣电池组装工作站物料类定义 -Button Battery Assembly Station Resource Classes -""" - -from __future__ import annotations - -from collections import OrderedDict -from typing import Any, Dict, List, Optional, TypedDict, Union, cast - -from pylabrobot.resources.coordinate import Coordinate -from pylabrobot.resources.container import Container -from pylabrobot.resources.deck import Deck -from pylabrobot.resources.itemized_resource import ItemizedResource -from pylabrobot.resources.resource import Resource -from pylabrobot.resources.resource_stack import ResourceStack -from pylabrobot.resources.tip_rack import TipRack, TipSpot -from pylabrobot.resources.trash import Trash -from pylabrobot.resources.utils import create_ordered_items_2d - - -class ElectrodeSheetState(TypedDict): - diameter: float # 直径 (mm) - thickness: float # 厚度 (mm) - mass: float # 质量 (g) - material_type: str # 材料类型(正极、负极、隔膜、弹片、垫片、铝箔等) - height: float - electrolyte_name: str - data_electrolyte_code: str - open_circuit_voltage: float - assembly_pressure: float - electrolyte_volume: float - - info: Optional[str] # 附加信息 - -class ElectrodeSheet(Resource): - """极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料""" - - def __init__( - self, - name: str = "极片", - size_x=10, - size_y=10, - size_z=10, - category: str = "electrode_sheet", - model: Optional[str] = None, - ): - """初始化极片 - - Args: - name: 极片名称 - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - category=category, - model=model, - ) - self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState( - diameter=14, - thickness=0.1, - mass=0.5, - material_type="copper", - info=None - ) - - # TODO: 这个还要不要?给self._unilabos_state赋值的? - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - #序列化 - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - -# TODO: 这个应该只能放一个极片 -class MaterialHoleState(TypedDict): - diameter: int - depth: int - max_sheets: int - info: Optional[str] # 附加信息 - -class MaterialHole(Resource): - """料板洞位类""" - children: List[ElectrodeSheet] = [] - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - category: str = "material_hole", - **kwargs - ): - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - category=category, - ) - self._unilabos_state: MaterialHoleState = MaterialHoleState( - diameter=20, - depth=10, - max_sheets=1, - info=None - ) - - def get_all_sheet_info(self): - info_list = [] - for sheet in self.children: - info_list.append(sheet._unilabos_state["info"]) - return info_list - - #这个函数函数好像没用,一般不会集中赋值质量 - def set_all_sheet_mass(self): - for sheet in self.children: - sheet._unilabos_state["mass"] = 0.5 # 示例:设置质量为0.5g - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - #移动极片前先取出对象 - def get_sheet_with_name(self, name: str) -> Optional[ElectrodeSheet]: - for sheet in self.children: - if sheet.name == name: - return sheet - return None - - def has_electrode_sheet(self) -> bool: - """检查洞位是否有极片""" - return len(self.children) > 0 - - def assign_child_resource( - self, - resource: ElectrodeSheet, - location: Optional[Coordinate], - reassign: bool = True, - ): - """放置极片""" - # TODO: 这里要改,diameter找不到,加入._unilabos_state后应该没问题 - #if resource._unilabos_state["diameter"] > self._unilabos_state["diameter"]: - # raise ValueError(f"极片直径 {resource._unilabos_state['diameter']} 超过洞位直径 {self._unilabos_state['diameter']}") - #if len(self.children) >= self._unilabos_state["max_sheets"]: - # raise ValueError(f"洞位已满,无法放置更多极片") - super().assign_child_resource(resource, location, reassign) - - # 根据children的编号取物料对象。 - def get_electrode_sheet_info(self, index: int) -> ElectrodeSheet: - return self.children[index] - - - -class MaterialPlateState(TypedDict): - hole_spacing_x: float - hole_spacing_y: float - hole_diameter: float - info: Optional[str] # 附加信息 - -class MaterialPlate(ItemizedResource[MaterialHole]): - """料板类 - 4x4个洞位,每个洞位放1个极片""" - - children: List[MaterialHole] - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - ordered_items: Optional[Dict[str, MaterialHole]] = None, - ordering: Optional[OrderedDict[str, str]] = None, - category: str = "material_plate", - model: Optional[str] = None, - fill: bool = False - ): - """初始化料板 - - Args: - name: 料板名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - hole_diameter: 洞直径 (mm) - hole_depth: 洞深度 (mm) - hole_spacing_x: X方向洞位间距 (mm) - hole_spacing_y: Y方向洞位间距 (mm) - number: 编号 - category: 类别 - model: 型号 - """ - self._unilabos_state: MaterialPlateState = MaterialPlateState( - hole_spacing_x=24.0, - hole_spacing_y=24.0, - hole_diameter=20.0, - info="", - ) - # 创建4x4的洞位 - # TODO: 这里要改,对应不同形状 - holes = create_ordered_items_2d( - klass=MaterialHole, - num_items_x=4, - num_items_y=4, - dx=(size_x - 4 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中 - dy=(size_y - 4 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中 - dz=size_z, - item_dx=self._unilabos_state["hole_spacing_x"], - item_dy=self._unilabos_state["hole_spacing_y"], - size_x = 16, - size_y = 16, - size_z = 16, - ) - if fill: - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=holes, - category=category, - model=model, - ) - else: - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=ordered_items, - ordering=ordering, - category=category, - model=model, - ) - - def update_locations(self): - # TODO:调多次相加 - holes = create_ordered_items_2d( - klass=MaterialHole, - num_items_x=4, - num_items_y=4, - dx=(self._size_x - 3 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中 - dy=(self._size_y - 3 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中 - dz=self._size_z, - item_dx=self._unilabos_state["hole_spacing_x"], - item_dy=self._unilabos_state["hole_spacing_y"], - size_x = 1, - size_y = 1, - size_z = 1, - ) - for item, original_item in zip(holes.items(), self.children): - original_item.location = item[1].location - - -class PlateSlot(ResourceStack): - """板槽位类 - 1个槽上能堆放8个板,移板只能操作最上方的板""" - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - max_plates: int = 8, - category: str = "plate_slot", - model: Optional[str] = None - ): - """初始化板槽位 - - Args: - name: 槽位名称 - max_plates: 最大板数量 - category: 类别 - """ - super().__init__( - name=name, - direction="z", # Z方向堆叠 - resources=[], - ) - self.max_plates = max_plates - self.category = category - - def can_add_plate(self) -> bool: - """检查是否可以添加板""" - return len(self.children) < self.max_plates - - def add_plate(self, plate: MaterialPlate) -> None: - """添加料板""" - if not self.can_add_plate(): - raise ValueError(f"槽位 {self.name} 已满,无法添加更多板") - self.assign_child_resource(plate) - - def get_top_plate(self) -> MaterialPlate: - """获取最上方的板""" - if len(self.children) == 0: - raise ValueError(f"槽位 {self.name} 为空") - return cast(MaterialPlate, self.get_top_item()) - - def take_top_plate(self) -> MaterialPlate: - """取出最上方的板""" - top_plate = self.get_top_plate() - self.unassign_child_resource(top_plate) - return top_plate - - def can_access_for_picking(self) -> bool: - """检查是否可以进行取料操作(只有最上方的板能进行取料操作)""" - return len(self.children) > 0 - - def serialize(self) -> dict: - return { - **super().serialize(), - "max_plates": self.max_plates, - } - - -class ClipMagazineHole(Container): - """子弹夹洞位类""" - - def __init__( - self, - name: str, - diameter: float, - depth: float, - max_sheets: int = 100, - category: str = "clip_magazine_hole", - ): - """初始化子弹夹洞位 - - Args: - name: 洞位名称 - diameter: 洞直径 (mm) - depth: 洞深度 (mm) - max_sheets: 最大极片数量 - category: 类别 - """ - super().__init__( - name=name, - size_x=diameter, - size_y=diameter, - size_z=depth, - category=category, - ) - self.diameter = diameter - self.depth = depth - self.max_sheets = max_sheets - self._sheets: List[ElectrodeSheet] = [] - - def can_add_sheet(self, sheet: ElectrodeSheet) -> bool: - """检查是否可以添加极片""" - return (len(self._sheets) < self.max_sheets and - sheet.diameter <= self.diameter) - - def add_sheet(self, sheet: ElectrodeSheet) -> None: - """添加极片""" - if not self.can_add_sheet(sheet): - raise ValueError(f"无法向洞位 {self.name} 添加极片") - self._sheets.append(sheet) - - def take_sheet(self) -> ElectrodeSheet: - """取出极片""" - if len(self._sheets) == 0: - raise ValueError(f"洞位 {self.name} 没有极片") - return self._sheets.pop() - - def get_sheet_count(self) -> int: - """获取极片数量""" - return len(self._sheets) - - def serialize_state(self) -> Dict[str, Any]: - return { - "sheet_count": len(self._sheets), - "sheets": [sheet.serialize() for sheet in self._sheets], - } - -# TODO: 这个要改 -class ClipMagazine(Resource): - """子弹夹类 - 有6个洞位,每个洞位放多个极片""" - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - hole_spacing: float = 25.0, - max_sheets_per_hole: int = 100, - category: str = "clip_magazine", - model: Optional[str] = None, - ): - """初始化子弹夹 - - Args: - name: 子弹夹名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - hole_diameter: 洞直径 (mm) - hole_depth: 洞深度 (mm) - hole_spacing: 洞位间距 (mm) - max_sheets_per_hole: 每个洞位最大极片数量 - category: 类别 - model: 型号 - """ - # 创建6个洞位,排成2x3布局 - holes = create_ordered_items_2d( - klass=ClipMagazineHole, - num_items_x=3, - num_items_y=2, - dx=(size_x - 2 * hole_spacing) / 2, # 居中 - dy=(size_y - hole_spacing) / 2, # 居中 - dz=size_z - 0, - item_dx=hole_spacing, - item_dy=hole_spacing, - diameter=0, - depth=0, - ) - - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=holes, - category=category, - model=model, - ) - - self.hole_diameter = hole_diameter - self.hole_depth = hole_depth - self.max_sheets_per_hole = max_sheets_per_hole - - def serialize(self) -> dict: - return { - **super().serialize(), - "hole_diameter": self.hole_diameter, - "hole_depth": self.hole_depth, - "max_sheets_per_hole": self.max_sheets_per_hole, - } -#是一种类型注解,不用self -class BatteryState(TypedDict): - """电池状态字典""" - diameter: float - height: float - assembly_pressure: float - electrolyte_volume: float - electrolyte_name: str - -class Battery(Resource): - """电池类 - 可容纳极片""" - children: List[ElectrodeSheet] = [] - - def __init__( - self, - name: str, - size_x=1, - size_y=1, - size_z=1, - category: str = "battery", - ): - """初始化电池 - - Args: - name: 电池名称 - diameter: 直径 (mm) - height: 高度 (mm) - max_volume: 最大容量 (μL) - barcode: 二维码编号 - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=1, - size_y=1, - size_z=1, - category=category, - ) - self._unilabos_state: BatteryState = BatteryState( - diameter = 1.0, - height = 1.0, - assembly_pressure = 1.0, - electrolyte_volume = 1.0, - electrolyte_name = "DP001" - ) - - def add_electrolyte_with_bottle(self, bottle: Bottle) -> bool: - to_add_name = bottle._unilabos_state["electrolyte_name"] - if bottle.aspirate_electrolyte(10): - if self.add_electrolyte(to_add_name, 10): - pass - else: - bottle._unilabos_state["electrolyte_volume"] += 10 - - def set_electrolyte(self, name: str, volume: float) -> None: - """设置电解液信息""" - self._unilabos_state["electrolyte_name"] = name - self._unilabos_state["electrolyte_volume"] = volume - #这个应该没用,不会有加了后再加的事情 - def add_electrolyte(self, name: str, volume: float) -> bool: - """添加电解液信息""" - if name != self._unilabos_state["electrolyte_name"]: - return False - self._unilabos_state["electrolyte_volume"] += volume - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - -# 电解液作为属性放进去 - -class BatteryPressSlotState(TypedDict): - """电池状态字典""" - diameter: float =20.0 - depth: float = 4.0 - -class BatteryPressSlot(Resource): - """电池压制槽类 - 设备,可容纳一个电池""" - children: List[Battery] = [] - - def __init__( - self, - name: str = "BatteryPressSlot", - category: str = "battery_press_slot", - ): - """初始化电池压制槽 - - Args: - name: 压制槽名称 - diameter: 直径 (mm) - depth: 深度 (mm) - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=10, - size_y=12, - size_z=13, - category=category, - ) - self._unilabos_state: BatteryPressSlotState = BatteryPressSlotState() - - def has_battery(self) -> bool: - """检查是否有电池""" - return len(self.children) > 0 - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - - def assign_child_resource( - self, - resource: Battery, - location: Optional[Coordinate], - reassign: bool = True, - ): - """放置极片""" - # TODO: 让高京看下槽位只有一个电池时是否这么写。 - if self.has_battery(): - raise ValueError(f"槽位已含有一个电池,无法再放置其他电池") - super().assign_child_resource(resource, location, reassign) - - # 根据children的编号取物料对象。 - def get_battery_info(self, index: int) -> Battery: - return self.children[0] - -# TODO:这个移液枪架子看一下从哪继承 -class TipBox64State(TypedDict): - """电池状态字典""" - tip_diameter: float = 5.0 - tip_length: float = 50.0 - with_tips: bool = True - -class TipBox64(TipRack): - """64孔枪头盒类""" - - children: List[TipSpot] = [] - def __init__( - self, - name: str, - size_x: float = 127.8, - size_y: float = 85.5, - size_z: float = 60.0, - category: str = "tip_box_64", - model: Optional[str] = None, - ): - """初始化64孔枪头盒 - - Args: - name: 枪头盒名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - tip_diameter: 枪头直径 (mm) - tip_length: 枪头长度 (mm) - category: 类别 - model: 型号 - with_tips: 是否带枪头 - """ - from pylabrobot.resources.tip import Tip - - # 创建8x8=64个枪头位 - def make_tip(): - return Tip( - has_filter=False, - total_tip_length=20.0, - maximal_volume=1000, # 1mL - fitting_depth=8.0, - ) - - tip_spots = create_ordered_items_2d( - klass=TipSpot, - num_items_x=8, - num_items_y=8, - dx=8.0, - dy=8.0, - dz=0.0, - item_dx=9.0, - item_dy=9.0, - size_x=10, - size_y=10, - size_z=0.0, - make_tip=make_tip, - ) - self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate() - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=tip_spots, - category=category, - model=model, - with_tips=True, - ) - - - -class WasteTipBoxstate(TypedDict): - """"废枪头盒状态字典""" - max_tips: int = 100 - tip_count: int = 0 - -#枪头不是一次性的(同一溶液则反复使用),根据寄存器判断 -class WasteTipBox(Trash): - """废枪头盒类 - 100个枪头容量""" - - def __init__( - self, - name: str, - size_x: float = 127.8, - size_y: float = 85.5, - size_z: float = 60.0, - category: str = "waste_tip_box", - model: Optional[str] = None, - ): - """初始化废枪头盒 - - Args: - name: 废枪头盒名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - max_tips: 最大枪头容量 - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - category=category, - model=model, - ) - self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate() - - def add_tip(self) -> None: - """添加废枪头""" - if self._unilabos_state["tip_count"] >= self._unilabos_state["max_tips"]: - raise ValueError(f"废枪头盒 {self.name} 已满") - self._unilabos_state["tip_count"] += 1 - - def get_tip_count(self) -> int: - """获取枪头数量""" - return self._unilabos_state["tip_count"] - - def empty(self) -> None: - """清空废枪头盒""" - self._unilabos_state["tip_count"] = 0 - - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - - -class BottleRackState(TypedDict): - """ bottle_diameter: 瓶子直径 (mm) - bottle_height: 瓶子高度 (mm) - position_spacing: 位置间距 (mm)""" - bottle_diameter: float - bottle_height: float - name_to_index: dict - - - -class BottleRack(Resource): - """瓶架类 - 12个待配位置+12个已配位置""" - children: List[Bottle] = [] - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - category: str = "bottle_rack", - model: Optional[str] = None, - ): - """初始化瓶架 - - Args: - name: 瓶架名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - category=category, - model=model, - ) - # TODO: 添加瓶位坐标映射 - self.index_to_pos = { - 0: Coordinate.zero(), - 1: Coordinate(x=1, y=2, z=3) # 添加 - } - self.name_to_index = {} - self.name_to_pos = {} - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - - # TODO: 这里有些问题要重新写一下 - def assign_child_resource(self, resource: Bottle, location=Coordinate.zero(), reassign = True): - assert len(self.children) <= 12, "瓶架已满,无法添加更多瓶子" - index = len(self.children) - location = Coordinate(x=20 + (index % 4) * 15, y=20 + (index // 4) * 15, z=0) - self.name_to_pos[resource.name] = location - self.name_to_index[resource.name] = index - return super().assign_child_resource(resource, location, reassign) - - def assign_child_resource_by_index(self, resource: Bottle, index: int): - assert 0 <= index < 12, "无效的瓶子索引" - self.name_to_index[resource.name] = index - location = self.index_to_pos[index] - return super().assign_child_resource(resource, location) - - def unassign_child_resource(self, resource: Bottle): - super().unassign_child_resource(resource) - self.index_to_pos.pop(self.name_to_index.pop(resource.name, None), None) - - # def serialize(self): - # self.children.sort(key=lambda x: self.name_to_index.get(x.name, 0)) - # return super().serialize() - - -class BottleState(TypedDict): - diameter: float - height: float - electrolyte_name: str - electrolyte_volume: float - max_volume: float - -class Bottle(Resource): - """瓶子类 - 容纳电解液""" - - def __init__( - self, - name: str, - category: str = "bottle", - ): - """初始化瓶子 - - Args: - name: 瓶子名称 - diameter: 直径 (mm) - height: 高度 (mm) - max_volume: 最大体积 (μL) - barcode: 二维码 - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=1, - size_y=1, - size_z=1, - category=category, - ) - self._unilabos_state: BottleState = BottleState() - - def aspirate_electrolyte(self, volume: float) -> bool: - current_volume = self._unilabos_state["electrolyte_volume"] - assert current_volume > volume, f"Cannot aspirate {volume}μL, only {current_volume}μL available." - self._unilabos_state["electrolyte_volume"] -= volume - return True - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - -class CoincellDeck(Deck): - """纽扣电池组装工作站台面类""" - - def __init__( - self, - name: str = "coin_cell_deck", - size_x: float = 1620.0, # 3.66m - size_y: float = 1270.0, # 1.23m - size_z: float = 500.0, - origin: Coordinate = Coordinate(0, 0, 0), - category: str = "coin_cell_deck", - ): - """初始化纽扣电池组装工作站台面 - - Args: - name: 台面名称 - size_x: 长度 (mm) - 3.66m - size_y: 宽度 (mm) - 1.23m - size_z: 高度 (mm) - origin: 原点坐标 - category: 类别 - """ - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - origin=origin, - category=category, - ) - -#if __name__ == "__main__": -# # 转移极片的测试代码 -# deck = CoincellDeck("coin_cell_deck") -# ban_cao_wei = PlateSlot("ban_cao_wei", max_plates=8) -# deck.assign_child_resource(ban_cao_wei, Coordinate(x=0, y=0, z=0)) -# -# plate_1 = MaterialPlate("plate_1", 1,1,1, fill=True) -# for i, hole in enumerate(plate_1.children): -# sheet = ElectrodeSheet(f"hole_{i}_sheet_1") -# sheet._unilabos_state = { -# "diameter": 14, -# "info": "NMC", -# "mass": 5.0, -# "material_type": "positive_electrode", -# "thickness": 0.1 -# } -# hole._unilabos_state = { -# "depth": 1.0, -# "diameter": 14, -# "info": "", -# "max_sheets": 1 -# } -# hole.assign_child_resource(sheet, Coordinate.zero()) -# plate_1._unilabos_state = { -# "hole_spacing_x": 20.0, -# "hole_spacing_y": 20.0, -# "hole_diameter": 5, -# "info": "这是第一块料板" -# } -# plate_1.update_locations() -# ban_cao_wei.assign_child_resource(plate_1, Coordinate.zero()) -# # zi_dan_jia = ClipMagazine("zi_dan_jia", 1, 1, 1) -# # deck.assign_child_resource(ban_cao_wei, Coordinate(x=200, y=200, z=0)) -# -# from unilabos.resources.graphio import * -# A = tree_to_list([resource_plr_to_ulab(deck)]) -# with open("test.json", "w") as f: -# json.dump(A, f) -# -# -#def get_plate_with_14mm_hole(name=""): -# plate = MaterialPlate(name=name) -# for i in range(4): -# for j in range(4): -# hole = MaterialHole(f"{i+1}x{j+1}") -# hole._unilabos_state["diameter"] = 14 -# hole._unilabos_state["max_sheets"] = 1 -# plate.assign_child_resource(hole) -# return plate - -import json - -if __name__ == "__main__": - #electrode1 = BatteryPressSlot() - #print(electrode1.get_size_x()) - #print(electrode1.get_size_y()) - #print(electrode1.get_size_z()) - #jipian = ElectrodeSheet() - #jipian._unilabos_state["diameter"] = 18 - #print(jipian.serialize()) - #print(jipian.serialize_state()) - - deck = CoincellDeck(size_x=1000, - size_y=1000, - size_z=900) - - #liaopan = TipBox64(name="liaopan") - - #创建一个4*4的物料板 - liaopan1 = MaterialPlate(name="liaopan1", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) - #把物料板放到桌子上 - deck.assign_child_resource(liaopan1, Coordinate(x=0, y=0, z=0)) - #创建一个极片 - for i in range(16): - jipian = ElectrodeSheet(name=f"jipian1_{i}", size_x= 12, size_y=12, size_z=0.1) - liaopan1.children[i].assign_child_resource(jipian, location=None) -# - #创建一个4*4的物料板 - liaopan2 = MaterialPlate(name="liaopan2", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) - #把物料板放到桌子上 - deck.assign_child_resource(liaopan2, Coordinate(x=500, y=0, z=0)) - - #创建一个4*4的物料板 - liaopan3 = MaterialPlate(name="电池料盘", size_x=120.8, size_y=160.5, size_z=10.0, fill=True) - #把物料板放到桌子上 - deck.assign_child_resource(liaopan3, Coordinate(x=100, y=100, z=0)) - - - - #liaopan.children[3].assign_child_resource(jipian, location=None) - print(deck) - - - from unilabos.resources.graphio import convert_resources_from_type - from unilabos.config.config import BasicConfig - BasicConfig.ak = "4d5ce6ae-7234-4639-834e-93899b9caf94" - BasicConfig.sk = "505d3b0a-620e-459a-9905-1efcffce382a" - from unilabos.app.web.client import http_client - - resources = convert_resources_from_type([deck], [Resource]) - json.dump({"nodes": resources, "links": []}, open("button_battery_station_resources_unilab.json", "w"), indent=2) - - - #print(resources) - http_client.remote_addr = "https://uni-lab.test.bohrium.com/api/v1" - - http_client.resource_add(resources) \ No newline at end of file From 03745c5d0875f2ca22c29465317c98f7a1258f0f Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Sat, 1 Nov 2025 10:37:45 +0800 Subject: [PATCH 041/104] byxinyu111 --- new_cellconfig3c.json | 38 ++++ test/resources/test.json | 170 +++++++++++------- test/resources/test_resourcetreeset.py | 7 +- .../bioyond_cell/2025092701.xlsx | Bin 18159 -> 18159 bytes .../bioyond_cell/bioyond_cell_workstation.py | 76 +------- .../bioyond_cell/material_template.xlsx | Bin 22207 -> 22149 bytes .../workstation/bioyond_studio/config.py | 5 +- .../coin_cell_assembly/coin_cell_assembly.py | 3 +- unilabos/registry/devices/bioyond_cell.yaml | 77 +++++++- 9 files changed, 230 insertions(+), 146 deletions(-) create mode 100644 new_cellconfig3c.json diff --git a/new_cellconfig3c.json b/new_cellconfig3c.json new file mode 100644 index 0000000..3619085 --- /dev/null +++ b/new_cellconfig3c.json @@ -0,0 +1,38 @@ +{ + "nodes": [ + { + "id": "bioyond_cell_workstation", + "name": "配液分液工站", + "children": [ + ], + "parent": null, + "type": "device", + "class": "bioyond_cell", + "config": { + "protocol_type": [], + "station_resource": {} + }, + "data": {} + }, + { + "id": "BatteryStation", + "name": "扣电工作站", + "children": [ + "coin_cell_deck" + ], + "parent": null, + "type": "device", + "class": "coincellassemblyworkstation_device", + "position": { + "x": 600, + "y": 400, + "z": 0 + }, + "config": { + "debug_mode": false, + "protocol_type": [] + } + } + ], + "links": [] +} \ No newline at end of file diff --git a/test/resources/test.json b/test/resources/test.json index ee1be0f..95a29d2 100644 --- a/test/resources/test.json +++ b/test/resources/test.json @@ -1,22 +1,22 @@ [ { - "id": "3a1d4b7e-4bdc-16bf-7169-f60350d03c7e", + "id": "3a1d4c14-a9fb-d7dc-9e96-7a3ad6e50219", "typeName": "配液瓶(小)板", - "code": "0001-00088", + "code": "0001-00093", "barCode": "", - "name": "test1", - "quantity": 1.0, + "name": "test", + "quantity": 2.0, "lockQuantity": 0.0, "unit": "块", "status": 1, "isUse": false, "locations": [ { - "id": "3a19deae-2c7a-2426-6d71-e9de3cb158b1", + "id": "3a19deae-2c7a-36f5-5e41-02c5b66feaea", "whid": "3a19deae-2c79-05a3-9c76-8e6760424841", "whName": "手动堆栈", - "code": "4", - "x": 2, + "code": "1", + "x": 1, "y": 1, "z": 1, "quantity": 0 @@ -24,13 +24,58 @@ ], "detail": [ { - "id": "3a1d4b7e-4bdc-12e8-4d26-dddc77b03f63", - "detailMaterialId": "3a1d4b7e-4bdc-4e9e-8a3c-e9ba4a26457e", - "code": null, - "name": "test1", + "id": "3a1d4c14-a9fc-1daa-71fa-146cb1ccb930", + "detailMaterialId": "3a1d4c14-a9fc-4f38-4c48-68486c391c42", + "code": "0001-00093 - 05", + "name": "配液瓶(小)", "quantity": "1", "lockQuantity": "0", - "unit": "块", + "unit": "个", + "x": 1, + "y": 3, + "z": 1, + "associateId": null, + "typeName": "配液瓶(小)", + "typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb" + }, + { + "id": "3a1d4c14-a9fc-3659-ea61-cd587da9e131", + "detailMaterialId": "3a1d4c14-a9fc-018f-93e5-c49343d37758", + "code": "0001-00093 - 08", + "name": "配液瓶(小)", + "quantity": "1", + "lockQuantity": "0", + "unit": "个", + "x": 2, + "y": 4, + "z": 1, + "associateId": null, + "typeName": "配液瓶(小)", + "typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb" + }, + { + "id": "3a1d4c14-a9fc-3f94-de83-979d2646e313", + "detailMaterialId": "3a1d4c14-a9fc-9987-c0ef-4b7cbad49e6b", + "code": "0001-00093 - 01", + "name": "配液瓶(小)", + "quantity": "1", + "lockQuantity": "0", + "unit": "个", + "x": 1, + "y": 1, + "z": 1, + "associateId": null, + "typeName": "配液瓶(小)", + "typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb" + }, + { + "id": "3a1d4c14-a9fc-8c35-6b25-913b11dbaf4e", + "detailMaterialId": "3a1d4c14-a9fc-9a83-865b-0c26ea5e8cc4", + "code": "0001-00093 - 03", + "name": "配液瓶(小)", + "quantity": "1", + "lockQuantity": "0", + "unit": "个", "x": 1, "y": 2, "z": 1, @@ -39,75 +84,64 @@ "typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb" }, { - "id": "3a1d4b7e-4bdc-35b6-22d4-e6f3235e1c27", - "detailMaterialId": "3a1d4b7e-4bdc-ce0f-1fbb-b88de76fce98", - "code": null, - "name": "test1", + "id": "3a1d4c14-a9fc-b41f-e968-64953bfddccd", + "detailMaterialId": "3a1d4c14-a9fc-daf7-9d64-e5ec8d3ae0e2", + "code": "0001-00093 - 07", + "name": "配液瓶(小)", "quantity": "1", "lockQuantity": "0", - "unit": "块", + "unit": "个", "x": 1, + "y": 4, + "z": 1, + "associateId": null, + "typeName": "配液瓶(小)", + "typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb" + }, + { + "id": "3a1d4c14-a9fc-c20f-c26e-b1bb2cdc3bca", + "detailMaterialId": "3a1d4c14-a9fc-673b-ac83-aaaf71287f1f", + "code": "0001-00093 - 06", + "name": "配液瓶(小)", + "quantity": "1", + "lockQuantity": "0", + "unit": "个", + "x": 2, + "y": 3, + "z": 1, + "associateId": null, + "typeName": "配液瓶(小)", + "typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb" + }, + { + "id": "3a1d4c14-a9fc-cf21-059c-fde361d82b6f", + "detailMaterialId": "3a1d4c14-a9fc-25b1-e736-6b0d8dac0fae", + "code": "0001-00093 - 02", + "name": "配液瓶(小)", + "quantity": "1", + "lockQuantity": "0", + "unit": "个", + "x": 2, "y": 1, "z": 1, "associateId": null, "typeName": "配液瓶(小)", "typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb" - } - ] - }, - { - "id": "3a1d4b7e-ee61-ae87-9cd0-31c7e6621b18", - "typeName": "5ml分液瓶板", - "code": "0010-00089", - "barCode": "", - "name": "test2", - "quantity": 1.0, - "lockQuantity": 0.0, - "unit": "块", - "status": 1, - "isUse": false, - "locations": [ + }, { - "id": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3", - "whid": "3a19deae-2c79-05a3-9c76-8e6760424841", - "whName": "手动堆栈", - "code": "5", + "id": "3a1d4c14-a9fc-d732-2b93-9b2bd2bf581b", + "detailMaterialId": "3a1d4c14-a9fc-7f5d-b6b6-8bcb2e15f320", + "code": "0001-00093 - 04", + "name": "配液瓶(小)", + "quantity": "1", + "lockQuantity": "0", + "unit": "个", "x": 2, "y": 2, "z": 1, - "quantity": 0 - } - ], - "detail": [ - { - "id": "3a1d4b7e-ee61-8fb3-9a39-2c2841c3c8d0", - "detailMaterialId": "3a1d4b7e-ee61-305c-fe30-2620017ca1bd", - "code": null, - "name": "test2", - "quantity": "1", - "lockQuantity": "0", - "unit": "块", - "x": 1, - "y": 1, - "z": 1, "associateId": null, - "typeName": "5ml分液瓶", - "typeId": "3a192c2a-ebb7-58a1-480d-8b3863bf74f4" - }, - { - "id": "3a1d4b7e-ee61-ef5f-a7d1-f9399a4d3145", - "detailMaterialId": "3a1d4b7e-ee61-2f1d-6969-202ad3cbe226", - "code": null, - "name": "test2", - "quantity": "1", - "lockQuantity": "0", - "unit": "块", - "x": 1, - "y": 2, - "z": 1, - "associateId": null, - "typeName": "5ml分液瓶", - "typeId": "3a192c2a-ebb7-58a1-480d-8b3863bf74f4" + "typeName": "配液瓶(小)", + "typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb" } ] } diff --git a/test/resources/test_resourcetreeset.py b/test/resources/test_resourcetreeset.py index b7602ed..99c6a98 100644 --- a/test/resources/test_resourcetreeset.py +++ b/test/resources/test_resourcetreeset.py @@ -9,6 +9,7 @@ from unilabos.ros.nodes.resource_tracker import ResourceTreeSet from unilabos.registry.registry import lab_registry from unilabos.resources.bioyond.decks import BIOYOND_PolymerReactionStation_Deck +from unilabos.resources.bioyond.decks import YB_Deck lab_registry.setup() @@ -16,6 +17,8 @@ lab_registry.setup() type_mapping = { "加样头(大)": ("YB_jia_yang_tou_da_1X1_carrier", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), "液": ("YB_1BottleCarrier", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), + "配液瓶(小)板": ("YB_6x_SmallSolutionBottleCarrier", "3a190c8b-3284-af78-d29f-9a69463ad047"), + "配液瓶(小)": ("YB_pei_ye_xiao_Bottler", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"), } @@ -56,10 +59,10 @@ def bioyond_materials_liquidhandling_2() -> list[dict]: def test_resourcetreeset_from_plr() -> list[dict]: # 直接加载 bioyond_materials_reaction.json 文件 current_dir = os.path.dirname(os.path.abspath(__file__)) - json_path = os.path.join(current_dir, "YB_materials_info.json") + json_path = os.path.join(current_dir, "test.json") with open(json_path, "r", encoding="utf-8") as f: materials = json.load(f) - deck = BIOYOND_PolymerReactionStation_Deck("test_deck") + deck = YB_Deck("test_deck") output = resource_bioyond_to_plr(materials, type_mapping=type_mapping, deck=deck) print(output) # print(deck.summary()) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx index d1e9d2f31a2385b1028209891e31c975ebc9744b..4147095ed2784e56ccc6f637aec02c2a575c2cc8 100644 GIT binary patch delta 66 zcmaFg%lN*RaYMfJj7zwu^!&0=Oo!_Av@&M*O~ R2K`N Dict[str, Any]: + def wait_for_order_finish(self, order_code: str, timeout: int = 36000) -> Dict[str, Any]: """ 等待指定 orderCode 的 /report/order_finish 报送。 Args: @@ -253,7 +253,7 @@ class BioyondCellWorkstation(BioyondWorkstation): def auto_feeding4to3( self, # ★ 修改点:默认模板路径 - xlsx_path: Optional[str] = "unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\material_template.xlsx", + xlsx_path: Optional[str] = "C:/ML/GitHub/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx", # ---------------- WH4 - 加样头面 (Z=1, 12个点位) ---------------- WH4_x1_y1_z1_1_materialName: str = "", WH4_x1_y1_z1_1_quantity: float = 0.0, WH4_x2_y1_z1_2_materialName: str = "", WH4_x2_y1_z1_2_quantity: float = 0.0, @@ -391,73 +391,7 @@ class BioyondCellWorkstation(BioyondWorkstation): # 等待完成报送 result = self.wait_for_order_finish(order_code) return result - - - - # 3.30 自动化上料(老版本) - def auto_feeding4to3_from_xlsx(self, xlsx_path: str) -> Dict[str, Any]: - """ - 根据固定模板解析 Excel: - - 四号手套箱加样头面 (2-13行, 3-7列) - - 四号手套箱原液瓶面 (15-23行, 3-9列) - - 三号手套箱人工堆栈 (26-40行, 3-7列) - """ - path = Path(xlsx_path) - if not path.exists(): - raise FileNotFoundError(f"未找到 Excel 文件:{path}") - - try: - df = pd.read_excel(path, sheet_name=0, header=None, engine="openpyxl") - except Exception as e: - raise RuntimeError(f"读取 Excel 失败:{e}") - - items: List[Dict[str, Any]] = [] - - # 四号手套箱 - 加样头面(2-13行, 3-7列) - for _, row in df.iloc[1:13, 2:7].iterrows(): - item = { - "sourceWHName": "四号手套箱堆栈", - "posX": int(row[2]), - "posY": int(row[3]), - "posZ": int(row[4]), - "materialName": str(row[5]).strip() if pd.notna(row[5]) else "", - "quantity": float(row[6]) if pd.notna(row[6]) else 0.0, - } - if item["materialName"]: - items.append(item) - - # 四号手套箱 - 原液瓶面(15-23行, 3-9列) - for _, row in df.iloc[14:23, 2:9].iterrows(): - item = { - "sourceWHName": "四号手套箱堆栈", - "posX": int(row[2]), - "posY": int(row[3]), - "posZ": int(row[4]), - "materialName": str(row[5]).strip() if pd.notna(row[5]) else "", - "quantity": float(row[6]) if pd.notna(row[6]) else 0.0, - "materialType": str(row[7]).strip() if pd.notna(row[7]) else "", - "targetWH": str(row[8]).strip() if pd.notna(row[8]) else "", - } - if item["materialName"]: - items.append(item) - - # 三号手套箱人工堆栈(26-40行, 3-7列) - for _, row in df.iloc[25:40, 2:7].iterrows(): - item = { - "sourceWHName": "三号手套箱人工堆栈", - "posX": int(row[2]), - "posY": int(row[3]), - "posZ": int(row[4]), - "materialType": str(row[5]).strip() if pd.notna(row[5]) else "", - "materialId": str(row[6]).strip() if pd.notna(row[6]) else "", - "quantity": 1 # 默认数量1 - } - if item["materialId"] or item["materialType"]: - items.append(item) - - response = self._post_lims("/api/lims/order/auto-feeding4to3", items) - self._wait_for_response_orders(response, "auto_feeding4to3_from_xlsx") - return response + def auto_batch_outbound_from_xlsx(self, xlsx_path: str) -> Dict[str, Any]: """ @@ -523,7 +457,7 @@ class BioyondCellWorkstation(BioyondWorkstation): }) response = self._post_lims("/api/lims/storage/auto-batch-out-bound", items) - self._wait_for_response_orders(response, "auto_batch_outbound_from_xlsx") + self.wait_for_response_orders(response, "auto_batch_outbound_from_xlsx") return response # 2.14 新建实验 @@ -1151,7 +1085,7 @@ if __name__ == "__main__": # result = ws.create_and_inbound_materials() # 继续后续流程 - # logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 + logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 # # # 使用正斜杠或 Path 对象来指定文件路径 # excel_path = Path("unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025092701.xlsx") # logger.info(ws.create_orders(excel_path)) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx index abaf145e68e8910ddf877637d20ff10748c489ad..2467ab05e52c39e2aa0b3cabc49d44b7c5754247 100644 GIT binary patch delta 179 zcmdnLma%m${ieK|cNxxQoLpea%Is!nFWZOlNW|sGX_k)8*0ILbh22OEu-q> TkT5&Ob(1HDsj+JYMwV* delta 194 zcmZo&%ea3nFU`LzMxW^LoSiU^x0UknLnOq;A3YQrS$GC4ienz3v0 l;!q35|C8^8+A_s^O?LDWo~#$9#rR@!WSAP8UI+sa006B+KTZGu diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 577833f..6acc8f6 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -8,7 +8,8 @@ import os # BioyondCellWorkstation 默认配置(包含所有必需参数) API_CONFIG = { # API 连接配置 - "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.10.169:44388"), + # "api_host": os.getenv("BIOYOND_API_HOST", "http://172.21.32.65:44389"),#实机 + "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.10.169:44388"),# 仿真机 "api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"), "timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")), @@ -16,7 +17,7 @@ API_CONFIG = { "report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"), # HTTP 服务配置 - "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.21.33.174"), # HTTP服务监听地址,监听计算机飞连ip地址 + "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.21.33.30"), # HTTP服务监听地址,监听计算机飞连ip地址 "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), "debug_mode": False,# 调试模式 } diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index d8bb597..f2571af 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -906,10 +906,11 @@ class CoinCellAssemblyWorkstation(WorkstationBase): return self.success - def func_allpack_cmd(self, elec_num, elec_use_num, elec_vol:int=50, assembly_type:int=7, assembly_pressure:int=4200, file_path: str="D:\\coin_cell_data") -> bool: + def func_allpack_cmd(self, elec_num, elec_use_num, elec_vol:int=50, assembly_type:int=7, assembly_pressure:int=4200, file_path: str="C:\\Users\\67484\\Desktop") -> bool: elec_num, elec_use_num, elec_vol, assembly_type, assembly_pressure = int(elec_num), int(elec_use_num), int(elec_vol), int(assembly_type), int(assembly_pressure) summary_csv_file = os.path.join(file_path, "duandian.csv") # 如果断点文件存在,先读取之前的进度 + if os.path.exists(summary_csv_file): read_status_flag = True with open(summary_csv_file, 'r', newline='', encoding='utf-8') as csvfile: diff --git a/unilabos/registry/devices/bioyond_cell.yaml b/unilabos/registry/devices/bioyond_cell.yaml index a54e702..d6988b7 100644 --- a/unilabos/registry/devices/bioyond_cell.yaml +++ b/unilabos/registry/devices/bioyond_cell.yaml @@ -137,7 +137,7 @@ bioyond_cell: WH4_x5_y1_z1_5_quantity: 0.0 WH4_x5_y2_z1_10_materialName: '' WH4_x5_y2_z1_10_quantity: 0.0 - xlsx_path: unilabos\devices\workstation\bioyond_studio\bioyond_cell\样品导入模板.xlsx + xlsx_path: unilabos\devices\workstation\bioyond_studio\bioyond_cell\material_template.xlsx handles: {} placeholder_keys: {} result: {} @@ -463,7 +463,7 @@ bioyond_cell: default: 0.0 type: number xlsx_path: - default: unilabos\devices\workstation\bioyond_studio\bioyond_cell\样品导入模板.xlsx + default: unilabos\devices\workstation\bioyond_studio\bioyond_cell\material_template.xlsx type: string required: [] type: object @@ -530,6 +530,42 @@ bioyond_cell: title: create_and_inbound_materials参数 type: object type: UniLabJsonCommand + auto-create_material: + feedback: {} + goal: {} + goal_default: + location_name_or_id: null + material_name: null + type_id: null + warehouse_name: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + location_name_or_id: + type: string + material_name: + type: string + type_id: + type: string + warehouse_name: + type: string + required: + - material_name + - type_id + - warehouse_name + type: object + result: {} + required: + - goal + title: create_material参数 + type: object + type: UniLabJsonCommand auto-create_materials: feedback: {} goal: {} @@ -580,6 +616,43 @@ bioyond_cell: title: create_orders参数 type: object type: UniLabJsonCommand + auto-create_sample: + feedback: {} + goal: {} + goal_default: + board_type: null + bottle_type: null + location_code: null + name: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + board_type: + type: string + bottle_type: + type: string + location_code: + type: string + name: + type: string + required: + - name + - board_type + - bottle_type + - location_code + type: object + result: {} + required: + - goal + title: create_sample参数 + type: object + type: UniLabJsonCommand auto-order_list_v2: feedback: {} goal: {} From 727d2c2595f06323a45dd65e30348c36f93beca8 Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Sat, 1 Nov 2025 10:39:08 +0800 Subject: [PATCH 042/104] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3YB=20warehouse?= =?UTF-8?q?=E6=8E=92=E5=88=97=E6=96=B9=E5=BC=8F=E5=92=8C=E7=89=A9=E6=96=99?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改warehouse_factory为YB_warehouse_factory - 调整warehouse排列方式:左上角为A01,竖着排ABCD,横着排01、02、03 - 修正config.py中的物料名称拼写错误(YB_fen_ye_20ml_Bottle, YB_pei_ye_xiao_Bottle) - 添加缺失的warehouse函数(bioyond_warehouse_2x2x1, bioyond_warehouse_3x5x1, bioyond_warehouse_20x1x1) - 更新decks.py中的warehouse位置映射 - 删除废弃的bottles.py和warehouses.py文件 --- .../workstation/bioyond_studio/config.py | 6 +- unilabos/registry/devices/laiyu_liquid.yaml | 11 +- unilabos/registry/devices/liquid_handler.yaml | 33 +-- unilabos/resources/bioyond/YB_warehouses.py | 71 ++++- unilabos/resources/bioyond/bottles.py | 255 ------------------ unilabos/resources/bioyond/decks.py | 38 ++- unilabos/resources/bioyond/warehouses.py | 161 ----------- unilabos/resources/warehouse.py | 5 +- 8 files changed, 97 insertions(+), 483 deletions(-) delete mode 100644 unilabos/resources/bioyond/bottles.py delete mode 100644 unilabos/resources/bioyond/warehouses.py diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 2eb3dbb..99cbdda 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -16,7 +16,7 @@ API_CONFIG = { "report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"), # HTTP 服务配置 - "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.21.32.210"), # HTTP服务监听地址,监听计算机飞连ip地址 + "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.21.32.83"), # HTTP服务监听地址,监听计算机飞连ip地址 "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), "debug_mode": False,# 调试模式 } @@ -159,9 +159,9 @@ MATERIAL_TYPE_MAPPINGS = { "5ml分液瓶板": ("YB_6x5ml_DispensingVialCarrier", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"), "5ml分液瓶": ("YB_fen_ye_5ml_Bottle", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"), "20ml分液瓶板": ("YB_6x20ml_DispensingVialCarrier", "3a192fa4-47db-3449-162a-eaf8aba57e27"), - "20ml分液瓶": ("YB_fen_ye_20ml_Bottler", "3a192c2b-19e8-f0a3-035e-041ca8ca1035"), + "20ml分液瓶": ("YB_fen_ye_20ml_Bottle", "3a192c2b-19e8-f0a3-035e-041ca8ca1035"), "配液瓶(小)板": ("YB_6x_SmallSolutionBottleCarrier", "3a190c8b-3284-af78-d29f-9a69463ad047"), - "配液瓶(小)": ("YB_pei_ye_xiao_Bottler", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"), + "配液瓶(小)": ("YB_pei_ye_xiao_Bottle", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"), "配液瓶(大)板": ("YB_4x_LargeSolutionBottleCarrier", "53e50377-32dc-4781-b3c0-5ce45bc7dc27"), "配液瓶(大)": ("YB_pei_ye_da_Bottle", "19c52ad1-51c5-494f-8854-576f4ca9c6ca"), "适配器块": ("YB_AdapterBlock", "efc3bb32-d504-4890-91c0-b64ed3ac80cf"), diff --git a/unilabos/registry/devices/laiyu_liquid.yaml b/unilabos/registry/devices/laiyu_liquid.yaml index 98201a7..64c0c18 100644 --- a/unilabos/registry/devices/laiyu_liquid.yaml +++ b/unilabos/registry/devices/laiyu_liquid.yaml @@ -1361,8 +1361,7 @@ laiyu_liquid: mix_liquid_height: 0.0 mix_rate: 0 mix_stage: '' - mix_times: - - 0 + mix_times: 0 mix_vol: 0 none_keys: - '' @@ -1492,11 +1491,9 @@ laiyu_liquid: mix_stage: type: string mix_times: - items: - maximum: 2147483647 - minimum: -2147483648 - type: integer - type: array + maximum: 2147483647 + minimum: -2147483648 + type: integer mix_vol: maximum: 2147483647 minimum: -2147483648 diff --git a/unilabos/registry/devices/liquid_handler.yaml b/unilabos/registry/devices/liquid_handler.yaml index b21ccd7..99c9233 100644 --- a/unilabos/registry/devices/liquid_handler.yaml +++ b/unilabos/registry/devices/liquid_handler.yaml @@ -3994,8 +3994,7 @@ liquid_handler: mix_liquid_height: 0.0 mix_rate: 0 mix_stage: '' - mix_times: - - 0 + mix_times: 0 mix_vol: 0 none_keys: - '' @@ -4151,11 +4150,9 @@ liquid_handler: mix_stage: type: string mix_times: - items: - maximum: 2147483647 - minimum: -2147483648 - type: integer - type: array + maximum: 2147483647 + minimum: -2147483648 + type: integer mix_vol: maximum: 2147483647 minimum: -2147483648 @@ -5015,8 +5012,7 @@ liquid_handler.biomek: mix_liquid_height: 0.0 mix_rate: 0 mix_stage: '' - mix_times: - - 0 + mix_times: 0 mix_vol: 0 none_keys: - '' @@ -5159,11 +5155,9 @@ liquid_handler.biomek: mix_stage: type: string mix_times: - items: - maximum: 2147483647 - minimum: -2147483648 - type: integer - type: array + maximum: 2147483647 + minimum: -2147483648 + type: integer mix_vol: maximum: 2147483647 minimum: -2147483648 @@ -7807,8 +7801,7 @@ liquid_handler.prcxi: mix_liquid_height: 0.0 mix_rate: 0 mix_stage: '' - mix_times: - - 0 + mix_times: 0 mix_vol: 0 none_keys: - '' @@ -7937,11 +7930,9 @@ liquid_handler.prcxi: mix_stage: type: string mix_times: - items: - maximum: 2147483647 - minimum: -2147483648 - type: integer - type: array + maximum: 2147483647 + minimum: -2147483648 + type: integer mix_vol: maximum: 2147483647 minimum: -2147483648 diff --git a/unilabos/resources/bioyond/YB_warehouses.py b/unilabos/resources/bioyond/YB_warehouses.py index c546759..39f79ea 100644 --- a/unilabos/resources/bioyond/YB_warehouses.py +++ b/unilabos/resources/bioyond/YB_warehouses.py @@ -1,9 +1,9 @@ -from unilabos.resources.warehouse import WareHouse, warehouse_factory +from unilabos.resources.warehouse import WareHouse, YB_warehouse_factory def bioyond_warehouse_1x4x4(name: str) -> WareHouse: """创建BioYond 4x1x4仓库""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=1, num_items_y=4, @@ -20,7 +20,7 @@ def bioyond_warehouse_1x4x4(name: str) -> WareHouse: def bioyond_warehouse_1x4x2(name: str) -> WareHouse: """创建BioYond 4x1x2仓库""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=1, num_items_y=4, @@ -37,7 +37,7 @@ def bioyond_warehouse_1x4x2(name: str) -> WareHouse: # 定义benyond的堆栈 def bioyond_warehouse_1x2x2(name: str) -> WareHouse: """创建BioYond 4x1x4仓库""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=2, num_items_y=2, @@ -50,9 +50,26 @@ def bioyond_warehouse_1x2x2(name: str) -> WareHouse: item_dz=120.0, category="YB_warehouse", ) + +def bioyond_warehouse_2x2x1(name: str) -> WareHouse: + """创建BioYond 2x2x1仓库(自动堆栈)""" + return YB_warehouse_factory( + name=name, + num_items_x=2, + num_items_y=2, + num_items_z=1, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="YB_warehouse", + ) + def bioyond_warehouse_10x1x1(name: str) -> WareHouse: """创建BioYond 4x1x4仓库""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=10, num_items_y=1, @@ -67,7 +84,7 @@ def bioyond_warehouse_10x1x1(name: str) -> WareHouse: ) def bioyond_warehouse_1x3x3(name: str) -> WareHouse: """创建BioYond 4x1x4仓库""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=1, num_items_y=3, @@ -82,7 +99,7 @@ def bioyond_warehouse_1x3x3(name: str) -> WareHouse: ) def bioyond_warehouse_2x1x3(name: str) -> WareHouse: """创建BioYond 4x1x4仓库""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=2, num_items_y=1, @@ -98,7 +115,7 @@ def bioyond_warehouse_2x1x3(name: str) -> WareHouse: def bioyond_warehouse_3x3x1(name: str) -> WareHouse: """创建BioYond 4x1x4仓库""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=3, num_items_y=3, @@ -113,7 +130,7 @@ def bioyond_warehouse_3x3x1(name: str) -> WareHouse: ) def bioyond_warehouse_5x1x1(name: str) -> WareHouse: """创建BioYond 4x1x4仓库""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=5, num_items_y=1, @@ -128,7 +145,7 @@ def bioyond_warehouse_5x1x1(name: str) -> WareHouse: ) def bioyond_warehouse_3x3x1_2(name: str) -> WareHouse: """创建BioYond 4x1x4仓库""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=3, num_items_y=3, @@ -144,7 +161,7 @@ def bioyond_warehouse_3x3x1_2(name: str) -> WareHouse: def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse: """创建BioYond开关盖加液模块台面""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=2, num_items_y=5, @@ -157,4 +174,36 @@ def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse: item_dz=120.0, category="warehouse", removed_positions=None + ) + +def bioyond_warehouse_3x5x1(name: str) -> WareHouse: + """创建BioYond 3x5x1仓库(手动堆栈)""" + return YB_warehouse_factory( + name=name, + num_items_x=3, + num_items_y=5, + num_items_z=1, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) + +def bioyond_warehouse_20x1x1(name: str) -> WareHouse: + """创建BioYond 20x1x1仓库(粉末加样头堆栈)""" + return YB_warehouse_factory( + name=name, + num_items_x=20, + num_items_y=1, + num_items_z=1, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", ) \ No newline at end of file diff --git a/unilabos/resources/bioyond/bottles.py b/unilabos/resources/bioyond/bottles.py deleted file mode 100644 index 23b9523..0000000 --- a/unilabos/resources/bioyond/bottles.py +++ /dev/null @@ -1,255 +0,0 @@ -from unilabos.resources.itemized_carrier import Bottle, BottleCarrier -# 工厂函数 - - -def YB_Solid_Stock( - name: str, - diameter: float = 20.0, - height: float = 100.0, - max_volume: float = 30000.0, # 30mL - barcode: str = None, -) -> Bottle: - """创建粉末瓶""" - return Bottle( - name=name, - diameter=diameter,# 未知 - height=height, - max_volume=max_volume, - barcode=barcode, - model="Solid_Stock", - ) - - -def YB_Solid_Vial( - name: str, - diameter: float = 25.0, - height: float = 60.0, - max_volume: float = 30000.0, # 30mL - barcode: str = None, -) -> Bottle: - """创建粉末瓶""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="Solid_Vial", - ) - - -def YB_Liquid_Vial( - name: str, - diameter: float = 25.0, - height: float = 60.0, - max_volume: float = 30000.0, # 30mL - barcode: str = None, -) -> Bottle: - """创建滴定液瓶""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="Liquid_Vial", - ) - - -def YB_Solution_Beaker( - name: str, - diameter: float = 60.0, - height: float = 70.0, - max_volume: float = 200000.0, # 200mL - barcode: str = None, -) -> Bottle: - """创建溶液烧杯""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="Solution_Beaker", - ) - - -def YB_Reagent_Bottle( - name: str, - diameter: float = 70.0, - height: float = 120.0, - max_volume: float = 500000.0, # 500mL - barcode: str = None, -) -> Bottle: - """创建试剂瓶""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="Reagent_Bottle", - ) - - -def YB_100ml_Liquid_Bottle( - name: str, - diameter: float = 50.0, - height: float = 80.0, - max_volume: float = 100000.0, # 100mL - barcode: str = None, -) -> Bottle: - """创建100ml液体瓶""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="100ml_Liquid_Bottle", - ) - - -def YB_Liquid_Bottle( - name: str, - diameter: float = 40.0, - height: float = 70.0, - max_volume: float = 50000.0, # 50mL - barcode: str = None, -) -> Bottle: - """创建液体瓶""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="Liquid_Bottle", - ) - - -def YB_High_Viscosity_Liquid_Bottle( - name: str, - diameter: float = 45.0, - height: float = 75.0, - max_volume: float = 60000.0, # 60mL - barcode: str = None, -) -> Bottle: - """创建高粘液瓶""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="High_Viscosity_Liquid_Bottle", - ) - - -def YB_Large_Dispense_Head( - name: str, - diameter: float = 35.0, - height: float = 90.0, - max_volume: float = 50000.0, # 50mL - barcode: str = None, -) -> Bottle: - """创建加样头(大)""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="Large_Dispense_Head", - ) - - -def YB_5ml_Dispensing_Vial( - name: str, - diameter: float = 15.0, - height: float = 45.0, - max_volume: float = 5000.0, # 5mL - barcode: str = None, -) -> Bottle: - """创建5ml分液瓶""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="5ml_Dispensing_Vial", - ) - - -def YB_20ml_Dispensing_Vial( - name: str, - diameter: float = 20.0, - height: float = 65.0, - max_volume: float = 20000.0, # 20mL - barcode: str = None, -) -> Bottle: - """创建20ml分液瓶""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="20ml_Dispensing_Vial", - ) - - -def YB_Small_Solution_Bottle( - name: str, - diameter: float = 35.0, - height: float = 60.0, - max_volume: float = 40000.0, # 40mL - barcode: str = None, -) -> Bottle: - """创建配液瓶(小)""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="Small_Solution_Bottle", - ) - - -def YB_Large_Solution_Bottle( - name: str, - diameter: float = 55.0, - height: float = 90.0, - max_volume: float = 150000.0, # 150mL - barcode: str = None, -) -> Bottle: - """创建配液瓶(大)""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="Large_Solution_Bottle", - ) - - -def YB_Pipette_Tip( - name: str, - diameter: float = 10.0, - height: float = 50.0, - max_volume: float = 1000.0, # 1mL - barcode: str = None, -) -> Bottle: - """创建枪头""" - return Bottle( - name=name, - diameter=diameter, - height=height, - max_volume=max_volume, - barcode=barcode, - model="Pipette_Tip", - ) - diff --git a/unilabos/resources/bioyond/decks.py b/unilabos/resources/bioyond/decks.py index fa242c3..f15e065 100644 --- a/unilabos/resources/bioyond/decks.py +++ b/unilabos/resources/bioyond/decks.py @@ -1,7 +1,7 @@ from os import name from pylabrobot.resources import Deck, Coordinate, Rotation -from unilabos.resources.bioyond.warehouses import bioyond_warehouse_1x4x4, bioyond_warehouse_1x4x2, bioyond_warehouse_liquid_and_lid_handling, bioyond_warehouse_1x2x2, bioyond_warehouse_1x3x3, bioyond_warehouse_10x1x1, bioyond_warehouse_3x3x1, bioyond_warehouse_3x3x1_2, bioyond_warehouse_5x1x1 +from unilabos.resources.bioyond.YB_warehouses import bioyond_warehouse_1x4x4, bioyond_warehouse_1x4x2, bioyond_warehouse_liquid_and_lid_handling, bioyond_warehouse_1x2x2, bioyond_warehouse_1x3x3, bioyond_warehouse_10x1x1, bioyond_warehouse_3x3x1, bioyond_warehouse_3x3x1_2, bioyond_warehouse_5x1x1, bioyond_warehouse_20x1x1, bioyond_warehouse_2x2x1, bioyond_warehouse_3x5x1 class BIOYOND_PolymerReactionStation_Deck(Deck): @@ -85,31 +85,23 @@ class BIOYOND_YB_Deck(Deck): def setup(self) -> None: # 添加仓库 self.warehouses = { - "321窗口": bioyond_warehouse_1x2x2("321窗口"), - "43窗口": bioyond_warehouse_1x2x2("43窗口"), - "手动传递窗左": bioyond_warehouse_1x3x3("手动传递窗左"), - "手动传递窗右": bioyond_warehouse_1x3x3("手动传递窗右"), - "加样头堆栈左": bioyond_warehouse_10x1x1("加样头堆栈左"), - "加样头堆栈右": bioyond_warehouse_10x1x1("加样头堆栈右"), - - "15ml配液堆栈左": bioyond_warehouse_3x3x1("15ml配液堆栈左"), - "母液加样右": bioyond_warehouse_3x3x1_2("母液加样右"), - "大瓶母液堆栈左": bioyond_warehouse_5x1x1("大瓶母液堆栈左"), - "大瓶母液堆栈右": bioyond_warehouse_5x1x1("大瓶母液堆栈右"), + "自动堆栈-左": bioyond_warehouse_2x2x1("自动堆栈-左"), + "自动堆栈-右": bioyond_warehouse_2x2x1("自动堆栈-右"), + "手动堆栈-左": bioyond_warehouse_3x5x1("手动堆栈-左"), + "手动堆栈-右": bioyond_warehouse_3x5x1("手动堆栈-右"), + "粉末加样头堆栈": bioyond_warehouse_20x1x1("粉末加样头堆栈"), + "配液站内试剂仓库": bioyond_warehouse_3x3x1("配液站内试剂仓库"), + "试剂替换仓库": bioyond_warehouse_10x1x1("试剂替换仓库"), } # warehouse 的位置 self.warehouse_locations = { - "321窗口": Coordinate(-150.0, 158.0, 0.0), - "43窗口": Coordinate(4160.0, 158.0, 0.0), - "手动传递窗左": Coordinate(-150.0, 877.0, 0.0), - "手动传递窗右": Coordinate(4160.0, 877.0, 0.0), - "加样头堆栈左": Coordinate(385.0, 1300.0, 0.0), - "加样头堆栈右": Coordinate(2187.0, 1300.0, 0.0), - - "15ml配液堆栈左": Coordinate(749.0, 355.0, 0.0), - "母液加样右": Coordinate(2152.0, 333.0, 0.0), - "大瓶母液堆栈左": Coordinate(1164.0, 676.0, 0.0), - "大瓶母液堆栈右": Coordinate(2717.0, 676.0, 0.0), + "自动堆栈-左": Coordinate(-300.0, 158.0, 0.0), + "自动堆栈-右": Coordinate(4160.0, 158.0, 0.0), + "手动堆栈-左": Coordinate(-400.0, 877.0, 0.0), + "手动堆栈-右": Coordinate(4160.0, 877.0, 0.0), + "粉末加样头堆栈": Coordinate(385.0, 1300.0, 0.0), + "配液站内试剂仓库": Coordinate(1164.0, 676.0, 0.0), + "试剂替换仓库": Coordinate(2717.0, 676.0, 0.0), } for warehouse_name, warehouse in self.warehouses.items(): diff --git a/unilabos/resources/bioyond/warehouses.py b/unilabos/resources/bioyond/warehouses.py deleted file mode 100644 index 6eb4f26..0000000 --- a/unilabos/resources/bioyond/warehouses.py +++ /dev/null @@ -1,161 +0,0 @@ -from unilabos.resources.warehouse import WareHouse, warehouse_factory - - -def bioyond_warehouse_1x4x4(name: str) -> WareHouse: - """创建BioYond 4x1x4仓库""" - return warehouse_factory( - name=name, - num_items_x=1, - num_items_y=4, - num_items_z=4, - dx=10.0, - dy=10.0, - dz=10.0, - item_dx=137.0, - item_dy=96.0, - item_dz=120.0, - category="warehouse", - ) - - - -def bioyond_warehouse_1x4x2(name: str) -> WareHouse: - """创建BioYond 4x1x2仓库""" - return warehouse_factory( - name=name, - num_items_x=1, - num_items_y=4, - num_items_z=2, - dx=10.0, - dy=10.0, - dz=10.0, - item_dx=137.0, - item_dy=96.0, - item_dz=120.0, - category="warehouse", - removed_positions=None - ) - # 定义benyond的堆栈 -def bioyond_warehouse_1x2x2(name: str) -> WareHouse: - """创建BioYond 4x1x4仓库""" - return warehouse_factory( - name=name, - num_items_x=2, - num_items_y=2, - num_items_z=1, - dx=10.0, - dy=10.0, - dz=10.0, - item_dx=137.0, - item_dy=96.0, - item_dz=120.0, - category="YB_warehouse", - ) -def bioyond_warehouse_10x1x1(name: str) -> WareHouse: - """创建BioYond 4x1x4仓库""" - return warehouse_factory( - name=name, - num_items_x=10, - num_items_y=1, - num_items_z=1, - dx=10.0, - dy=10.0, - dz=10.0, - item_dx=137.0, - item_dy=96.0, - item_dz=120.0, - category="warehouse", - ) -def bioyond_warehouse_1x3x3(name: str) -> WareHouse: - """创建BioYond 4x1x4仓库""" - return warehouse_factory( - name=name, - num_items_x=1, - num_items_y=3, - num_items_z=3, - dx=10.0, - dy=10.0, - dz=10.0, - item_dx=137.0, - item_dy=96.0, - item_dz=120.0, - category="warehouse", - ) -def bioyond_warehouse_2x1x3(name: str) -> WareHouse: - """创建BioYond 4x1x4仓库""" - return warehouse_factory( - name=name, - num_items_x=2, - num_items_y=1, - num_items_z=3, - dx=10.0, - dy=10.0, - dz=10.0, - item_dx=137.0, - item_dy=96.0, - item_dz=120.0, - category="warehouse", - ) - -def bioyond_warehouse_3x3x1(name: str) -> WareHouse: - """创建BioYond 4x1x4仓库""" - return warehouse_factory( - name=name, - num_items_x=3, - num_items_y=3, - num_items_z=1, - dx=10.0, - dy=10.0, - dz=10.0, - item_dx=137.0, - item_dy=96.0, - item_dz=120.0, - category="warehouse", - ) -def bioyond_warehouse_5x1x1(name: str) -> WareHouse: - """创建BioYond 4x1x4仓库""" - return warehouse_factory( - name=name, - num_items_x=5, - num_items_y=1, - num_items_z=1, - dx=10.0, - dy=10.0, - dz=10.0, - item_dx=137.0, - item_dy=96.0, - item_dz=120.0, - category="warehouse", - ) -def bioyond_warehouse_3x3x1_2(name: str) -> WareHouse: - """创建BioYond 4x1x4仓库""" - return warehouse_factory( - name=name, - num_items_x=3, - num_items_y=3, - num_items_z=1, - dx=12.0, - dy=12.0, - dz=12.0, - item_dx=137.0, - item_dy=96.0, - item_dz=120.0, - category="warehouse", - ) - -def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse: - """创建BioYond开关盖加液模块台面""" - return warehouse_factory( - name=name, - num_items_x=2, - num_items_y=5, - num_items_z=1, - dx=10.0, - dy=10.0, - dz=10.0, - item_dx=137.0, - item_dy=96.0, - item_dz=120.0, - category="warehouse", - removed_positions=None - ) \ No newline at end of file diff --git a/unilabos/resources/warehouse.py b/unilabos/resources/warehouse.py index c665b7f..2f613cf 100644 --- a/unilabos/resources/warehouse.py +++ b/unilabos/resources/warehouse.py @@ -8,7 +8,7 @@ from unilabos.resources.itemized_carrier import ItemizedCarrier, ResourcePLR LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -def warehouse_factory( +def YB_warehouse_factory( name: str, num_items_x: int = 1, num_items_y: int = 4, @@ -44,7 +44,8 @@ def warehouse_factory( name_prefix=name, ) len_x, len_y = (num_items_x, num_items_y) if num_items_z == 1 else (num_items_y, num_items_z) if num_items_x == 1 else (num_items_x, num_items_z) - keys = [f"{LETTERS[j]}{i + 1}" for i in range(len_x) for j in range(len_y)] + + keys = [f"{LETTERS[len_y-1-j]}{str(i+1).zfill(2)}" for j in range(len_y) for i in range(len_x)] sites = {i: site for i, site in zip(keys, _sites.values())} return WareHouse( From e561c818b8b747e73c2a9930c086fc45bdbe33d4 Mon Sep 17 00:00:00 2001 From: calvincao Date: Mon, 3 Nov 2025 14:31:50 +0800 Subject: [PATCH 043/104] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=A4=9A?= =?UTF-8?q?=E4=B8=AA=E6=96=B0=E4=BB=93=E5=BA=93=E9=85=8D=E7=BD=AE=E5=88=B0?= =?UTF-8?q?config.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增多个仓库配置,包括大分液瓶堆栈、小分液瓶堆栈、站内Tip头盒堆栈等 - 每个仓库配置包含UUID和站点UUID映射 --- .../workstation/bioyond_studio/config.py | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 39a2589..8a6fcf8 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -147,7 +147,89 @@ WAREHOUSE_MAPPING = { "C03": "3a1baa20-a7b1-cca7-781e-0522b729bf5d", "C04": "3a1baa20-a7b1-7c98-5fd9-5855355ae4b3" } + }, + "大分液瓶堆栈": { + "uuid": "", + "site_uuids": { + "A01": "3a19da3d-4f3d-bcac-2932-7542041e10e0", + "A02": "3a19da3d-4f3d-4d75-38ac-fb58ad0687c3", + "A03": "3a19da3d-4f3d-b25e-f2b1-85342a5b7eae", + "B01": "3a19da3d-4f3d-fd3e-058a-2733a0925767", + "B02": "3a19da3d-4f3d-37bd-a944-c391ad56857f", + "B03": "3a19da3d-4f3d-e353-7862-c6d1d4bc667f", + "C01": "3a19da3d-4f3d-9519-5da7-76179c958e70", + "C02": "3a19da3d-4f3d-b586-d7ed-9ec244f6f937", + "C03": "3a19da3d-4f3d-5061-249b-35dfef732811" + } + }, + "小分液瓶堆栈": { + "uuid": "", + "site_uuids": { + "C03": "3a19da40-55bf-8943-d20d-a8b3ea0d16c0" + } + }, + "站内Tip头盒堆栈": { + "uuid": "", + "site_uuids": { + "A01": "3a19deab-d5cc-be1e-5c37-4e9e5a664388", + "A02": "3a19deab-d5cc-b394-8141-27cb3853e8ea", + "B01": "3a19deab-d5cc-4dca-596e-ca7414d5f501", + "B02": "3a19deab-d5cc-9bc0-442b-12d9d59aa62a", + "C01": "3a19deab-d5cc-2eaf-b6a4-f0d54e4f1246", + "C02": "3a19deab-d5cc-d9f4-25df-b8018c372bc7" + } + }, + "配液站内配液大板仓库(无需提前上料)": { + "uuid": "", + "site_uuids": { + "A01": "3a1a21dc-06af-3915-9cb9-80a9dc42f386" + } + }, + "配液站内配液小板仓库(无需以前入料)": { + "uuid": "", + "site_uuids": { + "A01": "3a1a21de-8e8b-7938-2d06-858b36c10e31" + } + }, + "移液站内大瓶板仓库(无需提前如料)": { + "uuid": "", + "site_uuids": { + "A01": "3a1a224c-c727-fa62-1f2b-0037a84b9fca" + } + }, + "移液站内小瓶板仓库(无需提前入料)": { + "uuid": "", + "site_uuids": { + "A01": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb" + } + }, + "适配器位仓库": { + "uuid": "", + "site_uuids": { + "A01": "3a1abd46-18fe-1f56-6ced-a1f7fe08e36c" + } + }, + "1号2号手套箱交接堆栈": { + "uuid": "", + "site_uuids": { + "A01": "3a1baa49-7f77-35aa-60b1-e55a45d065fa" + } + }, + "2号手套箱内部堆栈": { + "uuid": "", + "site_uuids": { + "A01": "3a1baa4b-393e-9f86-3921-7a18b0a8e371", + "A02": "3a1baa4b-393e-9425-928b-ee0f6f679d44", + "A03": "3a1baa4b-393e-0baf-632b-59dfdc931a3a", + "B01": "3a1baa4b-393e-f8aa-c8a9-956f3132f05c", + "B02": "3a1baa4b-393e-ef05-42f6-53f4c6e89d70", + "B03": "3a1baa4b-393e-c07b-a924-a9f0dfda9711", + "C01": "3a1baa4b-393e-4c2b-821a-16a7fe025c48", + "C02": "3a1baa4b-393e-2eaf-61a1-9063c832d5a2", + "C03": "3a1baa4b-393e-034e-8e28-8626d934a85f" + } } + } # 物料类型配置 From f52fbd650e36bda0f69757dfa54d5d8445ed7b4d Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Mon, 3 Nov 2025 16:50:59 +0800 Subject: [PATCH 044/104] Update bioyond_cell_workstation.py --- .../bioyond_cell/bioyond_cell_workstation.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index 45a946f..deab824 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -19,7 +19,7 @@ from unilabos.devices.workstation.bioyond_studio.config import ( from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService from unilabos.utils.log import logger from unilabos.registry.registry import lab_registry - +from unilabos.resources.bioyond.decks import YB_Deck, BIOYOND_YB_Deck def _iso_local_now_ms() -> str: # 文档要求:到毫秒 + Z,例如 2025-08-15T05:43:22.814Z @@ -39,7 +39,7 @@ class BioyondCellWorkstation(BioyondWorkstation): def __init__( self, bioyond_config: Optional[Dict[str, Any]] = None, - station_resource: Optional[Dict[str, Any]] = None, + deck: Optional[Dict[str, Any]] = None, *args, **kwargs, ): @@ -52,13 +52,13 @@ class BioyondCellWorkstation(BioyondWorkstation): # "material_type_mappings": MATERIAL_TYPE_MAPPINGS # "warehouse_mapping": WAREHOUSE_MAPPING - + self.deck = BIOYOND_YB_Deck() + self.deck.setup() print(self.bioyond_config) self.debug_mode = self.bioyond_config["debug_mode"] self.http_service_started = self.debug_mode - deck = kwargs.pop("deck", None) self.device_id = kwargs.pop("device_id", "bioyond_cell_workstation") - super().__init__(bioyond_config=self.bioyond_config, deck=deck, station_resource=station_resource, *args, **kwargs) + super().__init__(bioyond_config=self.bioyond_config, deck=self.deck) self.update_push_ip() #直接修改奔耀端的报送ip地址 logger.info("已更新奔耀端推送 IP 地址") @@ -1074,7 +1074,7 @@ class BioyondCellWorkstation(BioyondWorkstation): if __name__ == "__main__": lab_registry.setup() ws = BioyondCellWorkstation() - ws.create_sample(name="test", board_type="配液瓶(小)板", bottle_type="配液瓶(小)", location_code="B01") + # ws.create_sample(name="test", board_type="配液瓶(小)板", bottle_type="配液瓶(小)", location_code="B01") # logger.info(ws.scheduler_stop()) # logger.info(ws.scheduler_start()) @@ -1085,7 +1085,7 @@ if __name__ == "__main__": # result = ws.create_and_inbound_materials() # 继续后续流程 - logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 + # logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 # # # 使用正斜杠或 Path 对象来指定文件路径 # excel_path = Path("unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025092701.xlsx") # logger.info(ws.create_orders(excel_path)) From 11f4f44bf91a03355f52021ba755dd9cdeebccc6 Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Mon, 3 Nov 2025 16:51:28 +0800 Subject: [PATCH 045/104] Update coin_cell_assembly.py --- .../coin_cell_assembly/coin_cell_assembly.py | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index f2571af..d968e99 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -149,7 +149,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): """ 连接初始化 """ modbus_client = TCPClient(addr=address, port=port) - print("modbus_client", modbus_client) + logger.debug(f"创建 Modbus 客户端: {modbus_client}") _ensure_modbus_slave_kw_alias(modbus_client.client) if not debug_mode: modbus_client.client.connect() @@ -602,11 +602,11 @@ class CoinCellAssemblyWorkstation(WorkstationBase): try: # 尝试不同的字节序读取 code_little, read_err = self.client.use_node('REG_DATA_COIN_CELL_CODE').read(10, word_order=WorderOrder.LITTLE) - print(code_little) + # logger.debug(f"读取电池二维码原始数据: {code_little}") clean_code = code_little[-8:][::-1] return clean_code except Exception as e: - print(f"读取电池二维码失败: {e}") + logger.error(f"读取电池二维码失败: {e}") return "N/A" @@ -615,11 +615,11 @@ class CoinCellAssemblyWorkstation(WorkstationBase): try: # 尝试不同的字节序读取 code_little, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_CODE').read(10, word_order=WorderOrder.LITTLE) - print(code_little) + # logger.debug(f"读取电解液二维码原始数据: {code_little}") clean_code = code_little[-8:][::-1] return clean_code except Exception as e: - print(f"读取电解液二维码失败: {e}") + logger.error(f"读取电解液二维码失败: {e}") return "N/A" # ===================== 环境监控区 ====================== @@ -809,14 +809,14 @@ class CoinCellAssemblyWorkstation(WorkstationBase): data_coin_num = self.data_coin_num data_electrolyte_code = self.data_electrolyte_code data_coin_cell_code = self.data_coin_cell_code - print("data_open_circuit_voltage", data_open_circuit_voltage) - print("data_pole_weight", data_pole_weight) - print("data_assembly_time", data_assembly_time) - print("data_assembly_pressure", data_assembly_pressure) - print("data_electrolyte_volume", data_electrolyte_volume) - print("data_coin_num", data_coin_num) - print("data_electrolyte_code", data_electrolyte_code) - print("data_coin_cell_code", data_coin_cell_code) + logger.debug(f"data_open_circuit_voltage: {data_open_circuit_voltage}") + logger.debug(f"data_pole_weight: {data_pole_weight}") + logger.debug(f"data_assembly_time: {data_assembly_time}") + logger.debug(f"data_assembly_pressure: {data_assembly_pressure}") + logger.debug(f"data_electrolyte_volume: {data_electrolyte_volume}") + logger.debug(f"data_coin_num: {data_coin_num}") + logger.debug(f"data_electrolyte_code: {data_electrolyte_code}") + logger.debug(f"data_coin_cell_code: {data_coin_cell_code}") #接收完信息后,读取完毕标志位置True liaopan3 = self.deck.get_resource("\u7535\u6c60\u6599\u76d8") #把物料解绑后放到另一盘上 From b5b235896706b0e5661fe89920e55c49222722ac Mon Sep 17 00:00:00 2001 From: calvincao Date: Mon, 3 Nov 2025 18:20:50 +0800 Subject: [PATCH 046/104] =?UTF-8?q?fix:=20=E6=9B=B4=E6=96=B0HTTP=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E9=85=8D=E7=BD=AE=E5=92=8C=E7=89=A9=E6=96=99=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改BIOYOND_HTTP_HOST的默认值为新的IP地址172.21.32.91 - 调整物料类型映射中“加样头(大)”的UUID顺序,并注释掉“加样头(大)板”配置 --- unilabos/devices/workstation/bioyond_studio/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 8a6fcf8..740af61 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -17,7 +17,7 @@ API_CONFIG = { "report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"), # HTTP 服务配置 - "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.21.32.83"), # HTTP服务监听地址,监听计算机飞连ip地址 + "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.21.32.91"), # HTTP服务监听地址,监听计算机飞连ip地址 "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), "debug_mode": False,# 调试模式 } @@ -237,8 +237,8 @@ MATERIAL_TYPE_MAPPINGS = { "100ml液体": ("YB_1Bottle100mlCarrier", "d37166b3-ecaa-481e-bd84-3032b795ba07"), "液": ("YB_1BottleCarrier", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), "高粘液": ("YB_1GaoNianYeBottleCarrier", "abe8df30-563d-43d2-85e0-cabec59ddc16"), - "加样头(大)": ("YB_jia_yang_tou_da", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), - "加样头(大)板": ("YB_jia_yang_tou_da_1X1_carrier", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"), + "加样头(大)": ("YB_jia_yang_tou_da_1X1_carrier", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + # "加样头(大)板": ("YB_jia_yang_tou_da_1X1_carrier", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"), "5ml分液瓶板": ("YB_6x5ml_DispensingVialCarrier", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"), "5ml分液瓶": ("YB_fen_ye_5ml_Bottle", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"), "20ml分液瓶板": ("YB_6x20ml_DispensingVialCarrier", "3a192fa4-47db-3449-162a-eaf8aba57e27"), From 4485907df83a4ac39b08a2b64882059564ee9448 Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Mon, 3 Nov 2025 18:46:50 +0800 Subject: [PATCH 047/104] 1103byxinyu --- new_cellconfig3c.json | 54 +- .../bioyond_cell/bioyond_cell_workstation.py | 31 +- .../bioyond_cell/bioyond_workstation copy.py | 715 ------------------ .../workstation/bioyond_studio/config.py | 7 +- .../workstation/bioyond_studio/station.py | 5 +- unilabos/registry/devices/bioyond_cell.yaml | 39 +- unilabos/registry/resources/bioyond/deck.yaml | 6 +- unilabos/resources/bioyond/decks.py | 11 +- 8 files changed, 81 insertions(+), 787 deletions(-) delete mode 100644 unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_workstation copy.py diff --git a/new_cellconfig3c.json b/new_cellconfig3c.json index 3619085..446d235 100644 --- a/new_cellconfig3c.json +++ b/new_cellconfig3c.json @@ -3,34 +3,64 @@ { "id": "bioyond_cell_workstation", "name": "配液分液工站", - "children": [ - ], "parent": null, + "children": [ + "YB_Bioyond_Deck" + ], "type": "device", "class": "bioyond_cell", "config": { - "protocol_type": [], - "station_resource": {} + "deck": { + "data": { + "_resource_child_name": "YB_Bioyond_Deck", + "_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_YB_Deck" + } + }, + "protocol_type": [] }, "data": {} - }, - { + }, + { + "id": "YB_Bioyond_Deck", + "name": "YB_Bioyond_Deck", + "children": [], + "parent": "bioyond_cell_workstation", + "type": "deck", + "class": "BIOYOND_YB_Deck", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "BIOYOND_YB_Deck", + "setup": true, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + } + }, + "data": {} + }, + { "id": "BatteryStation", "name": "扣电工作站", "children": [ - "coin_cell_deck" + "coin_cell_deck" ], "parent": null, "type": "device", "class": "coincellassemblyworkstation_device", "position": { - "x": 600, - "y": 400, - "z": 0 + "x": 600, + "y": 400, + "z": 0 }, "config": { - "debug_mode": false, - "protocol_type": [] + "debug_mode": false, + "protocol_type": [] } } ], diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index deab824..ee82579 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -11,7 +11,6 @@ import re import threading import json from urllib3 import response -from unilabos.devices.workstation.workstation_base import WorkstationBase from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation, BioyondResourceSynchronizer from unilabos.devices.workstation.bioyond_studio.config import ( API_CONFIG, MATERIAL_TYPE_MAPPINGS, WAREHOUSE_MAPPING, SOLID_LIQUID_MAPPINGS @@ -19,7 +18,6 @@ from unilabos.devices.workstation.bioyond_studio.config import ( from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService from unilabos.utils.log import logger from unilabos.registry.registry import lab_registry -from unilabos.resources.bioyond.decks import YB_Deck, BIOYOND_YB_Deck def _iso_local_now_ms() -> str: # 文档要求:到毫秒 + Z,例如 2025-08-15T05:43:22.814Z @@ -36,29 +34,25 @@ class BioyondCellWorkstation(BioyondWorkstation): 查询实验(2.5/2.6) → 3-2-1 转运(2.32) → 样品/废料取出(2.28) """ - def __init__( - self, - bioyond_config: Optional[Dict[str, Any]] = None, - deck: Optional[Dict[str, Any]] = None, - *args, **kwargs, - ): + def __init__(self, config: dict = None, deck=None, protocol_type=None, **kwargs): # 使用统一配置,支持自定义覆盖, 从 config.py 加载完整配置 - self.bioyond_config = bioyond_config or { + self.bioyond_config ={ **API_CONFIG, "material_type_mappings": MATERIAL_TYPE_MAPPINGS, - "warehouse_mapping": WAREHOUSE_MAPPING + "warehouse_mapping": WAREHOUSE_MAPPING, + "debug_mode": False } # "material_type_mappings": MATERIAL_TYPE_MAPPINGS # "warehouse_mapping": WAREHOUSE_MAPPING - self.deck = BIOYOND_YB_Deck() - self.deck.setup() - print(self.bioyond_config) + if deck is None and config: + deck = config.get('deck') + # print(self.bioyond_config) self.debug_mode = self.bioyond_config["debug_mode"] self.http_service_started = self.debug_mode - self.device_id = kwargs.pop("device_id", "bioyond_cell_workstation") - super().__init__(bioyond_config=self.bioyond_config, deck=self.deck) + self._device_id = "bioyond_cell_workstation" # 默认值,后续会从_ros_node获取 + super().__init__(bioyond_config=config, deck=deck) self.update_push_ip() #直接修改奔耀端的报送ip地址 logger.info("已更新奔耀端推送 IP 地址") @@ -72,6 +66,13 @@ class BioyondCellWorkstation(BioyondWorkstation): self.last_order_code = None logger.info(f"Bioyond工作站初始化完成 (debug_mode={self.debug_mode})") + @property + def device_id(self): + """获取设备ID,优先从_ros_node获取,否则返回默认值""" + if hasattr(self, '_ros_node') and self._ros_node is not None: + return getattr(self._ros_node, 'device_id', self._device_id) + return self._device_id + def _start_http_service(self): """启动 HTTP 服务""" host = self.bioyond_config.get("HTTP_host", "") diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_workstation copy.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_workstation copy.py deleted file mode 100644 index 864a9dd..0000000 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_workstation copy.py +++ /dev/null @@ -1,715 +0,0 @@ -# -*- coding: utf-8 -*- -from typing import Dict, Any, List, Optional -from datetime import datetime, timezone -import requests -from pathlib import Path -import pandas as pd -import time -from datetime import datetime, timezone, timedelta -import re -import threading -from unilabos.devices.workstation.workstation_base import WorkstationBase -from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService -from unilabos.utils.log import logger -from pylabrobot.resources.deck import Deck - - -def _iso_utc_now_ms() -> str: - # 文档要求:到毫秒 + Z,例如 2025-08-15T05:43:22.814Z - dt = datetime.now(timezone.utc) - return dt.strftime("%Y-%m-%dT%H:%M:%S.") + f"{int(dt.microsecond/1000):03d}Z" - - -class BioyondWorkstation(WorkstationBase): - """ - 集成 Bioyond LIMS 的工作站示例, - 覆盖:入库(2.17/2.18) → 新建实验(2.14) → 启动调度(2.7) → - 运行中推送:物料变更(2.24)、步骤完成(2.21)、订单完成(2.23) → - 查询实验(2.5/2.6) → 3-2-1 转运(2.32) → 样品/废料取出(2.28) - """ - - def __init__( - self, - bioyond_config: Optional[Dict[str, Any]] = None, - station_resource: Optional[Dict[str, Any]] = None, - debug_mode: bool = False, # 增加调试模式开关 - *args, **kwargs, - ): - self.bioyond_config = bioyond_config or { - "base_url": "http://192.168.1.200:44386", - "api_key": "8A819E5C", - "timeout": 30, - "report_token": "CHANGE_ME_TOKEN" - } - - self.http_service_started = False - self.debug_mode = debug_mode - super().__init__(deck=Deck, station_resource=station_resource, *args, **kwargs) - logger.info(f"Bioyond工作站初始化完成 (debug_mode={self.debug_mode})") - - # 实例化并在后台线程启动 HTTP 报送服务 - self.order_status = {} - try: - t = threading.Thread(target=self._start_http_service_bg, daemon=True, name="unilab_http") - t.start() - - except Exception as e: - logger.error(f"unilab-server后台启动报送服务失败: {e}") - - @property - def device_id(self) -> str: - try: - return getattr(self, "_ros_node").device_id # 兼容 ROS 场景 - except Exception: - return "bioyond_workstation" - - def _start_http_service_bg(self, host: str = "192.168.1.104", port: int = 8080) -> None: - logger.info("进入 _start_http_service_bg 函数") - try: - self.service = WorkstationHTTPService(self, host=host, port=port) - logger.info("WorkstationHTTPService 实例化完成") - self.service.start() - self.http_service_started = True - logger.info(f"unilab_HTTP 服务成功启动: {host}:{port}") - - #一直挂着,直到进程退出 - while True: - time.sleep(1) - - except Exception as e: - self.http_service_started = False - logger.error(f"启动unilab_HTTP服务失败: {e}", exc_info=True) - - # -------------------- 基础HTTP封装 -------------------- - def _url(self, path: str) -> str: - return f"{self.bioyond_config['base_url'].rstrip('/')}/{path.lstrip('/')}" - - def _post_lims(self, path: str, data: Optional[Any] = None) -> Dict[str, Any]: - """LIMS API:大多数接口用 {apiKey/requestTime,data} 包装""" - payload = { - "apiKey": self.bioyond_config["api_key"], - "requestTime": _iso_utc_now_ms() - } - if data is not None: - payload["data"] = data - - if self.debug_mode: - # 模拟返回,不发真实请求 - logger.info(f"[DEBUG] POST {path} with payload={payload}") - return {"debug": True, "url": self._url(path), "payload": payload, "status": "ok"} - - try: - r = requests.post( - self._url(path), - json=payload, - timeout=self.bioyond_config.get("timeout", 30), - headers={"Content-Type": "application/json"} - ) - r.raise_for_status() - return r.json() - except Exception as e: - logger.error(f"POST {path} 失败: {e}") - return {"error": str(e)} - - # --- 修正:_post_report / _post_report_raw 同样走 debug_mode --- - def _post_report(self, path: str, data: Dict[str, Any]) -> Dict[str, Any]: - payload = { - "token": self.bioyond_config.get("report_token", ""), - "request_time": _iso_utc_now_ms(), - "data": data - } - if self.debug_mode: - logger.info(f"[DEBUG] POST {path} with payload={payload}") - return {"debug": True, "url": self._url(path), "payload": payload, "status": "ok"} - try: - r = requests.post(self._url(path), json=payload, - timeout=self.bioyond_config.get("timeout", 30), - headers={"Content-Type": "application/json"}) - r.raise_for_status() - return r.json() - except Exception as e: - logger.error(f"POST {path} 失败: {e}") - return {"error": str(e)} - - def _post_report_raw(self, path: str, body: Dict[str, Any]) -> Dict[str, Any]: - if self.debug_mode: - logger.info(f"[DEBUG] POST {path} with body={body}") - return {"debug": True, "url": self._url(path), "payload": body, "status": "ok"} - try: - r = requests.post(self._url(path), json=body, - timeout=self.bioyond_config.get("timeout", 30), - headers={"Content-Type": "application/json"}) - r.raise_for_status() - return r.json() - except Exception as e: - logger.error(f"POST {path} 失败: {e}") - return {"error": str(e)} - - - # -------------------- 单点接口封装 -------------------- - # 2.17 入库物料(单个) - def storage_inbound(self, material_id: str, location_id: str) -> Dict[str, Any]: - return self._post_lims("/api/lims/storage/inbound", { - "materialId": material_id, - "locationId": location_id - }) - - # 2.18 批量入库(多个) - def storage_batch_inbound(self, items: List[Dict[str, str]]) -> Dict[str, Any]: - """ - items = [{"materialId": "...", "locationId": "..."}, ...] - """ - return self._post_lims("/api/lims/storage/batch-inbound", items) - - # 3.30 自动化上料(Excel -> JSON -> POST /api/lims/order/auto-feeding4to3) - def auto_feeding4to3_from_xlsx(self, xlsx_path: str) -> Dict[str, Any]: - """ - 根据固定模板解析 Excel: - - 四号手套箱加样头面 (2-13行, 3-7列) - - 四号手套箱原液瓶面 (15-23行, 3-9列) - - 三号手套箱人工堆栈 (26-40行, 3-7列) - """ - path = Path(xlsx_path) - if not path.exists(): - raise FileNotFoundError(f"未找到 Excel 文件:{path}") - - try: - df = pd.read_excel(path, sheet_name=0, header=None, engine="openpyxl") - except Exception as e: - raise RuntimeError(f"读取 Excel 失败:{e}") - - items: List[Dict[str, Any]] = [] - - # 四号手套箱 - 加样头面(2-13行, 3-7列) - for _, row in df.iloc[1:13, 2:7].iterrows(): - item = { - "sourceWHName": "四号手套箱堆栈", - "posX": int(row[2]), - "posY": int(row[3]), - "posZ": int(row[4]), - "materialName": str(row[5]).strip() if pd.notna(row[5]) else "", - "quantity": float(row[6]) if pd.notna(row[6]) else 0.0, - } - if item["materialName"]: - items.append(item) - - # 四号手套箱 - 原液瓶面(15-23行, 3-9列) - for _, row in df.iloc[14:23, 2:9].iterrows(): - item = { - "sourceWHName": "四号手套箱堆栈", - "posX": int(row[2]), - "posY": int(row[3]), - "posZ": int(row[4]), - "materialName": str(row[5]).strip() if pd.notna(row[5]) else "", - "quantity": float(row[6]) if pd.notna(row[6]) else 0.0, - "materialType": str(row[7]).strip() if pd.notna(row[7]) else "", - "targetWH": str(row[8]).strip() if pd.notna(row[8]) else "", - } - if item["materialName"]: - items.append(item) - - # 三号手套箱人工堆栈(26-40行, 3-7列) - for _, row in df.iloc[25:40, 2:7].iterrows(): - item = { - "sourceWHName": "三号手套箱人工堆栈", - "posX": int(row[2]), - "posY": int(row[3]), - "posZ": int(row[4]), - "materialType": str(row[5]).strip() if pd.notna(row[5]) else "", - "materialId": str(row[6]).strip() if pd.notna(row[6]) else "", - "quantity": 1 # 默认数量1 - } - if item["materialId"] or item["materialType"]: - items.append(item) - - return self._post_lims("/api/lims/order/auto-feeding4to3", items) - - - - def auto_batch_outbound_from_xlsx(self, xlsx_path: str) -> Dict[str, Any]: - """ - 3.31 自动化下料(Excel -> JSON -> POST /api/lims/storage/auto-batch-out-bound) - """ - path = Path(xlsx_path) - if not path.exists(): - raise FileNotFoundError(f"未找到 Excel 文件:{path}") - - try: - df = pd.read_excel(path, sheet_name=0, engine="openpyxl") - except Exception as e: - raise RuntimeError(f"读取 Excel 失败:{e}") - - def pick(names: List[str]) -> Optional[str]: - for n in names: - if n in df.columns: - return n - return None - - c_loc = pick(["locationId", "库位ID", "库位Id", "库位id"]) - c_wh = pick(["warehouseId", "仓库ID", "仓库Id", "仓库id"]) - c_qty = pick(["数量", "quantity"]) - c_x = pick(["x", "X", "posX", "坐标X"]) - c_y = pick(["y", "Y", "posY", "坐标Y"]) - c_z = pick(["z", "Z", "posZ", "坐标Z"]) - - required = [c_loc, c_wh, c_qty, c_x, c_y, c_z] - if any(c is None for c in required): - raise KeyError("Excel 缺少必要列:locationId/warehouseId/数量/x/y/z(支持多别名,至少要能匹配到)。") - - def as_int(v, d=0): - try: - if pd.isna(v): return d - return int(v) - except Exception: - try: - return int(float(v)) - except Exception: - return d - - def as_float(v, d=0.0): - try: - if pd.isna(v): return d - return float(v) - except Exception: - return d - - def as_str(v, d=""): - if v is None or (isinstance(v, float) and pd.isna(v)): return d - s = str(v).strip() - return s if s else d - - items: List[Dict[str, Any]] = [] - for _, row in df.iterrows(): - items.append({ - "locationId": as_str(row[c_loc]), - "warehouseId": as_str(row[c_wh]), - "quantity": as_float(row[c_qty]), - "x": as_int(row[c_x]), - "y": as_int(row[c_y]), - "z": as_int(row[c_z]), - }) - - return self._post_lims("/api/lims/storage/auto-batch-out-bound", items) - - # 2.14 新建实验 - def create_orders(self, xlsx_path: str) -> Dict[str, Any]: - """ - 从 Excel 解析并创建实验(2.14) - 约定: - - batchId = Excel 文件名(不含扩展名) - - 物料列:所有以 "(g)" 结尾(不再读取“总质量(g)”列) - - totalMass 自动计算为所有物料质量之和 - - createTime 缺失或为空时自动填充为当前日期(YYYY/M/D) - """ - path = Path(xlsx_path) - if not path.exists(): - raise FileNotFoundError(f"未找到 Excel 文件:{path}") - - try: - df = pd.read_excel(path, sheet_name=0, engine="openpyxl") - except Exception as e: - raise RuntimeError(f"读取 Excel 失败:{e}") - - # 列名容错:返回可选列名,找不到则返回 None - def _pick(col_names: List[str]) -> Optional[str]: - for c in col_names: - if c in df.columns: - return c - return None - - col_order_name = _pick(["配方ID", "orderName", "订单编号"]) - col_create_time = _pick(["创建日期", "createTime"]) - col_bottle_type = _pick(["配液瓶类型", "bottleType"]) - col_mix_time = _pick(["混匀时间(s)", "mixTime"]) - col_load = _pick(["扣电组装分液体积", "loadSheddingInfo"]) - col_pouch = _pick(["软包组装分液体积", "pouchCellInfo"]) - col_cond = _pick(["电导测试分液体积", "conductivityInfo"]) - col_cond_cnt = _pick(["电导测试分液瓶数", "conductivityBottleCount"]) - - # 物料列:所有以 (g) 结尾 - material_cols = [c for c in df.columns if isinstance(c, str) and c.endswith("(g)")] - if not material_cols: - raise KeyError("未发现任何以“(g)”结尾的物料列,请检查表头。") - - batch_id = path.stem - - def _to_ymd_slash(v) -> str: - # 统一为 "YYYY/M/D";为空或解析失败则用当前日期 - if v is None or (isinstance(v, float) and pd.isna(v)) or str(v).strip() == "": - ts = datetime.now() - else: - try: - ts = pd.to_datetime(v) - except Exception: - ts = datetime.now() - return f"{ts.year}/{ts.month}/{ts.day}" - - def _as_int(val, default=0) -> int: - try: - if pd.isna(val): - return default - return int(val) - except Exception: - return default - - def _as_str(val, default="") -> str: - if val is None or (isinstance(val, float) and pd.isna(val)): - return default - s = str(val).strip() - return s if s else default - - orders: List[Dict[str, Any]] = [] - - for idx, row in df.iterrows(): - mats: List[Dict[str, Any]] = [] - total_mass = 0.0 - - for mcol in material_cols: - val = row.get(mcol, None) - if val is None or (isinstance(val, float) and pd.isna(val)): - continue - try: - mass = float(val) - except Exception: - continue - if mass > 0: - mats.append({"name": mcol.replace("(g)", ""), "mass": mass}) - total_mass += mass - - order_data = { - "batchId": batch_id, - "orderName": _as_str(row[col_order_name], default=f"{batch_id}_order_{idx+1}") if col_order_name else f"{batch_id}_order_{idx+1}", - "createTime": _to_ymd_slash(row[col_create_time]) if col_create_time else _to_ymd_slash(None), - "bottleType": _as_str(row[col_bottle_type], default="配液小瓶") if col_bottle_type else "配液小瓶", - "mixTime": _as_int(row[col_mix_time]) if col_mix_time else 0, - "loadSheddingInfo": _as_int(row[col_load]) if col_load else 0, - "pouchCellInfo": _as_int(row[col_pouch]) if col_pouch else 0, - "conductivityInfo": _as_int(row[col_cond]) if col_cond else 0, - "conductivityBottleCount": _as_int(row[col_cond_cnt]) if col_cond_cnt else 0, - "materialInfos": mats, - "totalMass": round(total_mass, 4) # 自动汇总 - } - orders.append(order_data) - - # print(orders) - - response = self._post_lims("/api/lims/order/orders", orders) - self.order_status[response["data"]["orderCode"]] = "running" - - while True: - time.sleep(5) - if self.order_status.get(response["data"]["orderCode"], None) == "finished": - logger.info(f"配液实验已完成 ,即将执行 3-2-1 转运") - break - logger.info(f"等待配液实验完成") - - self.transfer_3_to_2_to_1() - r321 = self.wait_for_transfer_task() - logger.info(f"3-2-1 转运完成,返回结果") - return r321 - - - # 2.7 启动调度 - def scheduler_start(self) -> Dict[str, Any]: - return self._post_lims("/api/lims/scheduler/start") - # 3.10 停止调度 - def scheduler_stop(self) -> Dict[str, Any]: - """ - 停止调度 (3.10) - 请求体只包含 apiKey 和 requestTime - """ - return self._post_lims("/api/lims/scheduler/stop") - # 2.9 继续调度 - def scheduler_continue(self) -> Dict[str, Any]: - """ - 继续调度 (2.9) - 请求体只包含 apiKey 和 requestTime - """ - return self._post_lims("/api/lims/scheduler/continue") - - - - # 2.24 物料变更推送 - def report_material_change(self, material_obj: Dict[str, Any]) -> Dict[str, Any]: - """ - material_obj 按 2.24 的裸对象格式(包含 id/typeName/locations/detail 等) - """ - return self._post_report_raw("/report/material_change", material_obj) - - # 2.21 步骤完成推送(BS → LIMS) - def report_step_finish(self, - order_code: str, - order_name: str, - step_name: str, - step_id: str, - sample_id: str, - start_time: str, - end_time: str, - execution_status: str = "completed") -> Dict[str, Any]: - data = { - "orderCode": order_code, - "orderName": order_name, - "stepName": step_name, - "stepId": step_id, - "sampleId": sample_id, - "startTime": start_time, - "endTime": end_time, - "executionStatus": execution_status - } - return self._post_report("/report/step_finish", data) - - # 2.23 订单完成推送(BS → LIMS) - def report_order_finish(self, - order_code: str, - order_name: str, - start_time: str, - end_time: str, - status: str = "30", # 30 完成 / -11 异常停止 / -12 人工停止 - workflow_status: str = "Finished", - completion_time: Optional[str] = None, - used_materials: Optional[List[Dict[str, Any]]] = None) -> Dict[str, Any]: - data = { - "orderCode": order_code, - "orderName": order_name, - "startTime": start_time, - "endTime": end_time, - "status": status, - "workflowStatus": workflow_status, - "completionTime": completion_time or end_time, - "usedMaterials": used_materials or [] - } - return self._post_report("/report/order_finish", data) - - # 2.5 批量查询实验报告(用于轮询是否完成) - def order_list(self, - status: Optional[str] = None, - begin_time: Optional[str] = None, - end_time: Optional[str] = None, - filter_text: Optional[str] = None, - skip: int = 0, page: int = 10) -> Dict[str, Any]: - data: Dict[str, Any] = {"skipCount": skip, "pageCount": page} - if status is not None: # 80 成功 / 90 失败 / 100 执行中 - data["status"] = status - if begin_time: - data["timeType"] = "CreationTime" - data["beginTime"] = begin_time - if end_time: - data["endTime"] = end_time - if filter_text: - data["filter"] = filter_text - return self._post_lims("/api/lims/order/order-list", data) - - # 2.6 实验报告查询(根据任务ID拿详情) - def order_report(self, order_id: str) -> Dict[str, Any]: - return self._post_lims("/api/lims/order/order-report", order_id) - - # 2.32 3-2-1 物料转运 - def transfer_3_to_2_to_1(self, - # source_wh_id: Optional[str] = None, - source_wh_id: Optional[str] = '3a19debc-84b4-0359-e2d4-b3beea49348b', - source_x: int = 1, source_y: int = 1, source_z: int = 1) -> Dict[str, Any]: - payload: Dict[str, Any] = { - "sourcePosX": source_x, "sourcePosY": source_y, "sourcePosZ": source_z - } - if source_wh_id: - payload["sourceWHID"] = source_wh_id - return self._post_lims("/api/lims/order/transfer-task3To2To1", payload) - - # 2.28 样品/废料取出 - def take_out(self, - order_id: str, - preintake_ids: Optional[List[str]] = None, - material_ids: Optional[List[str]] = None) -> Dict[str, Any]: - data = { - "orderId": order_id, - "preintakeIds": preintake_ids or [], - "materialIds": material_ids or [] - } - return self._post_lims("/api/lims/order/take-out", data) - - # --------(可选)占位方法:文档未定义的“1号站内部流程 / 1-2转运”-------- - def start_station1_internal_flow(self, **kwargs) -> None: - logger.info("启动1号站内部流程(占位,按现场系统填充具体指令)") - - - # 3.x 1→2 物料转运 - def transfer_1_to_2(self) -> Dict[str, Any]: - """ - 1→2 物料转运 - URL: /api/lims/order/transfer-task1To2 - 只需要 apiKey 和 requestTime - """ - return self._post_lims("/api/lims/order/transfer-task1To2") - - - # -------------------- 整体编排 -------------------- - def run_full_workflow(self, - inbound_items: List[Dict[str, str]], - orders: List[Dict[str, Any]], - poll_filter_code: Optional[str] = None, - poll_timeout_s: int = 600, - poll_interval_s: int = 5, - transfer_source: Optional[Dict[str, Any]] = None, - takeout_order_id: Optional[str] = None) -> None: - """ - 一键串联: - 1) 入库 3-4 个物料 → 2) 新建实验 → 3) 启动调度 - 运行中(如需):4) 物料变更推送 5) 步骤完成推送 6) 订单完成推送 - 完成后:查询实验(2.5/2.6)→ 7) 3-2-1 转运 → 8) 1号站内部流程 - → 9) 1-2 转运 → 10) 样品/废料取出 - """ - # 1. 入库(多于1个就用批量接口 2.18) - if len(inbound_items) == 1: - r = self.storage_inbound(inbound_items[0]["materialId"], inbound_items[0]["locationId"]) - logger.info(f"单个入库结果: {r}") - else: - r = self.storage_batch_inbound(inbound_items) - logger.info(f"批量入库结果: {r}") - - # 2. 新建实验(2.14) - r = self.create_orders(orders) - logger.info(f"新建实验结果: {r}") - - # 3. 启动调度(2.7) - r = self.scheduler_start() - logger.info(f"启动调度结果: {r}") - - # —— 运行中各类推送(2.24 / 2.21 / 2.23),通常由实际任务驱动,这里提供调用方式 —— # - # self.report_material_change({...}) - # self.report_step_finish(order_code="BSO...", order_name="配液分液", step_name="xxx", step_id="...", sample_id="...", - # start_time=_iso_utc_now_ms(), end_time=_iso_utc_now_ms(), execution_status="completed") - # self.report_order_finish(order_code="BSO...", order_name="配液分液", start_time="...", end_time=_iso_utc_now_ms()) - - # 完成后才能转运:用 2.5 批量查询配合 filter=任务编码 轮询到 status=80(成功) - if poll_filter_code: - import time - deadline = time.time() + poll_timeout_s - while time.time() < deadline: - res = self.order_list(status="80", filter_text=poll_filter_code, page=5) - if isinstance(res, dict) and res.get("data", {}).get("items"): - logger.info(f"实验 {poll_filter_code} 已完成:{res['data']['items'][0]}") - break - time.sleep(poll_interval_s) - else: - logger.warning(f"等待实验 {poll_filter_code} 完成超时(未到 status=80)") - - # 7. 启动 3-2-1 转运(2.32) - if transfer_source: - r = self.transfer_3_to_2_to_1( - source_wh_id=transfer_source.get("sourceWHID"), - source_x=transfer_source.get("sourcePosX", 1), - source_y=transfer_source.get("sourcePosY", 1), - source_z=transfer_source.get("sourcePosZ", 1), - ) - logger.info(f"3-2-1 转运结果: {r}") - - # 8. 1号站内部流程(占位) - self.start_station1_internal_flow() - - # 9. 1→2 转运(占位) - self.transfer_1_to_2() - - # 10. 样品/废料取出(2.28) - if takeout_order_id: - r = self.take_out(order_id=takeout_order_id) - logger.info(f"样品/废料取出结果: {r}") - - # 2.5 批量查询实验报告 - def order_list_v2(self, - timeType: str = "string", - beginTime: str = "", - endTime: str = "", - status: str = "", - filter: str = "物料转移任务", - skipCount: int = 0, - pageCount: int = 1, - sorting: str = "") -> Dict[str, Any]: - """ - 批量查询实验报告的详细信息 (2.5) - URL: /api/lims/order/order-list - 参数默认值和接口文档保持一致 - """ - data: Dict[str, Any] = { - "timeType": timeType, - "beginTime": beginTime, - "endTime": endTime, - "status": status, - "filter": filter, - "skipCount": skipCount, - "pageCount": pageCount, - "sorting": sorting - } - return self._post_lims("/api/lims/order/order-list", data) - - - def wait_for_transfer_task(self, timeout: int = 600, interval: int = 3) -> bool: - """ - 轮询查询物料转移任务是否成功完成 (status=80) - - timeout: 最大等待秒数 (默认600秒) - - interval: 轮询间隔秒数 (默认3秒) - 返回 True 表示找到并成功完成,False 表示超时未找到 - """ - now = datetime.now() - beginTime = now.strftime("%Y-%m-%dT%H:%M:%SZ") - endTime = (now + timedelta(minutes=5)).strftime("%Y-%m-%dT%H:%M:%SZ") - print(beginTime, endTime) - - deadline = time.time() + timeout - - while time.time() < deadline: - result = self.order_list_v2( - timeType="string", - beginTime=beginTime, - endTime=endTime, - status="", - filter="物料转移任务", - skipCount=0, - pageCount=1, - sorting="" - ) - print(result) - - items = result.get("data", {}).get("items", []) - for item in items: - name = item.get("name", "") - status = item.get("status") - if name.startswith("物料转移任务") and status == 80: - logger.info(f"硬件转移动作完成: {name}") - return True - - time.sleep(interval) - - logger.warning("超时未找到成功的物料转移任务") - return False - - -# -------------------------------- -if __name__ == "__main__": - ws = BioyondWorkstation() - # ws.scheduler_stop() - ws.scheduler_start() - logger.info("调度启动完成") - - # ws.scheduler_continue() - # 3.30 上料:读取模板 Excel 自动解析并 POST - r1 = ws.auto_feeding4to3_from_xlsx(r"C:\ML\GitHub\Uni-Lab-OS\unilabos\devices\workstation\bioyond_cell\样品导入模板 (8).xlsx") - ws.wait_for_transfer_task() - logger.info("4号箱向3号箱转运物料转移任务已完成") - - # ws.scheduler_start() - # print(r1["payload"]["data"]) # 调试模式下可直接看到要发的 JSON items - - # 新建实验 - res = ws.create_orders("C:/ML/GitHub/Uni-Lab-OS/unilabos/devices/workstation/bioyond_cell/2025092501.xlsx") - # ws.scheduler_start() - # print(res) - - #1号站启动 - ws.transfer_1_to_2() - ws.wait_for_transfer_task() - logger.info("1号站向2号站转移任务完成") - logger.info("全流程结束") - - # 3.31 下料:同理 - # r2 = ws.auto_batch_outbound_from_xlsx(r"C:/path/样品导入模板 (8).xlsx") - # print(r2["payload"]["data"]) diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 6acc8f6..d609440 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -17,7 +17,7 @@ API_CONFIG = { "report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"), # HTTP 服务配置 - "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.21.33.30"), # HTTP服务监听地址,监听计算机飞连ip地址 + "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.21.32.115"), # HTTP服务监听地址,监听计算机飞连ip地址 "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), "debug_mode": False,# 调试模式 } @@ -155,8 +155,9 @@ MATERIAL_TYPE_MAPPINGS = { "100ml液体": ("YB_1Bottle100mlCarrier", "d37166b3-ecaa-481e-bd84-3032b795ba07"), "液": ("YB_1BottleCarrier", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), "高粘液": ("YB_1GaoNianYeBottleCarrier", "abe8df30-563d-43d2-85e0-cabec59ddc16"), - "加样头(大)": ("YB_jia_yang_tou_da", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), - "加样头(大)板": ("YB_jia_yang_tou_da_1X1_carrier", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"), + # "加样头(大)": ("YB_jia_yang_tou_da", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + "加样头(大)": ("YB_jia_yang_tou_da_1X1_carrier", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + # "加样头(大)板": ("YB_jia_yang_tou_da_1X1_carrier", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"), "5ml分液瓶板": ("YB_6x5ml_DispensingVialCarrier", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"), "5ml分液瓶": ("YB_fen_ye_5ml_Bottle", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"), "20ml分液瓶板": ("YB_6x20ml_DispensingVialCarrier", "3a192fa4-47db-3449-162a-eaf8aba57e27"), diff --git a/unilabos/devices/workstation/bioyond_studio/station.py b/unilabos/devices/workstation/bioyond_studio/station.py index e2f4da8..af81d53 100644 --- a/unilabos/devices/workstation/bioyond_studio/station.py +++ b/unilabos/devices/workstation/bioyond_studio/station.py @@ -21,7 +21,6 @@ from unilabos.ros.nodes.base_device_node import ROS2DeviceNode, BaseROS2DeviceNo from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode from pylabrobot.resources.resource import Resource as ResourcePLR -from unilabos.resources.bioyond.decks import YB_Deck from unilabos.devices.workstation.bioyond_studio.config import ( API_CONFIG, WORKFLOW_MAPPINGS, MATERIAL_TYPE_MAPPINGS, WAREHOUSE_MAPPING ) @@ -64,7 +63,7 @@ class BioyondResourceSynchronizer(ResourceSynchronizer): logger.error("Bioyond API客户端未初始化") return False - bioyond_data = self.bioyond_api_client.stock_material('{"typeMode": 1, "includeDetail": true}') + bioyond_data = self.bioyond_api_client.stock_material('{"typeMode": 2, "includeDetail": true}') if not bioyond_data: logger.warning("从Bioyond获取的物料数据为空") return False @@ -138,7 +137,7 @@ class BioyondWorkstation(WorkstationBase): # 初始化父类 super().__init__( # 桌子 - deck=YB_Deck("YB_Deck14"), + deck=deck, *args, **kwargs, ) diff --git a/unilabos/registry/devices/bioyond_cell.yaml b/unilabos/registry/devices/bioyond_cell.yaml index d6988b7..a9d754f 100644 --- a/unilabos/registry/devices/bioyond_cell.yaml +++ b/unilabos/registry/devices/bioyond_cell.yaml @@ -137,7 +137,7 @@ bioyond_cell: WH4_x5_y1_z1_5_quantity: 0.0 WH4_x5_y2_z1_10_materialName: '' WH4_x5_y2_z1_10_quantity: 0.0 - xlsx_path: unilabos\devices\workstation\bioyond_studio\bioyond_cell\material_template.xlsx + xlsx_path: C:/ML/GitHub/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx handles: {} placeholder_keys: {} result: {} @@ -463,7 +463,7 @@ bioyond_cell: default: 0.0 type: number xlsx_path: - default: unilabos\devices\workstation\bioyond_studio\bioyond_cell\material_template.xlsx + default: C:/ML/GitHub/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx type: string required: [] type: object @@ -473,31 +473,6 @@ bioyond_cell: title: auto_feeding4to3参数 type: object type: UniLabJsonCommand - auto-auto_feeding4to3_from_xlsx: - feedback: {} - goal: {} - goal_default: - xlsx_path: null - handles: {} - placeholder_keys: {} - result: {} - schema: - description: '' - properties: - feedback: {} - goal: - properties: - xlsx_path: - type: string - required: - - xlsx_path - type: object - result: {} - required: - - goal - title: auto_feeding4to3_from_xlsx参数 - type: object - type: UniLabJsonCommand auto-create_and_inbound_materials: feedback: {} goal: {} @@ -1039,7 +1014,7 @@ bioyond_cell: goal: {} goal_default: order_code: null - timeout: 1800 + timeout: 36000 handles: {} placeholder_keys: {} result: {} @@ -1052,7 +1027,7 @@ bioyond_cell: order_code: type: string timeout: - default: 1800 + default: 36000 type: integer required: - order_code @@ -1105,9 +1080,11 @@ bioyond_cell: init_param_schema: config: properties: - bioyond_config: + config: + type: object + deck: type: string - station_resource: + protocol_type: type: string required: [] type: object diff --git a/unilabos/registry/resources/bioyond/deck.yaml b/unilabos/registry/resources/bioyond/deck.yaml index ef93705..664bab3 100644 --- a/unilabos/registry/resources/bioyond/deck.yaml +++ b/unilabos/registry/resources/bioyond/deck.yaml @@ -22,13 +22,13 @@ BIOYOND_PolymerReactionStation_Deck: init_param_schema: {} registry_type: resource version: 1.0.0 -YB_Deck16: +BIOYOND_YB_Deck: category: - deck class: - module: unilabos.resources.bioyond.decks:YB_Deck + module: unilabos.resources.bioyond.decks:BIOYOND_YB_Deck type: pylabrobot - description: BIOYOND PolymerReactionStation Deck + description: BIOYOND_YB_Deck handles: [] icon: 配液站.webp init_param_schema: {} diff --git a/unilabos/resources/bioyond/decks.py b/unilabos/resources/bioyond/decks.py index fa242c3..7bf1c38 100644 --- a/unilabos/resources/bioyond/decks.py +++ b/unilabos/resources/bioyond/decks.py @@ -1,4 +1,5 @@ from os import name +from pickle import TRUE from pylabrobot.resources import Deck, Coordinate, Rotation from unilabos.resources.bioyond.warehouses import bioyond_warehouse_1x4x4, bioyond_warehouse_1x4x2, bioyond_warehouse_liquid_and_lid_handling, bioyond_warehouse_1x2x2, bioyond_warehouse_1x3x3, bioyond_warehouse_10x1x1, bioyond_warehouse_3x3x1, bioyond_warehouse_3x3x1_2, bioyond_warehouse_5x1x1 @@ -71,7 +72,7 @@ class BIOYOND_PolymerPreparationStation_Deck(Deck): class BIOYOND_YB_Deck(Deck): def __init__( self, - name: str = "YB_Deck", + name: str = "YB_Bioyond_Deck", size_x: float = 4150, size_y: float = 1400.0, size_z: float = 2670.0, @@ -115,10 +116,10 @@ class BIOYOND_YB_Deck(Deck): for warehouse_name, warehouse in self.warehouses.items(): self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name]) -def YB_Deck(name: str) -> Deck: - by=BIOYOND_YB_Deck(name=name) - by.setup() - return by +# def YB_Deck(name: str) -> Deck: +# # by=BIOYOND_YB_Deck(name=name) +# # by.setup() +# return None From 10adc853a53fa4c7f02bd151fee63edd83bdfb7d Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Mon, 3 Nov 2025 19:50:01 +0800 Subject: [PATCH 048/104] 1103-2byxinyu --- .../workstation/bioyond_studio/station.py | 2 + .../devices/coin_cell_workstation.yaml | 4 +- unilabos/resources/bioyond/YB_warehouses.py | 71 ++++++++++++++++--- unilabos/resources/bioyond/decks.py | 40 +++++------ 4 files changed, 80 insertions(+), 37 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/station.py b/unilabos/devices/workstation/bioyond_studio/station.py index af81d53..d6fe487 100644 --- a/unilabos/devices/workstation/bioyond_studio/station.py +++ b/unilabos/devices/workstation/bioyond_studio/station.py @@ -172,6 +172,8 @@ class BioyondWorkstation(WorkstationBase): def post_init(self, ros_node: ROS2WorkstationNode): self._ros_node = ros_node + print("~~~",self._ros_node) + print("deck",self.deck) ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ "resources": [self.deck] }) diff --git a/unilabos/registry/devices/coin_cell_workstation.yaml b/unilabos/registry/devices/coin_cell_workstation.yaml index 4738946..06caa0f 100644 --- a/unilabos/registry/devices/coin_cell_workstation.yaml +++ b/unilabos/registry/devices/coin_cell_workstation.yaml @@ -79,7 +79,7 @@ coincellassemblyworkstation_device: elec_num: null elec_use_num: null elec_vol: 50 - file_path: D:\coin_cell_data + file_path: C:\Users\67484\Desktop handles: {} placeholder_keys: {} result: {} @@ -103,7 +103,7 @@ coincellassemblyworkstation_device: default: 50 type: integer file_path: - default: D:\coin_cell_data + default: C:\Users\67484\Desktop type: string required: - elec_num diff --git a/unilabos/resources/bioyond/YB_warehouses.py b/unilabos/resources/bioyond/YB_warehouses.py index c546759..39f79ea 100644 --- a/unilabos/resources/bioyond/YB_warehouses.py +++ b/unilabos/resources/bioyond/YB_warehouses.py @@ -1,9 +1,9 @@ -from unilabos.resources.warehouse import WareHouse, warehouse_factory +from unilabos.resources.warehouse import WareHouse, YB_warehouse_factory def bioyond_warehouse_1x4x4(name: str) -> WareHouse: """创建BioYond 4x1x4仓库""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=1, num_items_y=4, @@ -20,7 +20,7 @@ def bioyond_warehouse_1x4x4(name: str) -> WareHouse: def bioyond_warehouse_1x4x2(name: str) -> WareHouse: """创建BioYond 4x1x2仓库""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=1, num_items_y=4, @@ -37,7 +37,7 @@ def bioyond_warehouse_1x4x2(name: str) -> WareHouse: # 定义benyond的堆栈 def bioyond_warehouse_1x2x2(name: str) -> WareHouse: """创建BioYond 4x1x4仓库""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=2, num_items_y=2, @@ -50,9 +50,26 @@ def bioyond_warehouse_1x2x2(name: str) -> WareHouse: item_dz=120.0, category="YB_warehouse", ) + +def bioyond_warehouse_2x2x1(name: str) -> WareHouse: + """创建BioYond 2x2x1仓库(自动堆栈)""" + return YB_warehouse_factory( + name=name, + num_items_x=2, + num_items_y=2, + num_items_z=1, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="YB_warehouse", + ) + def bioyond_warehouse_10x1x1(name: str) -> WareHouse: """创建BioYond 4x1x4仓库""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=10, num_items_y=1, @@ -67,7 +84,7 @@ def bioyond_warehouse_10x1x1(name: str) -> WareHouse: ) def bioyond_warehouse_1x3x3(name: str) -> WareHouse: """创建BioYond 4x1x4仓库""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=1, num_items_y=3, @@ -82,7 +99,7 @@ def bioyond_warehouse_1x3x3(name: str) -> WareHouse: ) def bioyond_warehouse_2x1x3(name: str) -> WareHouse: """创建BioYond 4x1x4仓库""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=2, num_items_y=1, @@ -98,7 +115,7 @@ def bioyond_warehouse_2x1x3(name: str) -> WareHouse: def bioyond_warehouse_3x3x1(name: str) -> WareHouse: """创建BioYond 4x1x4仓库""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=3, num_items_y=3, @@ -113,7 +130,7 @@ def bioyond_warehouse_3x3x1(name: str) -> WareHouse: ) def bioyond_warehouse_5x1x1(name: str) -> WareHouse: """创建BioYond 4x1x4仓库""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=5, num_items_y=1, @@ -128,7 +145,7 @@ def bioyond_warehouse_5x1x1(name: str) -> WareHouse: ) def bioyond_warehouse_3x3x1_2(name: str) -> WareHouse: """创建BioYond 4x1x4仓库""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=3, num_items_y=3, @@ -144,7 +161,7 @@ def bioyond_warehouse_3x3x1_2(name: str) -> WareHouse: def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse: """创建BioYond开关盖加液模块台面""" - return warehouse_factory( + return YB_warehouse_factory( name=name, num_items_x=2, num_items_y=5, @@ -157,4 +174,36 @@ def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse: item_dz=120.0, category="warehouse", removed_positions=None + ) + +def bioyond_warehouse_3x5x1(name: str) -> WareHouse: + """创建BioYond 3x5x1仓库(手动堆栈)""" + return YB_warehouse_factory( + name=name, + num_items_x=3, + num_items_y=5, + num_items_z=1, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", + ) + +def bioyond_warehouse_20x1x1(name: str) -> WareHouse: + """创建BioYond 20x1x1仓库(粉末加样头堆栈)""" + return YB_warehouse_factory( + name=name, + num_items_x=20, + num_items_y=1, + num_items_z=1, + dx=10.0, + dy=10.0, + dz=10.0, + item_dx=137.0, + item_dy=96.0, + item_dz=120.0, + category="warehouse", ) \ No newline at end of file diff --git a/unilabos/resources/bioyond/decks.py b/unilabos/resources/bioyond/decks.py index 7bf1c38..b89a032 100644 --- a/unilabos/resources/bioyond/decks.py +++ b/unilabos/resources/bioyond/decks.py @@ -72,7 +72,7 @@ class BIOYOND_PolymerPreparationStation_Deck(Deck): class BIOYOND_YB_Deck(Deck): def __init__( self, - name: str = "YB_Bioyond_Deck", + name: str = "YB_Deck", size_x: float = 4150, size_y: float = 1400.0, size_z: float = 2670.0, @@ -86,36 +86,28 @@ class BIOYOND_YB_Deck(Deck): def setup(self) -> None: # 添加仓库 self.warehouses = { - "321窗口": bioyond_warehouse_1x2x2("321窗口"), - "43窗口": bioyond_warehouse_1x2x2("43窗口"), - "手动传递窗左": bioyond_warehouse_1x3x3("手动传递窗左"), - "手动传递窗右": bioyond_warehouse_1x3x3("手动传递窗右"), - "加样头堆栈左": bioyond_warehouse_10x1x1("加样头堆栈左"), - "加样头堆栈右": bioyond_warehouse_10x1x1("加样头堆栈右"), - - "15ml配液堆栈左": bioyond_warehouse_3x3x1("15ml配液堆栈左"), - "母液加样右": bioyond_warehouse_3x3x1_2("母液加样右"), - "大瓶母液堆栈左": bioyond_warehouse_5x1x1("大瓶母液堆栈左"), - "大瓶母液堆栈右": bioyond_warehouse_5x1x1("大瓶母液堆栈右"), + "自动堆栈-左": bioyond_warehouse_2x2x1("自动堆栈-左"), + "自动堆栈-右": bioyond_warehouse_2x2x1("自动堆栈-右"), + "手动堆栈-左": bioyond_warehouse_3x5x1("手动堆栈-左"), + "手动堆栈-右": bioyond_warehouse_3x5x1("手动堆栈-右"), + "粉末加样头堆栈": bioyond_warehouse_20x1x1("粉末加样头堆栈"), + "配液站内试剂仓库": bioyond_warehouse_3x3x1("配液站内试剂仓库"), + "试剂替换仓库": bioyond_warehouse_10x1x1("试剂替换仓库"), } # warehouse 的位置 self.warehouse_locations = { - "321窗口": Coordinate(-150.0, 158.0, 0.0), - "43窗口": Coordinate(4160.0, 158.0, 0.0), - "手动传递窗左": Coordinate(-150.0, 877.0, 0.0), - "手动传递窗右": Coordinate(4160.0, 877.0, 0.0), - "加样头堆栈左": Coordinate(385.0, 1300.0, 0.0), - "加样头堆栈右": Coordinate(2187.0, 1300.0, 0.0), - - "15ml配液堆栈左": Coordinate(749.0, 355.0, 0.0), - "母液加样右": Coordinate(2152.0, 333.0, 0.0), - "大瓶母液堆栈左": Coordinate(1164.0, 676.0, 0.0), - "大瓶母液堆栈右": Coordinate(2717.0, 676.0, 0.0), + "自动堆栈-左": Coordinate(-300.0, 158.0, 0.0), + "自动堆栈-右": Coordinate(4160.0, 158.0, 0.0), + "手动堆栈-左": Coordinate(-400.0, 877.0, 0.0), + "手动堆栈-右": Coordinate(4160.0, 877.0, 0.0), + "粉末加样头堆栈": Coordinate(385.0, 1300.0, 0.0), + "配液站内试剂仓库": Coordinate(1164.0, 676.0, 0.0), + "试剂替换仓库": Coordinate(2717.0, 676.0, 0.0), } for warehouse_name, warehouse in self.warehouses.items(): self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name]) - + # def YB_Deck(name: str) -> Deck: # # by=BIOYOND_YB_Deck(name=name) # # by.setup() From f230028558441e2195b79b57b1cd54d4df4bcd19 Mon Sep 17 00:00:00 2001 From: calvincao Date: Mon, 3 Nov 2025 21:30:27 +0800 Subject: [PATCH 049/104] feat: Enhance CoincellDeck setup with new ClipMagazine and BottleRack configurations - Refactored ClipMagazine class to inherit from ItemizedResource and updated hole dimensions. - Introduced ClipMagazine_four class for a new 2x2 hole layout. - Expanded CoincellDeck setup to include multiple ClipMagazines and MaterialPlates with ElectrodeSheets. - Improved BottleRack initialization with dynamic item positioning and resource assignment. - Added serialization methods for new classes to maintain state consistency. --- .../coin_cell_assembly/YB_YH_materials.py | 349 ++++++++++++++---- 1 file changed, 269 insertions(+), 80 deletions(-) diff --git a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py index 6232fb8..5c3aa87 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py +++ b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py @@ -387,17 +387,17 @@ class ClipMagazineHole(Container): } # TODO: 这个要改 -class ClipMagazine(Resource): +class ClipMagazine(ItemizedResource[ClipMagazineHole]): """子弹夹类 - 有6个洞位,每个洞位放多个极片""" - + children: List[ClipMagazineHole] def __init__( self, name: str, size_x: float, size_y: float, size_z: float, - hole_diameter: float = 20.0, - hole_depth: float = 50.0, + hole_diameter: float = 14.0, + hole_depth: float = 10.0, hole_spacing: float = 25.0, max_sheets_per_hole: int = 100, category: str = "clip_magazine", @@ -441,6 +441,7 @@ class ClipMagazine(Resource): model=model, ) + # 保存洞位的直径和深度 self.hole_diameter = hole_diameter self.hole_depth = hole_depth self.max_sheets_per_hole = max_sheets_per_hole @@ -744,16 +745,22 @@ class BottleRackState(TypedDict): class BottleRack(Resource): """瓶架类 - 12个待配位置+12个已配位置""" - children: List[Bottle] = [] + children: List[Resource] = [] def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - category: str = "bottle_rack", - model: Optional[str] = None, + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + category: str = "bottle_rack", + model: Optional[str] = None, + num_items_x: int = 3, + num_items_y: int = 4, + position_spacing: float = 35.0, + orientation: str = "horizontal", + padding_x: float = 20.0, + padding_y: float = 20.0, ): """初始化瓶架 @@ -773,13 +780,42 @@ class BottleRack(Resource): category=category, model=model, ) - # TODO: 添加瓶位坐标映射 - self.index_to_pos = { - 0: Coordinate.zero(), - 1: Coordinate(x=1, y=2, z=3) # 添加 - } + # 初始化状态 + self._unilabos_state: BottleRackState = BottleRackState( + bottle_diameter=30.0, + bottle_height=100.0, + position_spacing=position_spacing, + name_to_index={}, + ) + # 基于网格生成瓶位坐标映射(居中摆放) + # 使用内边距,避免点跑到容器外(前端渲染不按mm等比缩放时更稳妥) + origin_x = padding_x + origin_y = padding_y + self.index_to_pos = {} + for j in range(num_items_y): + for i in range(num_items_x): + idx = j * num_items_x + i + if orientation == "vertical": + # 纵向:沿 y 方向优先排列 + self.index_to_pos[idx] = Coordinate( + x=origin_x + j * position_spacing, + y=origin_y + i * position_spacing, + z=0, + ) + else: + # 横向(默认):沿 x 方向优先排列 + self.index_to_pos[idx] = Coordinate( + x=origin_x + i * position_spacing, + y=origin_y + j * position_spacing, + z=0, + ) self.name_to_index = {} self.name_to_pos = {} + self.num_items_x = num_items_x + self.num_items_y = num_items_y + self.orientation = orientation + self.padding_x = padding_x + self.padding_y = padding_y def load_state(self, state: Dict[str, Any]) -> None: """格式不变""" @@ -789,20 +825,23 @@ class BottleRack(Resource): def serialize_state(self) -> Dict[str, Dict[str, Any]]: """格式不变""" data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + data.update( + self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) return data # TODO: 这里有些问题要重新写一下 - def assign_child_resource(self, resource: Bottle, location=Coordinate.zero(), reassign = True): - assert len(self.children) <= 12, "瓶架已满,无法添加更多瓶子" + def assign_child_resource_old(self, resource: Resource, location=Coordinate.zero(), reassign=True): + capacity = self.num_items_x * self.num_items_y + assert len(self.children) < capacity, "瓶架已满,无法添加更多瓶子" index = len(self.children) - location = Coordinate(x=20 + (index % 4) * 15, y=20 + (index // 4) * 15, z=0) + location = self.index_to_pos.get(index, Coordinate.zero()) self.name_to_pos[resource.name] = location self.name_to_index[resource.name] = index return super().assign_child_resource(resource, location, reassign) - - def assign_child_resource_by_index(self, resource: Bottle, index: int): - assert 0 <= index < 12, "无效的瓶子索引" + + def assign_child_resource(self, resource: Resource, index: int): + capacity = self.num_items_x * self.num_items_y + assert 0 <= index < capacity, "无效的瓶子索引" self.name_to_index[resource.name] = index location = self.index_to_pos[index] return super().assign_child_resource(resource, location) @@ -811,10 +850,16 @@ class BottleRack(Resource): super().unassign_child_resource(resource) self.index_to_pos.pop(self.name_to_index.pop(resource.name, None), None) - # def serialize(self): - # self.children.sort(key=lambda x: self.name_to_index.get(x.name, 0)) - # return super().serialize() - + def serialize(self) -> dict: + return { + **super().serialize(), + "num_items_x": self.num_items_x, + "num_items_y": self.num_items_y, + "position_spacing": self._unilabos_state.get("position_spacing", 35.0), + "orientation": self.orientation, + "padding_x": self.padding_x, + "padding_y": self.padding_y, + } class BottleState(TypedDict): diameter: float @@ -868,6 +913,73 @@ class Bottle(Resource): data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) return data +class ClipMagazine_four(ItemizedResource[ClipMagazineHole]): + """子弹夹类 - 有4个洞位,每个洞位放多个极片""" + children: List[ClipMagazineHole] + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + hole_diameter: float = 14.0, + hole_depth: float = 10.0, + hole_spacing: float = 25.0, + max_sheets_per_hole: int = 100, + category: str = "clip_magazine_four", + model: Optional[str] = None, + ): + """初始化子弹夹 + + Args: + name: 子弹夹名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + hole_diameter: 洞直径 (mm) + hole_depth: 洞深度 (mm) + hole_spacing: 洞位间距 (mm) + max_sheets_per_hole: 每个洞位最大极片数量 + category: 类别 + model: 型号 + """ + # 创建4个洞位,排成2x2布局 + holes = create_ordered_items_2d( + klass=ClipMagazineHole, + num_items_x=2, + num_items_y=2, + dx=(size_x - 2 * hole_spacing) / 2, # 居中 + dy=(size_y - hole_spacing) / 2, # 居中 + dz=size_z - 0, + item_dx=hole_spacing, + item_dy=hole_spacing, + diameter=hole_diameter, + depth=hole_depth, + ) + + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=holes, + category=category, + model=model, + ) + + # 保存洞位的直径和深度 + self.hole_diameter = hole_diameter + self.hole_depth = hole_depth + self.max_sheets_per_hole = max_sheets_per_hole + + def serialize(self) -> dict: + return { + **super().serialize(), + "hole_diameter": self.hole_diameter, + "hole_depth": self.hole_depth, + "max_sheets_per_hole": self.max_sheets_per_hole, + } + class CoincellDeck(Deck): """纽扣电池组装工作站台面类""" @@ -904,59 +1016,131 @@ class CoincellDeck(Deck): self.setup() def setup(self) -> None: - """设置工作站的标准布局 - 包含3个料盘""" - # 步骤 1: 创建所有料盘 - self.plates = { - "liaopan1": MaterialPlate( - name="liaopan1", - size_x=120.8, - size_y=120.5, - size_z=10.0, - fill=True - ), - "liaopan2": MaterialPlate( - name="liaopan2", - size_x=120.8, - size_y=120.5, - size_z=10.0, - fill=True - ), - "电池料盘": MaterialPlate( - name="电池料盘", - size_x=120.8, - size_y=160.5, - size_z=10.0, - fill=True - ), - } + """设置工作站的标准布局 - 包含子弹夹、料盘、瓶架等完整配置""" + # ====================================== 子弹夹 ============================================ + zip_dan_jia = ClipMagazine_four("zi_dan_jia", 80, 80, 10) + self.assign_child_resource(zip_dan_jia, Coordinate(x=1400, y=50, z=0)) + zip_dan_jia2 = ClipMagazine_four("zi_dan_jia2", 80, 80, 10) + self.assign_child_resource(zip_dan_jia2, Coordinate(x=1600, y=200, z=0)) + zip_dan_jia3 = ClipMagazine("zi_dan_jia3", 80, 80, 10) + self.assign_child_resource(zip_dan_jia3, Coordinate(x=1500, y=200, z=0)) + zip_dan_jia4 = ClipMagazine("zi_dan_jia4", 80, 80, 10) + self.assign_child_resource(zip_dan_jia4, Coordinate(x=1500, y=300, z=0)) + zip_dan_jia5 = ClipMagazine("zi_dan_jia5", 80, 80, 10) + self.assign_child_resource(zip_dan_jia5, Coordinate(x=1600, y=300, z=0)) + zip_dan_jia6 = ClipMagazine("zi_dan_jia6", 80, 80, 10) + self.assign_child_resource(zip_dan_jia6, Coordinate(x=1530, y=500, z=0)) + zip_dan_jia7 = ClipMagazine("zi_dan_jia7", 80, 80, 10) + self.assign_child_resource(zip_dan_jia7, Coordinate(x=1180, y=400, z=0)) + zip_dan_jia8 = ClipMagazine("zi_dan_jia8", 80, 80, 10) + self.assign_child_resource(zip_dan_jia8, Coordinate(x=1280, y=400, z=0)) - # 步骤 2: 定义料盘在 deck 上的位置 - # Deck 尺寸: 1000×1000mm,料盘尺寸: 120.8×120.5mm 或 120.8×160.5mm - self.plate_locations = { - "liaopan1": Coordinate(x=50, y=50, z=0), # 左上角,留 50mm 边距 - "liaopan2": Coordinate(x=250, y=50, z=0), # 中间,liaopan1 右侧 - "电池料盘": Coordinate(x=450, y=50, z=0), # 右侧 - } + # 为子弹夹添加极片 + for i in range(4): + jipian = ElectrodeSheet(name=f"zi_dan_jia_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia2.children[i].assign_child_resource(jipian, location=None) + for i in range(4): + jipian2 = ElectrodeSheet(name=f"zi_dan_jia2_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia.children[i].assign_child_resource(jipian2, location=None) + for i in range(6): + jipian3 = ElectrodeSheet(name=f"zi_dan_jia3_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia3.children[i].assign_child_resource(jipian3, location=None) + for i in range(6): + jipian4 = ElectrodeSheet(name=f"zi_dan_jia4_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia4.children[i].assign_child_resource(jipian4, location=None) + for i in range(6): + jipian5 = ElectrodeSheet(name=f"zi_dan_jia5_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia5.children[i].assign_child_resource(jipian5, location=None) + for i in range(6): + jipian6 = ElectrodeSheet(name=f"zi_dan_jia6_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia6.children[i].assign_child_resource(jipian6, location=None) + for i in range(6): + jipian7 = ElectrodeSheet(name=f"zi_dan_jia7_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia7.children[i].assign_child_resource(jipian7, location=None) + for i in range(6): + jipian8 = ElectrodeSheet(name=f"zi_dan_jia8_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia8.children[i].assign_child_resource(jipian8, location=None) - # 步骤 3: 将料盘分配到 deck 上 - for plate_name, plate in self.plates.items(): - self.assign_child_resource( - plate, - location=self.plate_locations[plate_name] - ) - - # 步骤 4: 为 liaopan1 添加初始极片 + # ====================================== 物料板 ============================================ + # 创建6个4*4的物料板 + liaopan1 = MaterialPlate(name="liaopan1", size_x=120, size_y=100, size_z=10.0, fill=True) + self.assign_child_resource(liaopan1, Coordinate(x=1010, y=50, z=0)) for i in range(16): - jipian = ElectrodeSheet( - name=f"jipian1_{i}", - size_x=12, - size_y=12, - size_z=0.1 - ) - self.plates["liaopan1"].children[i].assign_child_resource( - jipian, - location=None - ) + jipian_1 = ElectrodeSheet(name=f"{liaopan1.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + liaopan1.children[i].assign_child_resource(jipian_1, location=None) + + liaopan2 = MaterialPlate(name="liaopan2", size_x=120, size_y=100, size_z=10.0, fill=True) + self.assign_child_resource(liaopan2, Coordinate(x=1130, y=50, z=0)) + + liaopan3 = MaterialPlate(name="liaopan3", size_x=120, size_y=100, size_z=10.0, fill=True) + self.assign_child_resource(liaopan3, Coordinate(x=1250, y=50, z=0)) + + liaopan4 = MaterialPlate(name="liaopan4", size_x=120, size_y=100, size_z=10.0, fill=True) + self.assign_child_resource(liaopan4, Coordinate(x=1010, y=150, z=0)) + for i in range(16): + jipian_4 = ElectrodeSheet(name=f"{liaopan4.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + liaopan4.children[i].assign_child_resource(jipian_4, location=None) + + liaopan5 = MaterialPlate(name="liaopan5", size_x=120, size_y=100, size_z=10.0, fill=True) + self.assign_child_resource(liaopan5, Coordinate(x=1130, y=150, z=0)) + + liaopan6 = MaterialPlate(name="liaopan6", size_x=120, size_y=100, size_z=10.0, fill=True) + self.assign_child_resource(liaopan6, Coordinate(x=1250, y=150, z=0)) + + # ====================================== 瓶架、移液枪 ============================================ + # 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒 + bottle_rack_3x4 = BottleRack( + name="bottle_rack_3x4", + size_x=210.0, + size_y=140.0, + size_z=100.0, + num_items_x=3, + num_items_y=4, + position_spacing=35.0, + orientation="vertical", + ) + self.assign_child_resource(bottle_rack_3x4, Coordinate(x=100, y=200, z=0)) + + bottle_rack_6x2 = BottleRack( + name="bottle_rack_6x2", + size_x=120.0, + size_y=250.0, + size_z=100.0, + num_items_x=6, + num_items_y=2, + position_spacing=35.0, + orientation="vertical", + ) + self.assign_child_resource(bottle_rack_6x2, Coordinate(x=300, y=300, z=0)) + + bottle_rack_6x2_2 = BottleRack( + name="bottle_rack_6x2_2", + size_x=120.0, + size_y=250.0, + size_z=100.0, + num_items_x=6, + num_items_y=2, + position_spacing=35.0, + orientation="vertical", + ) + self.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=430, y=300, z=0)) + + # 将 ElectrodeSheet 放满 3x4 与 6x2 的所有孔位 + for idx in range(bottle_rack_3x4.num_items_x * bottle_rack_3x4.num_items_y): + sheet = ElectrodeSheet(name=f"sheet_3x4_{idx}", size_x=12, size_y=12, size_z=0.1) + bottle_rack_3x4.assign_child_resource(sheet, index=idx) + + for idx in range(bottle_rack_6x2.num_items_x * bottle_rack_6x2.num_items_y): + sheet = ElectrodeSheet(name=f"sheet_6x2_{idx}", size_x=12, size_y=12, size_z=0.1) + bottle_rack_6x2.assign_child_resource(sheet, index=idx) + + tip_box = TipBox64(name="tip_box_64") + self.assign_child_resource(tip_box, Coordinate(x=300, y=100, z=0)) + + waste_tip_box = WasteTipBox(name="waste_tip_box") + self.assign_child_resource(waste_tip_box, Coordinate(x=300, y=200, z=0)) + + print(self) def create_coin_cell_deck(name: str = "coin_cell_deck", size_x: float = 1000.0, size_y: float = 1000.0, size_z: float = 900.0) -> CoincellDeck: @@ -971,6 +1155,11 @@ def create_coin_cell_deck(name: str = "coin_cell_deck", size_x: float = 1000.0, Returns: 已配置好的 CoincellDeck 对象 """ - deck = CoincellDeck(name=name, size_x=size_x, size_y=size_y, size_z=size_z) - deck.setup() + # 创建 CoincellDeck 实例并自动执行 setup 配置 + deck = CoincellDeck(name=name, size_x=size_x, size_y=size_y, size_z=size_z, setup=True) return deck + + +if __name__ == "__main__": + deck = create_coin_cell_deck() + print(deck) \ No newline at end of file From cd12932788b57ebceb821f419cdbf54f321b166d Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Mon, 3 Nov 2025 22:53:37 +0800 Subject: [PATCH 050/104] 1103byxinyu --- .../workstation/coin_cell_assembly/YB_YH_materials.py | 8 ++++---- .../workstation/coin_cell_assembly/coin_cell_assembly.py | 4 ++-- .../workstation/coin_cell_assembly/new_cellconfig3c.json | 5 +++-- unilabos/registry/devices/bioyond_cell.yaml | 1 + unilabos/resources/bioyond/decks.py | 1 - 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py index 5c3aa87..a2dff68 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py +++ b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py @@ -986,10 +986,10 @@ class CoincellDeck(Deck): def __init__( self, name: str = "coin_cell_deck", - size_x: float = 1000.0, # 1m - size_y: float = 1000.0, # 1m - size_z: float = 900.0, # 0.9m - origin: Coordinate = Coordinate(0, 0, 0), + size_x: float = 3650.0, # 1m + size_y: float = 1550.0, # 1m + size_z: float = 2100.0, # 0.9m + origin: Coordinate = Coordinate(-4000, 2000, 0), category: str = "coin_cell_deck", setup: bool = False, # 是否自动执行 setup ): diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index d968e99..752b3d5 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -112,7 +112,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): def __init__( self, deck: Deck=None, - address: str = "172.21.32.111", + address: str = "172.21.33.176", port: str = "502", debug_mode: bool = False, *args, @@ -138,7 +138,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): # 如果没有传入 deck,则创建标准配置的 deck if self.deck is None: - self.deck = CoincellDeck(size_x=1000, size_y=1000, size_z=900, setup=True) + self.deck = CoincellDeck(size_x=1000, size_y=1000, size_z=900, origin=Coordinate(-100, -100, 0),setup=True) else: # 如果传入了 deck 但还没有 setup,可以选择是否 setup if self.deck is not None and len(self.deck.children) == 0: diff --git a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json index 3619085..14e6a22 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json +++ b/unilabos/devices/workstation/coin_cell_assembly/new_cellconfig3c.json @@ -14,6 +14,7 @@ }, "data": {} }, + { "id": "BatteryStation", "name": "扣电工作站", @@ -24,8 +25,8 @@ "type": "device", "class": "coincellassemblyworkstation_device", "position": { - "x": 600, - "y": 400, + "x": -600, + "y": -400, "z": 0 }, "config": { diff --git a/unilabos/registry/devices/bioyond_cell.yaml b/unilabos/registry/devices/bioyond_cell.yaml index a9d754f..c6ad669 100644 --- a/unilabos/registry/devices/bioyond_cell.yaml +++ b/unilabos/registry/devices/bioyond_cell.yaml @@ -4,6 +4,7 @@ bioyond_cell: class: action_value_mappings: auto-auto_batch_outbound_from_xlsx: + display_name: 批量导入上料 feedback: {} goal: {} goal_default: diff --git a/unilabos/resources/bioyond/decks.py b/unilabos/resources/bioyond/decks.py index 5a08994..32642c3 100644 --- a/unilabos/resources/bioyond/decks.py +++ b/unilabos/resources/bioyond/decks.py @@ -36,7 +36,6 @@ class BIOYOND_PolymerReactionStation_Deck(Deck): for warehouse_name, warehouse in self.warehouses.items(): self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name]) - class BIOYOND_PolymerPreparationStation_Deck(Deck): def __init__( self, From 8009956326c41ea390cb223e47ceba0786584e32 Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Mon, 3 Nov 2025 23:48:40 +0800 Subject: [PATCH 051/104] =?UTF-8?q?=E6=9B=B4=E6=96=B0YB=E5=B7=A5=E7=AB=99d?= =?UTF-8?q?eck=E5=9D=90=E6=A0=87=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 根据实际布局图更新各堆栈的坐标位置 - 将粉末加样头堆栈拆分为左右两部分(10x1x1 -> 2个5x1x1) - 将试剂替换仓库拆分为左右两部分(10x1x1 -> 2个5x1x1) - 更新配液站内试剂仓库的坐标 - 所有坐标基于像素位置精确计算(deck原点: 206,446) --- unilabos/resources/bioyond/decks.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/unilabos/resources/bioyond/decks.py b/unilabos/resources/bioyond/decks.py index f15e065..94b6198 100644 --- a/unilabos/resources/bioyond/decks.py +++ b/unilabos/resources/bioyond/decks.py @@ -89,9 +89,11 @@ class BIOYOND_YB_Deck(Deck): "自动堆栈-右": bioyond_warehouse_2x2x1("自动堆栈-右"), "手动堆栈-左": bioyond_warehouse_3x5x1("手动堆栈-左"), "手动堆栈-右": bioyond_warehouse_3x5x1("手动堆栈-右"), - "粉末加样头堆栈": bioyond_warehouse_20x1x1("粉末加样头堆栈"), + "粉末加样头堆栈-左": bioyond_warehouse_10x1x1("粉末加样头堆栈-左"), + "粉末加样头堆栈-右": bioyond_warehouse_10x1x1("粉末加样头堆栈-右"), "配液站内试剂仓库": bioyond_warehouse_3x3x1("配液站内试剂仓库"), - "试剂替换仓库": bioyond_warehouse_10x1x1("试剂替换仓库"), + "试剂替换仓库-左": bioyond_warehouse_5x1x1("试剂替换仓库-左"), + "试剂替换仓库-右": bioyond_warehouse_5x1x1("试剂替换仓库-右"), } # warehouse 的位置 self.warehouse_locations = { @@ -99,9 +101,11 @@ class BIOYOND_YB_Deck(Deck): "自动堆栈-右": Coordinate(4160.0, 158.0, 0.0), "手动堆栈-左": Coordinate(-400.0, 877.0, 0.0), "手动堆栈-右": Coordinate(4160.0, 877.0, 0.0), - "粉末加样头堆栈": Coordinate(385.0, 1300.0, 0.0), - "配液站内试剂仓库": Coordinate(1164.0, 676.0, 0.0), - "试剂替换仓库": Coordinate(2717.0, 676.0, 0.0), + "粉末加样头堆栈-左": Coordinate(415.0, 1301.0, 0.0), + "粉末加样头堆栈-右": Coordinate(2200.0, 1304.0, 0.0), + "配液站内试剂仓库": Coordinate(2162.0, 337.0, 0.0), + "试剂替换仓库-左": Coordinate(1173.0, 702.0, 0.0), + "试剂替换仓库-右": Coordinate(2721.0, 739.0, 0.0), } for warehouse_name, warehouse in self.warehouses.items(): From bfd415279b09038790507b85834d0f1e001405f1 Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Tue, 4 Nov 2025 00:39:39 +0800 Subject: [PATCH 052/104] Update YB_YH_materials.py --- .../coin_cell_assembly/YB_YH_materials.py | 310 ++++++++++++------ 1 file changed, 210 insertions(+), 100 deletions(-) diff --git a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py index a2dff68..04cb36d 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py +++ b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py @@ -755,7 +755,7 @@ class BottleRack(Resource): size_z: float, category: str = "bottle_rack", model: Optional[str] = None, - num_items_x: int = 3, + num_items_x: int = 2, num_items_y: int = 4, position_spacing: float = 35.0, orientation: str = "horizontal", @@ -971,6 +971,7 @@ class ClipMagazine_four(ItemizedResource[ClipMagazineHole]): self.hole_diameter = hole_diameter self.hole_depth = hole_depth self.max_sheets_per_hole = max_sheets_per_hole + def serialize(self) -> dict: return { @@ -980,6 +981,141 @@ class ClipMagazine_four(ItemizedResource[ClipMagazineHole]): "max_sheets_per_hole": self.max_sheets_per_hole, } +class ClipMagazine_two(ItemizedResource[ClipMagazineHole]): + """子弹夹类 - 有2个洞位,每个洞位放多个极片""" + children: List[ClipMagazineHole] + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + hole_diameter: float = 14.0, + hole_depth: float = 10.0, + hole_spacing: float = 25.0, + max_sheets_per_hole: int = 100, + category: str = "clip_magazine_four", + model: Optional[str] = None, + ): + """初始化子弹夹 + + Args: + name: 子弹夹名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + hole_diameter: 洞直径 (mm) + hole_depth: 洞深度 (mm) + hole_spacing: 洞位间距 (mm) + max_sheets_per_hole: 每个洞位最大极片数量 + category: 类别 + model: 型号 + """ + # 创建4个洞位,排成2x2布局 + holes = create_ordered_items_2d( + klass=ClipMagazineHole, + num_items_x=1, + num_items_y=2, + dx=(size_x - 2 * hole_spacing) / 2, # 居中 + dy=(size_y - hole_spacing) / 2, # 居中 + dz=size_z - 0, + item_dx=hole_spacing, + item_dy=hole_spacing, + diameter=hole_diameter, + depth=hole_depth, + ) + + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=holes, + category=category, + model=model, + ) + + # 保存洞位的直径和深度 + self.hole_diameter = hole_diameter + self.hole_depth = hole_depth + self.max_sheets_per_hole = max_sheets_per_hole + + + def serialize(self) -> dict: + return { + **super().serialize(), + "hole_diameter": self.hole_diameter, + "hole_depth": self.hole_depth, + "max_sheets_per_hole": self.max_sheets_per_hole, + } +class ClipMagazine_one(ItemizedResource[ClipMagazineHole]): + """子弹夹类 - 有1个洞位,每个洞位放多个极片""" + children: List[ClipMagazineHole] + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + hole_diameter: float = 14.0, + hole_depth: float = 10.0, + hole_spacing: float = 25.0, + max_sheets_per_hole: int = 100, + category: str = "clip_magazine_four", + model: Optional[str] = None, + ): + """初始化子弹夹 + + Args: + name: 子弹夹名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + hole_diameter: 洞直径 (mm) + hole_depth: 洞深度 (mm) + hole_spacing: 洞位间距 (mm) + max_sheets_per_hole: 每个洞位最大极片数量 + category: 类别 + model: 型号 + """ + # 创建4个洞位,排成2x2布局 + holes = create_ordered_items_2d( + klass=ClipMagazineHole, + num_items_x=1, + num_items_y=1, + dx=(size_x - 2 * hole_spacing) / 2, # 居中 + dy=(size_y - hole_spacing) / 2, # 居中 + dz=size_z - 0, + item_dx=hole_spacing, + item_dy=hole_spacing, + diameter=hole_diameter, + depth=hole_depth, + ) + + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=holes, + category=category, + model=model, + ) + + # 保存洞位的直径和深度 + self.hole_diameter = hole_diameter + self.hole_depth = hole_depth + self.max_sheets_per_hole = max_sheets_per_hole + + + def serialize(self) -> dict: + return { + **super().serialize(), + "hole_diameter": self.hole_diameter, + "hole_depth": self.hole_depth, + "max_sheets_per_hole": self.max_sheets_per_hole, + } + class CoincellDeck(Deck): """纽扣电池组装工作站台面类""" @@ -1018,148 +1154,122 @@ class CoincellDeck(Deck): def setup(self) -> None: """设置工作站的标准布局 - 包含子弹夹、料盘、瓶架等完整配置""" # ====================================== 子弹夹 ============================================ - zip_dan_jia = ClipMagazine_four("zi_dan_jia", 80, 80, 10) - self.assign_child_resource(zip_dan_jia, Coordinate(x=1400, y=50, z=0)) - zip_dan_jia2 = ClipMagazine_four("zi_dan_jia2", 80, 80, 10) - self.assign_child_resource(zip_dan_jia2, Coordinate(x=1600, y=200, z=0)) - zip_dan_jia3 = ClipMagazine("zi_dan_jia3", 80, 80, 10) - self.assign_child_resource(zip_dan_jia3, Coordinate(x=1500, y=200, z=0)) - zip_dan_jia4 = ClipMagazine("zi_dan_jia4", 80, 80, 10) - self.assign_child_resource(zip_dan_jia4, Coordinate(x=1500, y=300, z=0)) - zip_dan_jia5 = ClipMagazine("zi_dan_jia5", 80, 80, 10) - self.assign_child_resource(zip_dan_jia5, Coordinate(x=1600, y=300, z=0)) - zip_dan_jia6 = ClipMagazine("zi_dan_jia6", 80, 80, 10) - self.assign_child_resource(zip_dan_jia6, Coordinate(x=1530, y=500, z=0)) - zip_dan_jia7 = ClipMagazine("zi_dan_jia7", 80, 80, 10) - self.assign_child_resource(zip_dan_jia7, Coordinate(x=1180, y=400, z=0)) - zip_dan_jia8 = ClipMagazine("zi_dan_jia8", 80, 80, 10) - self.assign_child_resource(zip_dan_jia8, Coordinate(x=1280, y=400, z=0)) + # 铝箔(1) + lvbo_zip = ClipMagazine_one("lvbo_zip", 80, 80, 10) + self.assign_child_resource(CoincellDeck, Coordinate(x=1400, y=50, z=0)) + #正极(234) + zhengji_zip = ClipMagazine_four("zhengji_zip", 80, 80, 10) + self.assign_child_resource(CoincellDeck, Coordinate(x=1400, y=50, z=0)) + #2 正极壳 + zhengjike_zip = ClipMagazine_four("zhengjike_zip", 80, 80, 10) + self.assign_child_resource(CoincellDeck, Coordinate(x=1600, y=200, z=0)) + # 垫片 + danpian_zip = ClipMagazine_two("danpian_zip", 80, 80, 10) + self.assign_child_resource(CoincellDeck, Coordinate(x=1500, y=200, z=0)) + #2 负极壳 + zhengjike_zip = ClipMagazine_four("zhengjike_zip", 80, 80, 10) + self.assign_child_resource(CoincellDeck, Coordinate(x=1600, y=200, z=0)) + # 弹片 + tanpian_zip = ClipMagazine_two("tantanpian_zippian", 80, 80, 10) + self.assign_child_resource(CoincellDeck, Coordinate(x=1500, y=300, z=0)) + #3成品弹夹 + chengpindanjia_zip = ClipMagazine("chengpindanjia_zip", 80, 80, 10) + self.assign_child_resource(CoincellDeck, Coordinate(x=1500, y=200, z=0)) # 为子弹夹添加极片 - for i in range(4): - jipian = ElectrodeSheet(name=f"zi_dan_jia_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia2.children[i].assign_child_resource(jipian, location=None) - for i in range(4): - jipian2 = ElectrodeSheet(name=f"zi_dan_jia2_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia.children[i].assign_child_resource(jipian2, location=None) - for i in range(6): - jipian3 = ElectrodeSheet(name=f"zi_dan_jia3_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia3.children[i].assign_child_resource(jipian3, location=None) - for i in range(6): - jipian4 = ElectrodeSheet(name=f"zi_dan_jia4_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia4.children[i].assign_child_resource(jipian4, location=None) - for i in range(6): - jipian5 = ElectrodeSheet(name=f"zi_dan_jia5_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia5.children[i].assign_child_resource(jipian5, location=None) - for i in range(6): - jipian6 = ElectrodeSheet(name=f"zi_dan_jia6_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia6.children[i].assign_child_resource(jipian6, location=None) - for i in range(6): - jipian7 = ElectrodeSheet(name=f"zi_dan_jia7_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia7.children[i].assign_child_resource(jipian7, location=None) - for i in range(6): - jipian8 = ElectrodeSheet(name=f"zi_dan_jia8_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - zip_dan_jia8.children[i].assign_child_resource(jipian8, location=None) + for i in range(10): + lvbo = ElectrodeSheet(name=f"lvbo{i}", size_x=12, size_y=12, size_z=0.1) + lvbo_zip.children[i].assign_child_resource(lvbo, location=None) + for i in range(10): + zhengji = ElectrodeSheet(name=f"zhengji_{i}", size_x=12, size_y=12, size_z=0.1) + zhengji_zip.children[i].assign_child_resource(zhengji, location=None) + for i in range(10): + zhengjike = ElectrodeSheet(name=f"zhengjike_{i}", size_x=12, size_y=12, size_z=0.1) + zhengjike_zip.children[i].assign_child_resource(zhengjike, location=None) + for i in range(10): + danpian = ElectrodeSheet(name=f"danpian_{i}", size_x=12, size_y=12, size_z=0.1) + danpian_zip.children[i].assign_child_resource(danpian, location=None) + for i in range(10): + zhengjike = ElectrodeSheet(name=f"zhengjike_{i}", size_x=12, size_y=12, size_z=0.1) + zhengjike_zip.children[i].assign_child_resource(zhengjike, location=None) + for i in range(10): + tanpian = ElectrodeSheet(name=f"tanpian_{i}", size_x=12, size_y=12, size_z=0.1) + tanpian_zip.children[i].assign_child_resource(tanpian, location=None) + for i in range(10): + chengpindanjia = ElectrodeSheet(name=f"chengpindanjia_{i}", size_x=12, size_y=12, size_z=0.1) + chengpindanjia_zip.children[i].assign_child_resource(chengpindanjia, location=None) + # ====================================== 物料板 ============================================ - # 创建6个4*4的物料板 - liaopan1 = MaterialPlate(name="liaopan1", size_x=120, size_y=100, size_z=10.0, fill=True) - self.assign_child_resource(liaopan1, Coordinate(x=1010, y=50, z=0)) - for i in range(16): - jipian_1 = ElectrodeSheet(name=f"{liaopan1.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - liaopan1.children[i].assign_child_resource(jipian_1, location=None) + # 创建6个4*4的物料板(料盘carrier) + fujiliaopan = MaterialPlate(name="fujiliaopan", size_x=120, size_y=100, size_z=10.0, fill=True) + self.assign_child_resource(CoincellDeck, Coordinate(x=1010, y=50, z=0)) + for i in range(8): + fujipian = ElectrodeSheet(name=f"{fujiliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + fujiliaopan.children[i].assign_child_resource(fujipian, location=None) - liaopan2 = MaterialPlate(name="liaopan2", size_x=120, size_y=100, size_z=10.0, fill=True) - self.assign_child_resource(liaopan2, Coordinate(x=1130, y=50, z=0)) + gemoliaopan = MaterialPlate(name="gemoliaopan", size_x=120, size_y=100, size_z=10.0, fill=True) + self.assign_child_resource(CoincellDeck, Coordinate(x=1130, y=50, z=0)) + for i in range(8): + gemopian = ElectrodeSheet(name=f"{gemoliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + gemoliaopan.children[i].assign_child_resource(gemopian, location=None) - liaopan3 = MaterialPlate(name="liaopan3", size_x=120, size_y=100, size_z=10.0, fill=True) - self.assign_child_resource(liaopan3, Coordinate(x=1250, y=50, z=0)) - - liaopan4 = MaterialPlate(name="liaopan4", size_x=120, size_y=100, size_z=10.0, fill=True) - self.assign_child_resource(liaopan4, Coordinate(x=1010, y=150, z=0)) - for i in range(16): - jipian_4 = ElectrodeSheet(name=f"{liaopan4.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - liaopan4.children[i].assign_child_resource(jipian_4, location=None) - - liaopan5 = MaterialPlate(name="liaopan5", size_x=120, size_y=100, size_z=10.0, fill=True) - self.assign_child_resource(liaopan5, Coordinate(x=1130, y=150, z=0)) - - liaopan6 = MaterialPlate(name="liaopan6", size_x=120, size_y=100, size_z=10.0, fill=True) - self.assign_child_resource(liaopan6, Coordinate(x=1250, y=150, z=0)) - # ====================================== 瓶架、移液枪 ============================================ # 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒 - bottle_rack_3x4 = BottleRack( + # 奔耀上料5ml分液瓶小板 + bottle_rack_2x4 = BottleRack( name="bottle_rack_3x4", size_x=210.0, size_y=140.0, size_z=100.0, - num_items_x=3, + num_items_x=2, num_items_y=4, position_spacing=35.0, orientation="vertical", ) - self.assign_child_resource(bottle_rack_3x4, Coordinate(x=100, y=200, z=0)) - - bottle_rack_6x2 = BottleRack( + self.assign_child_resource(bottle_rack_2x4, Coordinate(x=100, y=200, z=0)) + # 电解液缓存位2x6 + bottle_rack_2x6 = BottleRack( name="bottle_rack_6x2", size_x=120.0, size_y=250.0, size_z=100.0, - num_items_x=6, - num_items_y=2, + num_items_x=2, + num_items_y=6, position_spacing=35.0, orientation="vertical", ) - self.assign_child_resource(bottle_rack_6x2, Coordinate(x=300, y=300, z=0)) - - bottle_rack_6x2_2 = BottleRack( + self.assign_child_resource(bottle_rack_2x6, Coordinate(x=300, y=300, z=0)) + # 电解液回收位2x6 + bottle_rack_2x6_2 = BottleRack( name="bottle_rack_6x2_2", size_x=120.0, size_y=250.0, size_z=100.0, - num_items_x=6, - num_items_y=2, + num_items_x=2, + num_items_y=6, position_spacing=35.0, orientation="vertical", ) - self.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=430, y=300, z=0)) + self.assign_child_resource(bottle_rack_2x6_2, Coordinate(x=430, y=300, z=0)) # 将 ElectrodeSheet 放满 3x4 与 6x2 的所有孔位 - for idx in range(bottle_rack_3x4.num_items_x * bottle_rack_3x4.num_items_y): + for idx in range(bottle_rack_2x4.num_items_x * bottle_rack_2x4.num_items_y): sheet = ElectrodeSheet(name=f"sheet_3x4_{idx}", size_x=12, size_y=12, size_z=0.1) - bottle_rack_3x4.assign_child_resource(sheet, index=idx) + bottle_rack_2x4.assign_child_resource(sheet, index=idx) - for idx in range(bottle_rack_6x2.num_items_x * bottle_rack_6x2.num_items_y): + for idx in range(bottle_rack_2x6.num_items_x * bottle_rack_2x6.num_items_y): sheet = ElectrodeSheet(name=f"sheet_6x2_{idx}", size_x=12, size_y=12, size_z=0.1) - bottle_rack_6x2.assign_child_resource(sheet, index=idx) + bottle_rack_2x6.assign_child_resource(sheet, index=idx) tip_box = TipBox64(name="tip_box_64") self.assign_child_resource(tip_box, Coordinate(x=300, y=100, z=0)) waste_tip_box = WasteTipBox(name="waste_tip_box") self.assign_child_resource(waste_tip_box, Coordinate(x=300, y=200, z=0)) - - print(self) -def create_coin_cell_deck(name: str = "coin_cell_deck", size_x: float = 1000.0, size_y: float = 1000.0, size_z: float = 900.0) -> CoincellDeck: - """创建并配置标准的纽扣电池组装工作站台面 - - Args: - name: 台面名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - - Returns: - 已配置好的 CoincellDeck 对象 - """ - # 创建 CoincellDeck 实例并自动执行 setup 配置 - deck = CoincellDeck(name=name, size_x=size_x, size_y=size_y, size_z=size_z, setup=True) - return deck - if __name__ == "__main__": - deck = create_coin_cell_deck() + deck = CoincellDeck(setup=True) print(deck) \ No newline at end of file From 1477384c1aa31370d889e1e82b39298f47b79c81 Mon Sep 17 00:00:00 2001 From: calvincao Date: Tue, 4 Nov 2025 01:19:42 +0800 Subject: [PATCH 053/104] Update CoinCellAssembly and YB_YH_materials configurations - Adjusted CoincellDeck dimensions and origin coordinates for improved layout. - Replaced CoincellDeck references with specific ClipMagazine instances in YB_YH_materials.py. - Updated BottleRack configurations to reflect new item arrangements and dimensions. --- .../coin_cell_assembly/YB_YH_materials.py | 52 +++++++++---------- .../coin_cell_assembly/coin_cell_assembly.py | 2 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py index 04cb36d..7d26a15 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py +++ b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py @@ -1156,46 +1156,46 @@ class CoincellDeck(Deck): # ====================================== 子弹夹 ============================================ # 铝箔(1) lvbo_zip = ClipMagazine_one("lvbo_zip", 80, 80, 10) - self.assign_child_resource(CoincellDeck, Coordinate(x=1400, y=50, z=0)) + self.assign_child_resource(lvbo_zip, Coordinate(x=1400, y=50, z=0)) #正极(234) zhengji_zip = ClipMagazine_four("zhengji_zip", 80, 80, 10) - self.assign_child_resource(CoincellDeck, Coordinate(x=1400, y=50, z=0)) + self.assign_child_resource(zhengji_zip, Coordinate(x=1400, y=50, z=0)) #2 正极壳 zhengjike_zip = ClipMagazine_four("zhengjike_zip", 80, 80, 10) - self.assign_child_resource(CoincellDeck, Coordinate(x=1600, y=200, z=0)) + self.assign_child_resource(zhengjike_zip, Coordinate(x=1600, y=200, z=0)) # 垫片 danpian_zip = ClipMagazine_two("danpian_zip", 80, 80, 10) - self.assign_child_resource(CoincellDeck, Coordinate(x=1500, y=200, z=0)) + self.assign_child_resource(danpian_zip, Coordinate(x=1500, y=200, z=0)) #2 负极壳 - zhengjike_zip = ClipMagazine_four("zhengjike_zip", 80, 80, 10) - self.assign_child_resource(CoincellDeck, Coordinate(x=1600, y=200, z=0)) + fujike_zip = ClipMagazine_four("fujike_zip", 80, 80, 10) + self.assign_child_resource(fujike_zip, Coordinate(x=1600, y=200, z=0)) # 弹片 tanpian_zip = ClipMagazine_two("tantanpian_zippian", 80, 80, 10) - self.assign_child_resource(CoincellDeck, Coordinate(x=1500, y=300, z=0)) + self.assign_child_resource(tanpian_zip, Coordinate(x=1500, y=300, z=0)) #3成品弹夹 chengpindanjia_zip = ClipMagazine("chengpindanjia_zip", 80, 80, 10) - self.assign_child_resource(CoincellDeck, Coordinate(x=1500, y=200, z=0)) + self.assign_child_resource(chengpindanjia_zip, Coordinate(x=1500, y=200, z=0)) # 为子弹夹添加极片 - for i in range(10): + for i in range(1): # ClipMagazine_one 有1个洞位 lvbo = ElectrodeSheet(name=f"lvbo{i}", size_x=12, size_y=12, size_z=0.1) lvbo_zip.children[i].assign_child_resource(lvbo, location=None) - for i in range(10): + for i in range(4): # ClipMagazine_four 有4个洞位 zhengji = ElectrodeSheet(name=f"zhengji_{i}", size_x=12, size_y=12, size_z=0.1) zhengji_zip.children[i].assign_child_resource(zhengji, location=None) - for i in range(10): + for i in range(4): # ClipMagazine_four 有4个洞位 zhengjike = ElectrodeSheet(name=f"zhengjike_{i}", size_x=12, size_y=12, size_z=0.1) zhengjike_zip.children[i].assign_child_resource(zhengjike, location=None) - for i in range(10): + for i in range(2): # ClipMagazine_two 有2个洞位 danpian = ElectrodeSheet(name=f"danpian_{i}", size_x=12, size_y=12, size_z=0.1) danpian_zip.children[i].assign_child_resource(danpian, location=None) - for i in range(10): - zhengjike = ElectrodeSheet(name=f"zhengjike_{i}", size_x=12, size_y=12, size_z=0.1) - zhengjike_zip.children[i].assign_child_resource(zhengjike, location=None) - for i in range(10): + for i in range(4): # ClipMagazine_four 有4个洞位 + fujike = ElectrodeSheet(name=f"fujike_{i}", size_x=12, size_y=12, size_z=0.1) + fujike_zip.children[i].assign_child_resource(fujike, location=None) + for i in range(2): # ClipMagazine_two 有2个洞位 tanpian = ElectrodeSheet(name=f"tanpian_{i}", size_x=12, size_y=12, size_z=0.1) tanpian_zip.children[i].assign_child_resource(tanpian, location=None) - for i in range(10): + for i in range(6): # ClipMagazine 有6个洞位 chengpindanjia = ElectrodeSheet(name=f"chengpindanjia_{i}", size_x=12, size_y=12, size_z=0.1) chengpindanjia_zip.children[i].assign_child_resource(chengpindanjia, location=None) @@ -1203,13 +1203,13 @@ class CoincellDeck(Deck): # ====================================== 物料板 ============================================ # 创建6个4*4的物料板(料盘carrier) fujiliaopan = MaterialPlate(name="fujiliaopan", size_x=120, size_y=100, size_z=10.0, fill=True) - self.assign_child_resource(CoincellDeck, Coordinate(x=1010, y=50, z=0)) + self.assign_child_resource(fujiliaopan, Coordinate(x=1010, y=50, z=0)) for i in range(8): fujipian = ElectrodeSheet(name=f"{fujiliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) fujiliaopan.children[i].assign_child_resource(fujipian, location=None) gemoliaopan = MaterialPlate(name="gemoliaopan", size_x=120, size_y=100, size_z=10.0, fill=True) - self.assign_child_resource(CoincellDeck, Coordinate(x=1130, y=50, z=0)) + self.assign_child_resource(gemoliaopan, Coordinate(x=1130, y=50, z=0)) for i in range(8): gemopian = ElectrodeSheet(name=f"{gemoliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) gemoliaopan.children[i].assign_child_resource(gemopian, location=None) @@ -1228,26 +1228,26 @@ class CoincellDeck(Deck): orientation="vertical", ) self.assign_child_resource(bottle_rack_2x4, Coordinate(x=100, y=200, z=0)) - # 电解液缓存位2x6 - bottle_rack_2x6 = BottleRack( + # 电解液缓存位6x2 + bottle_rack_6x2 = BottleRack( name="bottle_rack_6x2", size_x=120.0, size_y=250.0, size_z=100.0, - num_items_x=2, - num_items_y=6, + num_items_x=6, + num_items_y=2, position_spacing=35.0, orientation="vertical", ) self.assign_child_resource(bottle_rack_2x6, Coordinate(x=300, y=300, z=0)) - # 电解液回收位2x6 + # 电解液回收位6x2 bottle_rack_2x6_2 = BottleRack( name="bottle_rack_6x2_2", size_x=120.0, size_y=250.0, size_z=100.0, - num_items_x=2, - num_items_y=6, + num_items_x=6, + num_items_y=2, position_spacing=35.0, orientation="vertical", ) diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index 752b3d5..65d18ba 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -138,7 +138,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): # 如果没有传入 deck,则创建标准配置的 deck if self.deck is None: - self.deck = CoincellDeck(size_x=1000, size_y=1000, size_z=900, origin=Coordinate(-100, -100, 0),setup=True) + self.deck = CoincellDeck(size_x=3650, size_y=1550, size_z=2100, origin=Coordinate(-2000, 100, 0),setup=True) else: # 如果传入了 deck 但还没有 setup,可以选择是否 setup if self.deck is not None and len(self.deck.children) == 0: From 0ad2eaafeaea39422e4ab219799320c5127cb767 Mon Sep 17 00:00:00 2001 From: calvincao Date: Tue, 4 Nov 2025 01:57:30 +0800 Subject: [PATCH 054/104] Fix BottleRack references in CoincellDeck setup - Updated references from bottle_rack_2x6 to bottle_rack_6x2 to align with the new configuration. - Adjusted the loop for assigning ElectrodeSheets to use the correct BottleRack dimensions. --- .../workstation/coin_cell_assembly/YB_YH_materials.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py index 7d26a15..3860135 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py +++ b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py @@ -1239,7 +1239,7 @@ class CoincellDeck(Deck): position_spacing=35.0, orientation="vertical", ) - self.assign_child_resource(bottle_rack_2x6, Coordinate(x=300, y=300, z=0)) + self.assign_child_resource(bottle_rack_6x2, Coordinate(x=300, y=300, z=0)) # 电解液回收位6x2 bottle_rack_2x6_2 = BottleRack( name="bottle_rack_6x2_2", @@ -1258,9 +1258,9 @@ class CoincellDeck(Deck): sheet = ElectrodeSheet(name=f"sheet_3x4_{idx}", size_x=12, size_y=12, size_z=0.1) bottle_rack_2x4.assign_child_resource(sheet, index=idx) - for idx in range(bottle_rack_2x6.num_items_x * bottle_rack_2x6.num_items_y): + for idx in range(bottle_rack_6x2.num_items_x * bottle_rack_6x2.num_items_y): sheet = ElectrodeSheet(name=f"sheet_6x2_{idx}", size_x=12, size_y=12, size_z=0.1) - bottle_rack_2x6.assign_child_resource(sheet, index=idx) + bottle_rack_6x2.assign_child_resource(sheet, index=idx) tip_box = TipBox64(name="tip_box_64") self.assign_child_resource(tip_box, Coordinate(x=300, y=100, z=0)) From 16ad4bbecc8197493c0d1d85e651e68931db481a Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Tue, 4 Nov 2025 02:01:44 +0800 Subject: [PATCH 055/104] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=A5=94=E8=80=80?= =?UTF-8?q?=E5=92=8C=E4=BE=9D=E5=8D=8E=E5=B7=A5=E7=AB=99=E7=9A=84Deck?= =?UTF-8?q?=E5=9D=90=E6=A0=87=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新奔耀YB工站deck坐标(基于图片像素精确计算) * 将粉末加样头堆栈拆分为左右两部分 * 将试剂替换仓库拆分为左右两部分 * 更新所有堆栈的坐标位置 - 更新依华扣电工站deck坐标(使用精确的像素-毫米转换) * 修正所有子弹夹的坐标位置(铝箔、正极片、正极壳等) * 更新料盘坐标(负极料盘、隔膜料盘) * 更新瓶架坐标(奔耀上料瓶架、电解液缓存位、回收位) * 更新枪头盒和废枪头盒坐标 * 确保所有坐标在deck范围内(3650×1550mm) - 转换比例说明: * 奔耀工站:deck左上角(206,446),使用1.56mm/像素 * 依华工站:deck左上角(494,444)到右下角(2430,1608) X方向:1.885mm/像素,Y方向:1.332mm/像素 --- .../coin_cell_assembly/YB_YH_materials.py | 74 +++++++++++-------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py index 7d26a15..adb25cf 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py +++ b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py @@ -1154,31 +1154,37 @@ class CoincellDeck(Deck): def setup(self) -> None: """设置工作站的标准布局 - 包含子弹夹、料盘、瓶架等完整配置""" # ====================================== 子弹夹 ============================================ - # 铝箔(1) + # 铝箔(1个洞位) lvbo_zip = ClipMagazine_one("lvbo_zip", 80, 80, 10) - self.assign_child_resource(lvbo_zip, Coordinate(x=1400, y=50, z=0)) - #正极(234) + self.assign_child_resource(lvbo_zip, Coordinate(x=2737.0, y=301.0, z=0)) + + # 正极片(4个洞位,2x2布局) zhengji_zip = ClipMagazine_four("zhengji_zip", 80, 80, 10) - self.assign_child_resource(zhengji_zip, Coordinate(x=1400, y=50, z=0)) - #2 正极壳 + self.assign_child_resource(zhengji_zip, Coordinate(x=2799.0, y=356.0, z=0)) + + # 正极壳(4个洞位,2x2布局) zhengjike_zip = ClipMagazine_four("zhengjike_zip", 80, 80, 10) - self.assign_child_resource(zhengjike_zip, Coordinate(x=1600, y=200, z=0)) - # 垫片 + self.assign_child_resource(zhengjike_zip, Coordinate(x=2586.0, y=1143.0, z=0)) + + # 垫片(2个洞位,1x2布局) danpian_zip = ClipMagazine_two("danpian_zip", 80, 80, 10) - self.assign_child_resource(danpian_zip, Coordinate(x=1500, y=200, z=0)) - #2 负极壳 + self.assign_child_resource(danpian_zip, Coordinate(x=2690.0, y=1141.0, z=0)) + + # 负极壳(4个洞位,2x2布局) fujike_zip = ClipMagazine_four("fujike_zip", 80, 80, 10) - self.assign_child_resource(fujike_zip, Coordinate(x=1600, y=200, z=0)) - # 弹片 - tanpian_zip = ClipMagazine_two("tantanpian_zippian", 80, 80, 10) - self.assign_child_resource(tanpian_zip, Coordinate(x=1500, y=300, z=0)) - #3成品弹夹 + self.assign_child_resource(fujike_zip, Coordinate(x=2492.0, y=1144.0, z=0)) + + # 弹片(2个洞位,1x2布局) + tanpian_zip = ClipMagazine_two("tanpian_zip", 80, 80, 10) + self.assign_child_resource(tanpian_zip, Coordinate(x=2492.0, y=1139.0, z=0)) + + # 成品弹夹(6个洞位,3x2布局) chengpindanjia_zip = ClipMagazine("chengpindanjia_zip", 80, 80, 10) - self.assign_child_resource(chengpindanjia_zip, Coordinate(x=1500, y=200, z=0)) + self.assign_child_resource(chengpindanjia_zip, Coordinate(x=3112.0, y=1295.0, z=0)) # 为子弹夹添加极片 for i in range(1): # ClipMagazine_one 有1个洞位 - lvbo = ElectrodeSheet(name=f"lvbo{i}", size_x=12, size_y=12, size_z=0.1) + lvbo = ElectrodeSheet(name=f"lvbo_{i}", size_x=12, size_y=12, size_z=0.1) lvbo_zip.children[i].assign_child_resource(lvbo, location=None) for i in range(4): # ClipMagazine_four 有4个洞位 zhengji = ElectrodeSheet(name=f"zhengji_{i}", size_x=12, size_y=12, size_z=0.1) @@ -1201,22 +1207,24 @@ class CoincellDeck(Deck): # ====================================== 物料板 ============================================ - # 创建6个4*4的物料板(料盘carrier) + # 创建物料板(料盘carrier)- 4x4布局 + # 负极料盘 fujiliaopan = MaterialPlate(name="fujiliaopan", size_x=120, size_y=100, size_z=10.0, fill=True) - self.assign_child_resource(fujiliaopan, Coordinate(x=1010, y=50, z=0)) - for i in range(8): + self.assign_child_resource(fujiliaopan, Coordinate(x=2107.0, y=304.0, z=0)) + for i in range(16): fujipian = ElectrodeSheet(name=f"{fujiliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) fujiliaopan.children[i].assign_child_resource(fujipian, location=None) + # 隔膜料盘 gemoliaopan = MaterialPlate(name="gemoliaopan", size_x=120, size_y=100, size_z=10.0, fill=True) - self.assign_child_resource(gemoliaopan, Coordinate(x=1130, y=50, z=0)) - for i in range(8): + self.assign_child_resource(gemoliaopan, Coordinate(x=2107.0, y=146.0, z=0)) + for i in range(16): gemopian = ElectrodeSheet(name=f"{gemoliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) gemoliaopan.children[i].assign_child_resource(gemopian, location=None) # ====================================== 瓶架、移液枪 ============================================ # 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒 - # 奔耀上料5ml分液瓶小板 + # 奔耀上料5ml分液瓶小板 - 2x4布局 bottle_rack_2x4 = BottleRack( name="bottle_rack_3x4", size_x=210.0, @@ -1227,8 +1235,9 @@ class CoincellDeck(Deck): position_spacing=35.0, orientation="vertical", ) - self.assign_child_resource(bottle_rack_2x4, Coordinate(x=100, y=200, z=0)) - # 电解液缓存位6x2 + self.assign_child_resource(bottle_rack_2x4, Coordinate(x=1542.0, y=717.0, z=0)) + + # 电解液缓存位 - 6x2布局 bottle_rack_6x2 = BottleRack( name="bottle_rack_6x2", size_x=120.0, @@ -1239,9 +1248,10 @@ class CoincellDeck(Deck): position_spacing=35.0, orientation="vertical", ) - self.assign_child_resource(bottle_rack_2x6, Coordinate(x=300, y=300, z=0)) - # 电解液回收位6x2 - bottle_rack_2x6_2 = BottleRack( + self.assign_child_resource(bottle_rack_6x2, Coordinate(x=1659.0, y=869.0, z=0)) + + # 电解液回收位 - 6x2布局 + bottle_rack_6x2_2 = BottleRack( name="bottle_rack_6x2_2", size_x=120.0, size_y=250.0, @@ -1251,22 +1261,22 @@ class CoincellDeck(Deck): position_spacing=35.0, orientation="vertical", ) - self.assign_child_resource(bottle_rack_2x6_2, Coordinate(x=430, y=300, z=0)) + self.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=1765.0, y=869.0, z=0)) # 将 ElectrodeSheet 放满 3x4 与 6x2 的所有孔位 for idx in range(bottle_rack_2x4.num_items_x * bottle_rack_2x4.num_items_y): sheet = ElectrodeSheet(name=f"sheet_3x4_{idx}", size_x=12, size_y=12, size_z=0.1) bottle_rack_2x4.assign_child_resource(sheet, index=idx) - for idx in range(bottle_rack_2x6.num_items_x * bottle_rack_2x6.num_items_y): + for idx in range(bottle_rack_6x2.num_items_x * bottle_rack_6x2.num_items_y): sheet = ElectrodeSheet(name=f"sheet_6x2_{idx}", size_x=12, size_y=12, size_z=0.1) - bottle_rack_2x6.assign_child_resource(sheet, index=idx) + bottle_rack_6x2.assign_child_resource(sheet, index=idx) tip_box = TipBox64(name="tip_box_64") - self.assign_child_resource(tip_box, Coordinate(x=300, y=100, z=0)) + self.assign_child_resource(tip_box, Coordinate(x=1938.0, y=743.0, z=0)) waste_tip_box = WasteTipBox(name="waste_tip_box") - self.assign_child_resource(waste_tip_box, Coordinate(x=300, y=200, z=0)) + self.assign_child_resource(waste_tip_box, Coordinate(x=1960.0, y=639.0, z=0)) From 765038a136672514adff04be89e2b07ba71a08ba Mon Sep 17 00:00:00 2001 From: calvincao Date: Tue, 4 Nov 2025 02:18:44 +0800 Subject: [PATCH 056/104] Revert "Update YB_YH_materials.py" This reverts commit bfd415279b09038790507b85834d0f1e001405f1. --- .../coin_cell_assembly/YB_YH_materials.py | 294 ++++++------------ .../coin_cell_assembly/coin_cell_assembly.py | 2 +- 2 files changed, 93 insertions(+), 203 deletions(-) diff --git a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py index 3860135..a2dff68 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py +++ b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py @@ -755,7 +755,7 @@ class BottleRack(Resource): size_z: float, category: str = "bottle_rack", model: Optional[str] = None, - num_items_x: int = 2, + num_items_x: int = 3, num_items_y: int = 4, position_spacing: float = 35.0, orientation: str = "horizontal", @@ -971,7 +971,6 @@ class ClipMagazine_four(ItemizedResource[ClipMagazineHole]): self.hole_diameter = hole_diameter self.hole_depth = hole_depth self.max_sheets_per_hole = max_sheets_per_hole - def serialize(self) -> dict: return { @@ -981,141 +980,6 @@ class ClipMagazine_four(ItemizedResource[ClipMagazineHole]): "max_sheets_per_hole": self.max_sheets_per_hole, } -class ClipMagazine_two(ItemizedResource[ClipMagazineHole]): - """子弹夹类 - 有2个洞位,每个洞位放多个极片""" - children: List[ClipMagazineHole] - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - hole_diameter: float = 14.0, - hole_depth: float = 10.0, - hole_spacing: float = 25.0, - max_sheets_per_hole: int = 100, - category: str = "clip_magazine_four", - model: Optional[str] = None, - ): - """初始化子弹夹 - - Args: - name: 子弹夹名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - hole_diameter: 洞直径 (mm) - hole_depth: 洞深度 (mm) - hole_spacing: 洞位间距 (mm) - max_sheets_per_hole: 每个洞位最大极片数量 - category: 类别 - model: 型号 - """ - # 创建4个洞位,排成2x2布局 - holes = create_ordered_items_2d( - klass=ClipMagazineHole, - num_items_x=1, - num_items_y=2, - dx=(size_x - 2 * hole_spacing) / 2, # 居中 - dy=(size_y - hole_spacing) / 2, # 居中 - dz=size_z - 0, - item_dx=hole_spacing, - item_dy=hole_spacing, - diameter=hole_diameter, - depth=hole_depth, - ) - - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=holes, - category=category, - model=model, - ) - - # 保存洞位的直径和深度 - self.hole_diameter = hole_diameter - self.hole_depth = hole_depth - self.max_sheets_per_hole = max_sheets_per_hole - - - def serialize(self) -> dict: - return { - **super().serialize(), - "hole_diameter": self.hole_diameter, - "hole_depth": self.hole_depth, - "max_sheets_per_hole": self.max_sheets_per_hole, - } -class ClipMagazine_one(ItemizedResource[ClipMagazineHole]): - """子弹夹类 - 有1个洞位,每个洞位放多个极片""" - children: List[ClipMagazineHole] - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - hole_diameter: float = 14.0, - hole_depth: float = 10.0, - hole_spacing: float = 25.0, - max_sheets_per_hole: int = 100, - category: str = "clip_magazine_four", - model: Optional[str] = None, - ): - """初始化子弹夹 - - Args: - name: 子弹夹名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - hole_diameter: 洞直径 (mm) - hole_depth: 洞深度 (mm) - hole_spacing: 洞位间距 (mm) - max_sheets_per_hole: 每个洞位最大极片数量 - category: 类别 - model: 型号 - """ - # 创建4个洞位,排成2x2布局 - holes = create_ordered_items_2d( - klass=ClipMagazineHole, - num_items_x=1, - num_items_y=1, - dx=(size_x - 2 * hole_spacing) / 2, # 居中 - dy=(size_y - hole_spacing) / 2, # 居中 - dz=size_z - 0, - item_dx=hole_spacing, - item_dy=hole_spacing, - diameter=hole_diameter, - depth=hole_depth, - ) - - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=holes, - category=category, - model=model, - ) - - # 保存洞位的直径和深度 - self.hole_diameter = hole_diameter - self.hole_depth = hole_depth - self.max_sheets_per_hole = max_sheets_per_hole - - - def serialize(self) -> dict: - return { - **super().serialize(), - "hole_diameter": self.hole_diameter, - "hole_depth": self.hole_depth, - "max_sheets_per_hole": self.max_sheets_per_hole, - } - class CoincellDeck(Deck): """纽扣电池组装工作站台面类""" @@ -1154,81 +1018,89 @@ class CoincellDeck(Deck): def setup(self) -> None: """设置工作站的标准布局 - 包含子弹夹、料盘、瓶架等完整配置""" # ====================================== 子弹夹 ============================================ - # 铝箔(1) - lvbo_zip = ClipMagazine_one("lvbo_zip", 80, 80, 10) - self.assign_child_resource(lvbo_zip, Coordinate(x=1400, y=50, z=0)) - #正极(234) - zhengji_zip = ClipMagazine_four("zhengji_zip", 80, 80, 10) - self.assign_child_resource(zhengji_zip, Coordinate(x=1400, y=50, z=0)) - #2 正极壳 - zhengjike_zip = ClipMagazine_four("zhengjike_zip", 80, 80, 10) - self.assign_child_resource(zhengjike_zip, Coordinate(x=1600, y=200, z=0)) - # 垫片 - danpian_zip = ClipMagazine_two("danpian_zip", 80, 80, 10) - self.assign_child_resource(danpian_zip, Coordinate(x=1500, y=200, z=0)) - #2 负极壳 - fujike_zip = ClipMagazine_four("fujike_zip", 80, 80, 10) - self.assign_child_resource(fujike_zip, Coordinate(x=1600, y=200, z=0)) - # 弹片 - tanpian_zip = ClipMagazine_two("tantanpian_zippian", 80, 80, 10) - self.assign_child_resource(tanpian_zip, Coordinate(x=1500, y=300, z=0)) - #3成品弹夹 - chengpindanjia_zip = ClipMagazine("chengpindanjia_zip", 80, 80, 10) - self.assign_child_resource(chengpindanjia_zip, Coordinate(x=1500, y=200, z=0)) + zip_dan_jia = ClipMagazine_four("zi_dan_jia", 80, 80, 10) + self.assign_child_resource(zip_dan_jia, Coordinate(x=1400, y=50, z=0)) + zip_dan_jia2 = ClipMagazine_four("zi_dan_jia2", 80, 80, 10) + self.assign_child_resource(zip_dan_jia2, Coordinate(x=1600, y=200, z=0)) + zip_dan_jia3 = ClipMagazine("zi_dan_jia3", 80, 80, 10) + self.assign_child_resource(zip_dan_jia3, Coordinate(x=1500, y=200, z=0)) + zip_dan_jia4 = ClipMagazine("zi_dan_jia4", 80, 80, 10) + self.assign_child_resource(zip_dan_jia4, Coordinate(x=1500, y=300, z=0)) + zip_dan_jia5 = ClipMagazine("zi_dan_jia5", 80, 80, 10) + self.assign_child_resource(zip_dan_jia5, Coordinate(x=1600, y=300, z=0)) + zip_dan_jia6 = ClipMagazine("zi_dan_jia6", 80, 80, 10) + self.assign_child_resource(zip_dan_jia6, Coordinate(x=1530, y=500, z=0)) + zip_dan_jia7 = ClipMagazine("zi_dan_jia7", 80, 80, 10) + self.assign_child_resource(zip_dan_jia7, Coordinate(x=1180, y=400, z=0)) + zip_dan_jia8 = ClipMagazine("zi_dan_jia8", 80, 80, 10) + self.assign_child_resource(zip_dan_jia8, Coordinate(x=1280, y=400, z=0)) # 为子弹夹添加极片 - for i in range(1): # ClipMagazine_one 有1个洞位 - lvbo = ElectrodeSheet(name=f"lvbo{i}", size_x=12, size_y=12, size_z=0.1) - lvbo_zip.children[i].assign_child_resource(lvbo, location=None) - for i in range(4): # ClipMagazine_four 有4个洞位 - zhengji = ElectrodeSheet(name=f"zhengji_{i}", size_x=12, size_y=12, size_z=0.1) - zhengji_zip.children[i].assign_child_resource(zhengji, location=None) - for i in range(4): # ClipMagazine_four 有4个洞位 - zhengjike = ElectrodeSheet(name=f"zhengjike_{i}", size_x=12, size_y=12, size_z=0.1) - zhengjike_zip.children[i].assign_child_resource(zhengjike, location=None) - for i in range(2): # ClipMagazine_two 有2个洞位 - danpian = ElectrodeSheet(name=f"danpian_{i}", size_x=12, size_y=12, size_z=0.1) - danpian_zip.children[i].assign_child_resource(danpian, location=None) - for i in range(4): # ClipMagazine_four 有4个洞位 - fujike = ElectrodeSheet(name=f"fujike_{i}", size_x=12, size_y=12, size_z=0.1) - fujike_zip.children[i].assign_child_resource(fujike, location=None) - for i in range(2): # ClipMagazine_two 有2个洞位 - tanpian = ElectrodeSheet(name=f"tanpian_{i}", size_x=12, size_y=12, size_z=0.1) - tanpian_zip.children[i].assign_child_resource(tanpian, location=None) - for i in range(6): # ClipMagazine 有6个洞位 - chengpindanjia = ElectrodeSheet(name=f"chengpindanjia_{i}", size_x=12, size_y=12, size_z=0.1) - chengpindanjia_zip.children[i].assign_child_resource(chengpindanjia, location=None) - + for i in range(4): + jipian = ElectrodeSheet(name=f"zi_dan_jia_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia2.children[i].assign_child_resource(jipian, location=None) + for i in range(4): + jipian2 = ElectrodeSheet(name=f"zi_dan_jia2_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia.children[i].assign_child_resource(jipian2, location=None) + for i in range(6): + jipian3 = ElectrodeSheet(name=f"zi_dan_jia3_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia3.children[i].assign_child_resource(jipian3, location=None) + for i in range(6): + jipian4 = ElectrodeSheet(name=f"zi_dan_jia4_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia4.children[i].assign_child_resource(jipian4, location=None) + for i in range(6): + jipian5 = ElectrodeSheet(name=f"zi_dan_jia5_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia5.children[i].assign_child_resource(jipian5, location=None) + for i in range(6): + jipian6 = ElectrodeSheet(name=f"zi_dan_jia6_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia6.children[i].assign_child_resource(jipian6, location=None) + for i in range(6): + jipian7 = ElectrodeSheet(name=f"zi_dan_jia7_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia7.children[i].assign_child_resource(jipian7, location=None) + for i in range(6): + jipian8 = ElectrodeSheet(name=f"zi_dan_jia8_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + zip_dan_jia8.children[i].assign_child_resource(jipian8, location=None) # ====================================== 物料板 ============================================ - # 创建6个4*4的物料板(料盘carrier) - fujiliaopan = MaterialPlate(name="fujiliaopan", size_x=120, size_y=100, size_z=10.0, fill=True) - self.assign_child_resource(fujiliaopan, Coordinate(x=1010, y=50, z=0)) - for i in range(8): - fujipian = ElectrodeSheet(name=f"{fujiliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - fujiliaopan.children[i].assign_child_resource(fujipian, location=None) + # 创建6个4*4的物料板 + liaopan1 = MaterialPlate(name="liaopan1", size_x=120, size_y=100, size_z=10.0, fill=True) + self.assign_child_resource(liaopan1, Coordinate(x=1010, y=50, z=0)) + for i in range(16): + jipian_1 = ElectrodeSheet(name=f"{liaopan1.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + liaopan1.children[i].assign_child_resource(jipian_1, location=None) - gemoliaopan = MaterialPlate(name="gemoliaopan", size_x=120, size_y=100, size_z=10.0, fill=True) - self.assign_child_resource(gemoliaopan, Coordinate(x=1130, y=50, z=0)) - for i in range(8): - gemopian = ElectrodeSheet(name=f"{gemoliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - gemoliaopan.children[i].assign_child_resource(gemopian, location=None) + liaopan2 = MaterialPlate(name="liaopan2", size_x=120, size_y=100, size_z=10.0, fill=True) + self.assign_child_resource(liaopan2, Coordinate(x=1130, y=50, z=0)) + liaopan3 = MaterialPlate(name="liaopan3", size_x=120, size_y=100, size_z=10.0, fill=True) + self.assign_child_resource(liaopan3, Coordinate(x=1250, y=50, z=0)) + + liaopan4 = MaterialPlate(name="liaopan4", size_x=120, size_y=100, size_z=10.0, fill=True) + self.assign_child_resource(liaopan4, Coordinate(x=1010, y=150, z=0)) + for i in range(16): + jipian_4 = ElectrodeSheet(name=f"{liaopan4.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + liaopan4.children[i].assign_child_resource(jipian_4, location=None) + + liaopan5 = MaterialPlate(name="liaopan5", size_x=120, size_y=100, size_z=10.0, fill=True) + self.assign_child_resource(liaopan5, Coordinate(x=1130, y=150, z=0)) + + liaopan6 = MaterialPlate(name="liaopan6", size_x=120, size_y=100, size_z=10.0, fill=True) + self.assign_child_resource(liaopan6, Coordinate(x=1250, y=150, z=0)) + # ====================================== 瓶架、移液枪 ============================================ # 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒 - # 奔耀上料5ml分液瓶小板 - bottle_rack_2x4 = BottleRack( + bottle_rack_3x4 = BottleRack( name="bottle_rack_3x4", size_x=210.0, size_y=140.0, size_z=100.0, - num_items_x=2, + num_items_x=3, num_items_y=4, position_spacing=35.0, orientation="vertical", ) - self.assign_child_resource(bottle_rack_2x4, Coordinate(x=100, y=200, z=0)) - # 电解液缓存位6x2 + self.assign_child_resource(bottle_rack_3x4, Coordinate(x=100, y=200, z=0)) + bottle_rack_6x2 = BottleRack( name="bottle_rack_6x2", size_x=120.0, @@ -1240,8 +1112,8 @@ class CoincellDeck(Deck): orientation="vertical", ) self.assign_child_resource(bottle_rack_6x2, Coordinate(x=300, y=300, z=0)) - # 电解液回收位6x2 - bottle_rack_2x6_2 = BottleRack( + + bottle_rack_6x2_2 = BottleRack( name="bottle_rack_6x2_2", size_x=120.0, size_y=250.0, @@ -1251,12 +1123,12 @@ class CoincellDeck(Deck): position_spacing=35.0, orientation="vertical", ) - self.assign_child_resource(bottle_rack_2x6_2, Coordinate(x=430, y=300, z=0)) + self.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=430, y=300, z=0)) # 将 ElectrodeSheet 放满 3x4 与 6x2 的所有孔位 - for idx in range(bottle_rack_2x4.num_items_x * bottle_rack_2x4.num_items_y): + for idx in range(bottle_rack_3x4.num_items_x * bottle_rack_3x4.num_items_y): sheet = ElectrodeSheet(name=f"sheet_3x4_{idx}", size_x=12, size_y=12, size_z=0.1) - bottle_rack_2x4.assign_child_resource(sheet, index=idx) + bottle_rack_3x4.assign_child_resource(sheet, index=idx) for idx in range(bottle_rack_6x2.num_items_x * bottle_rack_6x2.num_items_y): sheet = ElectrodeSheet(name=f"sheet_6x2_{idx}", size_x=12, size_y=12, size_z=0.1) @@ -1267,9 +1139,27 @@ class CoincellDeck(Deck): waste_tip_box = WasteTipBox(name="waste_tip_box") self.assign_child_resource(waste_tip_box, Coordinate(x=300, y=200, z=0)) + + print(self) +def create_coin_cell_deck(name: str = "coin_cell_deck", size_x: float = 1000.0, size_y: float = 1000.0, size_z: float = 900.0) -> CoincellDeck: + """创建并配置标准的纽扣电池组装工作站台面 + + Args: + name: 台面名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + + Returns: + 已配置好的 CoincellDeck 对象 + """ + # 创建 CoincellDeck 实例并自动执行 setup 配置 + deck = CoincellDeck(name=name, size_x=size_x, size_y=size_y, size_z=size_z, setup=True) + return deck + if __name__ == "__main__": - deck = CoincellDeck(setup=True) + deck = create_coin_cell_deck() print(deck) \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index 65d18ba..752b3d5 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -138,7 +138,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): # 如果没有传入 deck,则创建标准配置的 deck if self.deck is None: - self.deck = CoincellDeck(size_x=3650, size_y=1550, size_z=2100, origin=Coordinate(-2000, 100, 0),setup=True) + self.deck = CoincellDeck(size_x=1000, size_y=1000, size_z=900, origin=Coordinate(-100, -100, 0),setup=True) else: # 如果传入了 deck 但还没有 setup,可以选择是否 setup if self.deck is not None and len(self.deck.children) == 0: From d4e1286df7fc385ee740b5639a251d39a60e97a6 Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Tue, 4 Nov 2025 03:42:00 +0800 Subject: [PATCH 057/104] 1104_byxinyu --- .../workstation/coin_cell_assembly/coin_cell_assembly.py | 2 +- unilabos/registry/devices/coin_cell_workstation.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index 752b3d5..7657063 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -138,7 +138,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): # 如果没有传入 deck,则创建标准配置的 deck if self.deck is None: - self.deck = CoincellDeck(size_x=1000, size_y=1000, size_z=900, origin=Coordinate(-100, -100, 0),setup=True) + self.deck = CoincellDeck(size_x=1000, size_y=1000, size_z=900, origin=Coordinate(-800, 0, 0),setup=True) else: # 如果传入了 deck 但还没有 setup,可以选择是否 setup if self.deck is not None and len(self.deck.children) == 0: diff --git a/unilabos/registry/devices/coin_cell_workstation.yaml b/unilabos/registry/devices/coin_cell_workstation.yaml index 06caa0f..be2bcaa 100644 --- a/unilabos/registry/devices/coin_cell_workstation.yaml +++ b/unilabos/registry/devices/coin_cell_workstation.yaml @@ -502,7 +502,7 @@ coincellassemblyworkstation_device: config_info: [] description: '' handles: [] - icon: '' + icon: coin_cell_assembly_picture.webp init_param_schema: config: properties: From 3c583008aa6684a58bb54729d44a0b724ec5b518 Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Tue, 4 Nov 2025 20:19:27 +0800 Subject: [PATCH 058/104] YB4branc_bylixinyu --- new_cellconfig3c.json | 39 ++++++++++++-- .../coin_cell_assembly/coin_cell_assembly.py | 54 +++++-------------- unilabos/registry/resources/bioyond/deck.yaml | 15 ++++++ 3 files changed, 63 insertions(+), 45 deletions(-) diff --git a/new_cellconfig3c.json b/new_cellconfig3c.json index 446d235..2a5d6eb 100644 --- a/new_cellconfig3c.json +++ b/new_cellconfig3c.json @@ -47,21 +47,50 @@ { "id": "BatteryStation", "name": "扣电工作站", + "parent": null, "children": [ "coin_cell_deck" ], - "parent": null, "type": "device", - "class": "coincellassemblyworkstation_device", + "class":"coincellassemblyworkstation_device", "position": { - "x": 600, - "y": 400, + "x": -600, + "y": -400, "z": 0 }, "config": { - "debug_mode": false, + "deck": { + "data": { + "_resource_child_name": "YB_YH_Deck", + "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials:CoincellDeck" + } + }, "protocol_type": [] } + }, + { + "id": "YB_YH_Deck", + "name": "YB_YH_Deck", + "children": [], + "parent": "BatteryStation", + "type": "deck", + "class": "CoincellDeck", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "CoincellDeck", + "setup": true, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + } + }, + "data": {} } ], "links": [] diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index 65d18ba..21682a9 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -109,44 +109,23 @@ def _coerce_deck_input(deck: Any) -> Optional[Deck]: #构建物料系统 class CoinCellAssemblyWorkstation(WorkstationBase): - def __init__( - self, - deck: Deck=None, - address: str = "172.21.33.176", - port: str = "502", - debug_mode: bool = False, + def __init__(self, + config: dict = None, + deck=None, + address: str = "172.21.33.176", + port: str = "502", + debug_mode: bool = False, *args, - **kwargs, - ): - if deck is None and "deck" in kwargs: - deck = kwargs.pop("deck") - else: - kwargs.pop("deck", None) + **kwargs): - normalized_deck = _coerce_deck_input(deck) - - if deck is None and isinstance(normalized_deck, Deck): - deck = normalized_deck - - super().__init__( - #桌子 - deck=deck, - *args, - **kwargs, - ) + if deck is None and config: + deck = config.get('deck') + else : + logger.info("没有传入依华deck,检查启动json文件") + super().__init__(deck=deck, *args, **kwargs,) self.debug_mode = debug_mode - # 如果没有传入 deck,则创建标准配置的 deck - if self.deck is None: - self.deck = CoincellDeck(size_x=3650, size_y=1550, size_z=2100, origin=Coordinate(-2000, 100, 0),setup=True) - else: - # 如果传入了 deck 但还没有 setup,可以选择是否 setup - if self.deck is not None and len(self.deck.children) == 0: - # deck 为空,执行 setup - self.deck.setup() - # 否则使用传入的 deck(可能已经配置好了) - self.deck = self.deck - + """ 连接初始化 """ modbus_client = TCPClient(addr=address, port=port) logger.debug(f"创建 Modbus 客户端: {modbus_client}") @@ -173,12 +152,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): self.csv_export_running = False self.csv_export_file = None self.coin_num_N = 0 #已组装电池数量 - #创建一个物料台面,包含两个极片板 - #self._ros_node.update_resource(self.deck) - - #ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ - # "resources": [self.deck] - #}) + def post_init(self, ros_node: ROS2WorkstationNode): diff --git a/unilabos/registry/resources/bioyond/deck.yaml b/unilabos/registry/resources/bioyond/deck.yaml index 664bab3..d28218b 100644 --- a/unilabos/registry/resources/bioyond/deck.yaml +++ b/unilabos/registry/resources/bioyond/deck.yaml @@ -34,3 +34,18 @@ BIOYOND_YB_Deck: init_param_schema: {} registry_type: resource version: 1.0.0 +CoincellDeck: + category: + - deck + class: + module: unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials:CoincellDeck + type: pylabrobot + description: CoincellDeck + handles: [] + icon: yihua.webp + init_param_schema: {} + registry_type: resource + version: 1.0.0 + + + From 966b51042dd443a811b28e3329cf9420c60c551e Mon Sep 17 00:00:00 2001 From: Junhan Chang Date: Thu, 6 Nov 2025 00:59:46 +0800 Subject: [PATCH 059/104] =?UTF-8?q?rename=20and=20fix=20all=20Yihua=20Mate?= =?UTF-8?q?rials:=20ClipMagazineHole=E2=86=92Magazine(ResourceStack),=20an?= =?UTF-8?q?d=20use=20factory=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bioyond_yihua_YB.json | 104 +-- new_cellconfig.json | 54 ++ .../bioyond_cell/bioyond_cell_workstation.py | 10 +- .../bioyond_cell/bioyond_yihua_YB.json | 104 +-- .../coin_cell_assembly/YB_YH_materials.py | 736 +++--------------- .../coin_cell_assembly/coin_cell_assembly.py | 17 +- .../workstation_material_management.py | 583 -------------- unilabos/registry/resources/bioyond/deck.yaml | 3 - unilabos/resources/battery/bottle_carriers.py | 56 ++ unilabos/resources/battery/magazine.py | 284 +++++++ unilabos/resources/itemized_carrier.py | 2 +- 11 files changed, 602 insertions(+), 1351 deletions(-) create mode 100644 new_cellconfig.json delete mode 100644 unilabos/devices/workstation/workstation_material_management.py create mode 100644 unilabos/resources/battery/bottle_carriers.py create mode 100644 unilabos/resources/battery/magazine.py diff --git a/bioyond_yihua_YB.json b/bioyond_yihua_YB.json index c38179d..668ad6a 100644 --- a/bioyond_yihua_YB.json +++ b/bioyond_yihua_YB.json @@ -99,7 +99,7 @@ "z": 0 }, "config": { - "type": "ClipMagazine_four", + "type": "MagazineHolder_4", "size_x": 80, "size_y": 80, "size_z": 10, @@ -140,7 +140,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -235,7 +235,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -330,7 +330,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -425,7 +425,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -523,7 +523,7 @@ "z": 0 }, "config": { - "type": "ClipMagazine_four", + "type": "MagazineHolder_4", "size_x": 80, "size_y": 80, "size_z": 10, @@ -564,7 +564,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -659,7 +659,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -754,7 +754,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -849,7 +849,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -949,7 +949,7 @@ "z": 0 }, "config": { - "type": "ClipMagazine", + "type": "MagazineHolder_6", "size_x": 80, "size_y": 80, "size_z": 10, @@ -992,7 +992,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -1087,7 +1087,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -1182,7 +1182,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -1277,7 +1277,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -1372,7 +1372,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -1467,7 +1467,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -1567,7 +1567,7 @@ "z": 0 }, "config": { - "type": "ClipMagazine", + "type": "MagazineHolder_6", "size_x": 80, "size_y": 80, "size_z": 10, @@ -1610,7 +1610,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -1705,7 +1705,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -1800,7 +1800,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -1895,7 +1895,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -1990,7 +1990,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -2085,7 +2085,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -2185,7 +2185,7 @@ "z": 0 }, "config": { - "type": "ClipMagazine", + "type": "MagazineHolder_6", "size_x": 80, "size_y": 80, "size_z": 10, @@ -2228,7 +2228,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -2323,7 +2323,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -2418,7 +2418,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -2513,7 +2513,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -2608,7 +2608,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -2703,7 +2703,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -2803,7 +2803,7 @@ "z": 0 }, "config": { - "type": "ClipMagazine", + "type": "MagazineHolder_6", "size_x": 80, "size_y": 80, "size_z": 10, @@ -2846,7 +2846,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -2941,7 +2941,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3036,7 +3036,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3131,7 +3131,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3226,7 +3226,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3321,7 +3321,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3421,7 +3421,7 @@ "z": 0 }, "config": { - "type": "ClipMagazine", + "type": "MagazineHolder_6", "size_x": 80, "size_y": 80, "size_z": 10, @@ -3464,7 +3464,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3559,7 +3559,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3654,7 +3654,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3749,7 +3749,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3844,7 +3844,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3939,7 +3939,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -4039,7 +4039,7 @@ "z": 0 }, "config": { - "type": "ClipMagazine", + "type": "MagazineHolder_6", "size_x": 80, "size_y": 80, "size_z": 10, @@ -4082,7 +4082,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -4177,7 +4177,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -4272,7 +4272,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -4367,7 +4367,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -4462,7 +4462,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -4557,7 +4557,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, diff --git a/new_cellconfig.json b/new_cellconfig.json new file mode 100644 index 0000000..d06fd0e --- /dev/null +++ b/new_cellconfig.json @@ -0,0 +1,54 @@ +{ + "nodes": [ + { + "id": "BatteryStation", + "name": "扣电工作站", + "parent": null, + "children": [ + "coin_cell_deck" + ], + "type": "device", + "class":"coincellassemblyworkstation_device", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "deck": { + "data": { + "_resource_child_name": "YB_YH_Deck", + "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials:CoincellDeck" + } + }, + "debug_mode": true, + "protocol_type": [] + } + }, + { + "id": "YB_YH_Deck", + "name": "YB_YH_Deck", + "children": [], + "parent": "BatteryStation", + "type": "deck", + "class": "CoincellDeck", + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "config": { + "type": "CoincellDeck", + "setup": true, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + } + }, + "data": {} + } + ], + "links": [] +} \ No newline at end of file diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index ee82579..a672230 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -16,6 +16,7 @@ from unilabos.devices.workstation.bioyond_studio.config import ( API_CONFIG, MATERIAL_TYPE_MAPPINGS, WAREHOUSE_MAPPING, SOLID_LIQUID_MAPPINGS ) from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService +from unilabos.resources.bioyond.decks import BIOYOND_YB_Deck from unilabos.utils.log import logger from unilabos.registry.registry import lab_registry @@ -1074,17 +1075,12 @@ class BioyondCellWorkstation(BioyondWorkstation): if __name__ == "__main__": lab_registry.setup() - ws = BioyondCellWorkstation() + deck = BIOYOND_YB_Deck(setup=True) + ws = BioyondCellWorkstation(deck=deck) # ws.create_sample(name="test", board_type="配液瓶(小)板", bottle_type="配液瓶(小)", location_code="B01") # logger.info(ws.scheduler_stop()) # logger.info(ws.scheduler_start()) - # results = ws.create_materials(SOLID_LIQUID_MAPPINGS) - # for r in results: - # logger.info(r) - # 从CSV文件读取物料列表并批量创建入库 - # result = ws.create_and_inbound_materials() - # 继续后续流程 # logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 # # # 使用正斜杠或 Path 对象来指定文件路径 diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json index 3d1b98a..3119d0b 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_yihua_YB.json @@ -113,7 +113,7 @@ "z": 0 }, "config": { - "type": "ClipMagazine_four", + "type": "MagazineHolder_4", "size_x": 80, "size_y": 80, "size_z": 10, @@ -154,7 +154,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -249,7 +249,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -344,7 +344,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -439,7 +439,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -537,7 +537,7 @@ "z": 0 }, "config": { - "type": "ClipMagazine_four", + "type": "MagazineHolder_4", "size_x": 80, "size_y": 80, "size_z": 10, @@ -578,7 +578,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -673,7 +673,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -768,7 +768,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -863,7 +863,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -963,7 +963,7 @@ "z": 0 }, "config": { - "type": "ClipMagazine", + "type": "MagazineHolder_6", "size_x": 80, "size_y": 80, "size_z": 10, @@ -1006,7 +1006,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -1101,7 +1101,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -1196,7 +1196,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -1291,7 +1291,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -1386,7 +1386,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -1481,7 +1481,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -1581,7 +1581,7 @@ "z": 0 }, "config": { - "type": "ClipMagazine", + "type": "MagazineHolder_6", "size_x": 80, "size_y": 80, "size_z": 10, @@ -1624,7 +1624,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -1719,7 +1719,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -1814,7 +1814,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -1909,7 +1909,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -2004,7 +2004,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -2099,7 +2099,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -2199,7 +2199,7 @@ "z": 0 }, "config": { - "type": "ClipMagazine", + "type": "MagazineHolder_6", "size_x": 80, "size_y": 80, "size_z": 10, @@ -2242,7 +2242,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -2337,7 +2337,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -2432,7 +2432,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -2527,7 +2527,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -2622,7 +2622,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -2717,7 +2717,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -2817,7 +2817,7 @@ "z": 0 }, "config": { - "type": "ClipMagazine", + "type": "MagazineHolder_6", "size_x": 80, "size_y": 80, "size_z": 10, @@ -2860,7 +2860,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -2955,7 +2955,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3050,7 +3050,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3145,7 +3145,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3240,7 +3240,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3335,7 +3335,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3435,7 +3435,7 @@ "z": 0 }, "config": { - "type": "ClipMagazine", + "type": "MagazineHolder_6", "size_x": 80, "size_y": 80, "size_z": 10, @@ -3478,7 +3478,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3573,7 +3573,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3668,7 +3668,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3763,7 +3763,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3858,7 +3858,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -3953,7 +3953,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -4053,7 +4053,7 @@ "z": 0 }, "config": { - "type": "ClipMagazine", + "type": "MagazineHolder_6", "size_x": 80, "size_y": 80, "size_z": 10, @@ -4096,7 +4096,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -4191,7 +4191,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -4286,7 +4286,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -4381,7 +4381,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -4476,7 +4476,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, @@ -4571,7 +4571,7 @@ "z": 10 }, "config": { - "type": "ClipMagazineHole", + "type": "Magazine", "size_x": 14.0, "size_y": 14.0, "size_z": 10.0, diff --git a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py index 156ab6f..8bb0a8d 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py +++ b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py @@ -18,6 +18,9 @@ from pylabrobot.resources.tip_rack import TipRack, TipSpot from pylabrobot.resources.trash import Trash from pylabrobot.resources.utils import create_ordered_items_2d +from unilabos.resources.battery.magazine import MagazineHolder_1, MagazineHolder_2, MagazineHolder_4, MagazineHolder_6 +from unilabos.resources.battery.bottle_carriers import YIHUA_Electrolyte_12VialCarrier + class ElectrodeSheetState(TypedDict): diameter: float # 直径 (mm) @@ -165,7 +168,6 @@ class MaterialHole(Resource): return self.children[index] - class MaterialPlateState(TypedDict): hole_spacing_x: float hole_spacing_y: float @@ -327,132 +329,6 @@ class PlateSlot(ResourceStack): } -class ClipMagazineHole(Container): - """子弹夹洞位类""" - - def __init__( - self, - name: str, - diameter: float, - depth: float, - max_sheets: int = 100, - category: str = "clip_magazine_hole", - ): - """初始化子弹夹洞位 - - Args: - name: 洞位名称 - diameter: 洞直径 (mm) - depth: 洞深度 (mm) - max_sheets: 最大极片数量 - category: 类别 - """ - super().__init__( - name=name, - size_x=diameter, - size_y=diameter, - size_z=depth, - category=category, - ) - self.diameter = diameter - self.depth = depth - self.max_sheets = max_sheets - self._sheets: List[ElectrodeSheet] = [] - - def can_add_sheet(self, sheet: ElectrodeSheet) -> bool: - """检查是否可以添加极片""" - return (len(self._sheets) < self.max_sheets and - sheet.diameter <= self.diameter) - - def add_sheet(self, sheet: ElectrodeSheet) -> None: - """添加极片""" - if not self.can_add_sheet(sheet): - raise ValueError(f"无法向洞位 {self.name} 添加极片") - self._sheets.append(sheet) - - def take_sheet(self) -> ElectrodeSheet: - """取出极片""" - if len(self._sheets) == 0: - raise ValueError(f"洞位 {self.name} 没有极片") - return self._sheets.pop() - - def get_sheet_count(self) -> int: - """获取极片数量""" - return len(self._sheets) - - def serialize_state(self) -> Dict[str, Any]: - return { - "sheet_count": len(self._sheets), - "sheets": [sheet.serialize() for sheet in self._sheets], - } - -# TODO: 这个要改 -class ClipMagazine(ItemizedResource[ClipMagazineHole]): - """子弹夹类 - 有6个洞位,每个洞位放多个极片""" - children: List[ClipMagazineHole] - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - hole_diameter: float = 14.0, - hole_depth: float = 10.0, - hole_spacing: float = 25.0, - max_sheets_per_hole: int = 100, - category: str = "clip_magazine", - model: Optional[str] = None, - ): - """初始化子弹夹 - - Args: - name: 子弹夹名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - hole_diameter: 洞直径 (mm) - hole_depth: 洞深度 (mm) - hole_spacing: 洞位间距 (mm) - max_sheets_per_hole: 每个洞位最大极片数量 - category: 类别 - model: 型号 - """ - # 创建6个洞位,排成2x3布局 - holes = create_ordered_items_2d( - klass=ClipMagazineHole, - num_items_x=3, - num_items_y=2, - dx=(size_x - 2 * hole_spacing) / 2, # 居中 - dy=(size_y - hole_spacing) / 2, # 居中 - dz=size_z - 0, - item_dx=hole_spacing, - item_dy=hole_spacing, - diameter=hole_diameter, - depth=hole_depth, - ) - - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=holes, - category=category, - model=model, - ) - - # 保存洞位的直径和深度 - self.hole_diameter = hole_diameter - self.hole_depth = hole_depth - self.max_sheets_per_hole = max_sheets_per_hole - - def serialize(self) -> dict: - return { - **super().serialize(), - "hole_diameter": self.hole_diameter, - "hole_depth": self.hole_depth, - "max_sheets_per_hole": self.max_sheets_per_hole, - } #是一种类型注解,不用self class BatteryState(TypedDict): """电池状态字典""" @@ -595,76 +471,54 @@ class BatteryPressSlot(Resource): def get_battery_info(self, index: int) -> Battery: return self.children[0] -# TODO:这个移液枪架子看一下从哪继承 -class TipBox64State(TypedDict): - """电池状态字典""" - tip_diameter: float = 5.0 - tip_length: float = 50.0 - with_tips: bool = True -class TipBox64(TipRack): - """64孔枪头盒类""" - - children: List[TipSpot] = [] - def __init__( - self, +def TipBox64( name: str, size_x: float = 127.8, size_y: float = 85.5, size_z: float = 60.0, category: str = "tip_box_64", model: Optional[str] = None, - ): - """初始化64孔枪头盒 +): + """64孔枪头盒类""" + from pylabrobot.resources.tip import Tip - Args: - name: 枪头盒名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - tip_diameter: 枪头直径 (mm) - tip_length: 枪头长度 (mm) - category: 类别 - model: 型号 - with_tips: 是否带枪头 - """ - from pylabrobot.resources.tip import Tip - - # 创建8x8=64个枪头位 - def make_tip(): - return Tip( - has_filter=False, - total_tip_length=20.0, - maximal_volume=1000, # 1mL - fitting_depth=8.0, - ) - - tip_spots = create_ordered_items_2d( - klass=TipSpot, - num_items_x=8, - num_items_y=8, - dx=8.0, - dy=8.0, - dz=0.0, - item_dx=9.0, - item_dy=9.0, - size_x=10, - size_y=10, - size_z=0.0, - make_tip=make_tip, - ) - self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate() - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=tip_spots, - category=category, - model=model, - with_tips=True, + # 创建8x8=64个枪头位 + def make_tip(): + return Tip( + has_filter=False, + total_tip_length=20.0, + maximal_volume=1000, # 1mL + fitting_depth=8.0, ) + tip_spots = create_ordered_items_2d( + klass=TipSpot, + num_items_x=12, + num_items_y=8, + dx=8.0, + dy=8.0, + dz=0.0, + item_dx=9.0, + item_dy=9.0, + size_x=10, + size_y=10, + size_z=0.0, + make_tip=make_tip, + ) + idx_available = list(range(0, 32)) + list(range(64, 96)) + tip_spots_available = {k: v for i, (k, v) in enumerate(tip_spots.items()) if i in idx_available} + return TipRack( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=tip_spots_available, + category=category, + model=model, + with_tips=True, + ) + class WasteTipBoxstate(TypedDict): @@ -682,8 +536,12 @@ class WasteTipBox(Trash): size_x: float = 127.8, size_y: float = 85.5, size_z: float = 60.0, - category: str = "waste_tip_box", - model: Optional[str] = None, + material_z_thickness=0, + max_volume=float("inf"), + category="trash", + model=None, + compute_volume_from_height=None, + compute_height_from_volume=None, ): """初始化废枪头盒 @@ -733,389 +591,6 @@ class WasteTipBox(Trash): return data -class BottleRackState(TypedDict): - """ bottle_diameter: 瓶子直径 (mm) - bottle_height: 瓶子高度 (mm) - position_spacing: 位置间距 (mm)""" - bottle_diameter: float - bottle_height: float - name_to_index: dict - - - -class BottleRack(Resource): - """瓶架类 - 12个待配位置+12个已配位置""" - children: List[Resource] = [] - - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - category: str = "bottle_rack", - model: Optional[str] = None, - num_items_x: int = 2, - num_items_y: int = 4, - position_spacing: float = 35.0, - orientation: str = "horizontal", - padding_x: float = 20.0, - padding_y: float = 20.0, - ): - """初始化瓶架 - - Args: - name: 瓶架名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - category=category, - model=model, - ) - # 初始化状态 - self._unilabos_state: BottleRackState = BottleRackState( - bottle_diameter=30.0, - bottle_height=100.0, - position_spacing=position_spacing, - name_to_index={}, - ) - # 基于网格生成瓶位坐标映射(居中摆放) - # 使用内边距,避免点跑到容器外(前端渲染不按mm等比缩放时更稳妥) - origin_x = padding_x - origin_y = padding_y - self.index_to_pos = {} - for j in range(num_items_y): - for i in range(num_items_x): - idx = j * num_items_x + i - if orientation == "vertical": - # 纵向:沿 y 方向优先排列 - self.index_to_pos[idx] = Coordinate( - x=origin_x + j * position_spacing, - y=origin_y + i * position_spacing, - z=0, - ) - else: - # 横向(默认):沿 x 方向优先排列 - self.index_to_pos[idx] = Coordinate( - x=origin_x + i * position_spacing, - y=origin_y + j * position_spacing, - z=0, - ) - self.name_to_index = {} - self.name_to_pos = {} - self.num_items_x = num_items_x - self.num_items_y = num_items_y - self.orientation = orientation - self.padding_x = padding_x - self.padding_y = padding_y - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update( - self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - - # TODO: 这里有些问题要重新写一下 - def assign_child_resource_old(self, resource: Resource, location=Coordinate.zero(), reassign=True): - capacity = self.num_items_x * self.num_items_y - assert len(self.children) < capacity, "瓶架已满,无法添加更多瓶子" - index = len(self.children) - location = self.index_to_pos.get(index, Coordinate.zero()) - self.name_to_pos[resource.name] = location - self.name_to_index[resource.name] = index - return super().assign_child_resource(resource, location, reassign) - - def assign_child_resource(self, resource: Resource, index: int): - capacity = self.num_items_x * self.num_items_y - assert 0 <= index < capacity, "无效的瓶子索引" - self.name_to_index[resource.name] = index - location = self.index_to_pos[index] - return super().assign_child_resource(resource, location) - - def unassign_child_resource(self, resource: Bottle): - super().unassign_child_resource(resource) - self.index_to_pos.pop(self.name_to_index.pop(resource.name, None), None) - - def serialize(self) -> dict: - return { - **super().serialize(), - "num_items_x": self.num_items_x, - "num_items_y": self.num_items_y, - "position_spacing": self._unilabos_state.get("position_spacing", 35.0), - "orientation": self.orientation, - "padding_x": self.padding_x, - "padding_y": self.padding_y, - } - -class BottleState(TypedDict): - diameter: float - height: float - electrolyte_name: str - electrolyte_volume: float - max_volume: float - -class Bottle(Resource): - """瓶子类 - 容纳电解液""" - - def __init__( - self, - name: str, - category: str = "bottle", - ): - """初始化瓶子 - - Args: - name: 瓶子名称 - diameter: 直径 (mm) - height: 高度 (mm) - max_volume: 最大体积 (μL) - barcode: 二维码 - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=1, - size_y=1, - size_z=1, - category=category, - ) - self._unilabos_state: BottleState = BottleState() - - def aspirate_electrolyte(self, volume: float) -> bool: - current_volume = self._unilabos_state["electrolyte_volume"] - assert current_volume > volume, f"Cannot aspirate {volume}μL, only {current_volume}μL available." - self._unilabos_state["electrolyte_volume"] -= volume - return True - - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data - -class ClipMagazine_four(ItemizedResource[ClipMagazineHole]): - """子弹夹类 - 有4个洞位,每个洞位放多个极片""" - children: List[ClipMagazineHole] - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - hole_diameter: float = 14.0, - hole_depth: float = 10.0, - hole_spacing: float = 25.0, - max_sheets_per_hole: int = 100, - category: str = "clip_magazine_four", - model: Optional[str] = None, - ): - """初始化子弹夹 - - Args: - name: 子弹夹名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - hole_diameter: 洞直径 (mm) - hole_depth: 洞深度 (mm) - hole_spacing: 洞位间距 (mm) - max_sheets_per_hole: 每个洞位最大极片数量 - category: 类别 - model: 型号 - """ - # 创建4个洞位,排成2x2布局 - holes = create_ordered_items_2d( - klass=ClipMagazineHole, - num_items_x=2, - num_items_y=2, - dx=(size_x - 2 * hole_spacing) / 2, # 居中 - dy=(size_y - hole_spacing) / 2, # 居中 - dz=size_z - 0, - item_dx=hole_spacing, - item_dy=hole_spacing, - diameter=hole_diameter, - depth=hole_depth, - ) - - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=holes, - category=category, - model=model, - ) - - # 保存洞位的直径和深度 - self.hole_diameter = hole_diameter - self.hole_depth = hole_depth - self.max_sheets_per_hole = max_sheets_per_hole - - - def serialize(self) -> dict: - return { - **super().serialize(), - "hole_diameter": self.hole_diameter, - "hole_depth": self.hole_depth, - "max_sheets_per_hole": self.max_sheets_per_hole, - } - -class ClipMagazine_two(ItemizedResource[ClipMagazineHole]): - """子弹夹类 - 有2个洞位,每个洞位放多个极片""" - children: List[ClipMagazineHole] - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - hole_diameter: float = 14.0, - hole_depth: float = 10.0, - hole_spacing: float = 25.0, - max_sheets_per_hole: int = 100, - category: str = "clip_magazine_four", - model: Optional[str] = None, - ): - """初始化子弹夹 - - Args: - name: 子弹夹名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - hole_diameter: 洞直径 (mm) - hole_depth: 洞深度 (mm) - hole_spacing: 洞位间距 (mm) - max_sheets_per_hole: 每个洞位最大极片数量 - category: 类别 - model: 型号 - """ - # 创建4个洞位,排成2x2布局 - holes = create_ordered_items_2d( - klass=ClipMagazineHole, - num_items_x=1, - num_items_y=2, - dx=(size_x - 2 * hole_spacing) / 2, # 居中 - dy=(size_y - hole_spacing) / 2, # 居中 - dz=size_z - 0, - item_dx=hole_spacing, - item_dy=hole_spacing, - diameter=hole_diameter, - depth=hole_depth, - ) - - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=holes, - category=category, - model=model, - ) - - # 保存洞位的直径和深度 - self.hole_diameter = hole_diameter - self.hole_depth = hole_depth - self.max_sheets_per_hole = max_sheets_per_hole - - - def serialize(self) -> dict: - return { - **super().serialize(), - "hole_diameter": self.hole_diameter, - "hole_depth": self.hole_depth, - "max_sheets_per_hole": self.max_sheets_per_hole, - } -class ClipMagazine_one(ItemizedResource[ClipMagazineHole]): - """子弹夹类 - 有1个洞位,每个洞位放多个极片""" - children: List[ClipMagazineHole] - def __init__( - self, - name: str, - size_x: float, - size_y: float, - size_z: float, - hole_diameter: float = 14.0, - hole_depth: float = 10.0, - hole_spacing: float = 25.0, - max_sheets_per_hole: int = 100, - category: str = "clip_magazine_four", - model: Optional[str] = None, - ): - """初始化子弹夹 - - Args: - name: 子弹夹名称 - size_x: 长度 (mm) - size_y: 宽度 (mm) - size_z: 高度 (mm) - hole_diameter: 洞直径 (mm) - hole_depth: 洞深度 (mm) - hole_spacing: 洞位间距 (mm) - max_sheets_per_hole: 每个洞位最大极片数量 - category: 类别 - model: 型号 - """ - # 创建4个洞位,排成2x2布局 - holes = create_ordered_items_2d( - klass=ClipMagazineHole, - num_items_x=1, - num_items_y=1, - dx=(size_x - 2 * hole_spacing) / 2, # 居中 - dy=(size_y - hole_spacing) / 2, # 居中 - dz=size_z - 0, - item_dx=hole_spacing, - item_dy=hole_spacing, - diameter=hole_diameter, - depth=hole_depth, - ) - - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - ordered_items=holes, - category=category, - model=model, - ) - - # 保存洞位的直径和深度 - self.hole_diameter = hole_diameter - self.hole_depth = hole_depth - self.max_sheets_per_hole = max_sheets_per_hole - - - def serialize(self) -> dict: - return { - **super().serialize(), - "hole_diameter": self.hole_diameter, - "hole_depth": self.hole_depth, - "max_sheets_per_hole": self.max_sheets_per_hole, - } - class CoincellDeck(Deck): """纽扣电池组装工作站台面类""" @@ -1155,122 +630,96 @@ class CoincellDeck(Deck): """设置工作站的标准布局 - 包含子弹夹、料盘、瓶架等完整配置""" # ====================================== 子弹夹 ============================================ # 铝箔(1个洞位) - lvbo_zip = ClipMagazine_one("lvbo_zip", 80, 80, 10) + lvbo_zip = MagazineHolder_1("铝箔弹夹", 80, 80, 10) self.assign_child_resource(lvbo_zip, Coordinate(x=2737.0, y=301.0, z=0)) # 正极片(4个洞位,2x2布局) - zhengji_zip = ClipMagazine_four("zhengji_zip", 80, 80, 10) + zhengji_zip = MagazineHolder_4("正极弹夹", 80, 80, 10) self.assign_child_resource(zhengji_zip, Coordinate(x=2799.0, y=356.0, z=0)) # 正极壳(4个洞位,2x2布局) - zhengjike_zip = ClipMagazine_four("zhengjike_zip", 80, 80, 10) + zhengjike_zip = MagazineHolder_4("正极壳弹夹", 80, 80, 10) self.assign_child_resource(zhengjike_zip, Coordinate(x=2586.0, y=1143.0, z=0)) # 垫片(2个洞位,1x2布局) - danpian_zip = ClipMagazine_two("danpian_zip", 80, 80, 10) + danpian_zip = MagazineHolder_2("垫片弹夹", 80, 80, 10) self.assign_child_resource(danpian_zip, Coordinate(x=2690.0, y=1141.0, z=0)) # 负极壳(4个洞位,2x2布局) - fujike_zip = ClipMagazine_four("fujike_zip", 80, 80, 10) + fujike_zip = MagazineHolder_4("负极壳弹夹", 80, 80, 10) self.assign_child_resource(fujike_zip, Coordinate(x=2492.0, y=1144.0, z=0)) # 弹片(2个洞位,1x2布局) - tanpian_zip = ClipMagazine_two("tanpian_zip", 80, 80, 10) + tanpian_zip = MagazineHolder_2("弹片弹夹", 80, 80, 10) self.assign_child_resource(tanpian_zip, Coordinate(x=2492.0, y=1139.0, z=0)) # 成品弹夹(6个洞位,3x2布局) - chengpindanjia_zip = ClipMagazine("chengpindanjia_zip", 80, 80, 10) + chengpindanjia_zip = MagazineHolder_6("成品弹夹", 80, 80, 10) self.assign_child_resource(chengpindanjia_zip, Coordinate(x=3112.0, y=1295.0, z=0)) # 为子弹夹添加极片 - for i in range(1): # ClipMagazine_one 有1个洞位 - lvbo = ElectrodeSheet(name=f"lvbo_{i}", size_x=12, size_y=12, size_z=0.1) + for i in range(1): # MagazineHolder_1 有1个洞位 + lvbo = ElectrodeSheet(name=f"铝箔_{i}", size_x=12, size_y=12, size_z=0.1) lvbo_zip.children[i].assign_child_resource(lvbo, location=None) - for i in range(4): # ClipMagazine_four 有4个洞位 - zhengji = ElectrodeSheet(name=f"zhengji_{i}", size_x=12, size_y=12, size_z=0.1) + for i in range(4): # MagazineHolder_4 有4个洞位 + zhengji = ElectrodeSheet(name=f"正极_{i}", size_x=12, size_y=12, size_z=0.1) zhengji_zip.children[i].assign_child_resource(zhengji, location=None) - for i in range(4): # ClipMagazine_four 有4个洞位 - zhengjike = ElectrodeSheet(name=f"zhengjike_{i}", size_x=12, size_y=12, size_z=0.1) + for i in range(4): # MagazineHolder_4 有4个洞位 + zhengjike = ElectrodeSheet(name=f"正极壳_{i}", size_x=12, size_y=12, size_z=0.1) zhengjike_zip.children[i].assign_child_resource(zhengjike, location=None) - for i in range(2): # ClipMagazine_two 有2个洞位 - danpian = ElectrodeSheet(name=f"danpian_{i}", size_x=12, size_y=12, size_z=0.1) + for i in range(2): # MagazineHolder_2 有2个洞位 + danpian = ElectrodeSheet(name=f"垫片_{i}", size_x=12, size_y=12, size_z=0.1) danpian_zip.children[i].assign_child_resource(danpian, location=None) - for i in range(4): # ClipMagazine_four 有4个洞位 - fujike = ElectrodeSheet(name=f"fujike_{i}", size_x=12, size_y=12, size_z=0.1) + for i in range(4): # MagazineHolder_4 有4个洞位 + fujike = ElectrodeSheet(name=f"负极壳_{i}", size_x=12, size_y=12, size_z=0.1) fujike_zip.children[i].assign_child_resource(fujike, location=None) - for i in range(2): # ClipMagazine_two 有2个洞位 - tanpian = ElectrodeSheet(name=f"tanpian_{i}", size_x=12, size_y=12, size_z=0.1) + for i in range(2): # MagazineHolder_2 有2个洞位 + tanpian = ElectrodeSheet(name=f"弹片_{i}", size_x=12, size_y=12, size_z=0.1) tanpian_zip.children[i].assign_child_resource(tanpian, location=None) - for i in range(6): # ClipMagazine 有6个洞位 - chengpindanjia = ElectrodeSheet(name=f"chengpindanjia_{i}", size_x=12, size_y=12, size_z=0.1) - chengpindanjia_zip.children[i].assign_child_resource(chengpindanjia, location=None) + # for i in range(6): # MagazineHolder_6 有6个洞位 + # chengpindanjia = ElectrodeSheet(name=f"成品弹夹_{i}", size_x=12, size_y=12, size_z=0.1) + # chengpindanjia_zip.children[i].assign_child_resource(chengpindanjia, location=None) # ====================================== 物料板 ============================================ # 创建物料板(料盘carrier)- 4x4布局 # 负极料盘 - fujiliaopan = MaterialPlate(name="fujiliaopan", size_x=120, size_y=100, size_z=10.0, fill=True) + fujiliaopan = MaterialPlate(name="负极料盘", size_x=120, size_y=100, size_z=10.0, fill=True) self.assign_child_resource(fujiliaopan, Coordinate(x=2107.0, y=304.0, z=0)) - for i in range(16): - fujipian = ElectrodeSheet(name=f"{fujiliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - fujiliaopan.children[i].assign_child_resource(fujipian, location=None) + # for i in range(16): + # fujipian = ElectrodeSheet(name=f"{fujiliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + # fujiliaopan.children[i].assign_child_resource(fujipian, location=None) # 隔膜料盘 - gemoliaopan = MaterialPlate(name="gemoliaopan", size_x=120, size_y=100, size_z=10.0, fill=True) + gemoliaopan = MaterialPlate(name="隔膜料盘", size_x=120, size_y=100, size_z=10.0, fill=True) self.assign_child_resource(gemoliaopan, Coordinate(x=2107.0, y=146.0, z=0)) - for i in range(16): - gemopian = ElectrodeSheet(name=f"{gemoliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) - gemoliaopan.children[i].assign_child_resource(gemopian, location=None) + # for i in range(16): + # gemopian = ElectrodeSheet(name=f"{gemoliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) + # gemoliaopan.children[i].assign_child_resource(gemopian, location=None) # ====================================== 瓶架、移液枪 ============================================ # 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒 - # 奔耀上料5ml分液瓶小板 - 2x4布局 - bottle_rack_2x4 = BottleRack( - name="bottle_rack_3x4", - size_x=210.0, - size_y=140.0, - size_z=100.0, - num_items_x=2, - num_items_y=4, - position_spacing=35.0, - orientation="vertical", - ) - self.assign_child_resource(bottle_rack_2x4, Coordinate(x=1542.0, y=717.0, z=0)) + # 奔耀上料5ml分液瓶小板 - 由奔曜跨站转运而来,不单独写 + + # bottle_rack_3x4 = BottleRack( + # name="bottle_rack_3x4", + # size_x=210.0, + # size_y=140.0, + # size_z=100.0, + # num_items_x=2, + # num_items_y=4, + # position_spacing=35.0, + # orientation="vertical", + # ) + # self.assign_child_resource(bottle_rack_3x4, Coordinate(x=1542.0, y=717.0, z=0)) # 电解液缓存位 - 6x2布局 - bottle_rack_6x2 = BottleRack( - name="bottle_rack_6x2", - size_x=120.0, - size_y=250.0, - size_z=100.0, - num_items_x=6, - num_items_y=2, - position_spacing=35.0, - orientation="vertical", - ) + bottle_rack_6x2 = YIHUA_Electrolyte_12VialCarrier(name="bottle_rack_6x2") self.assign_child_resource(bottle_rack_6x2, Coordinate(x=300, y=300, z=0)) # 电解液回收位6x2 - bottle_rack_2x6_2 = BottleRack( - name="bottle_rack_6x2_2", - size_x=120.0, - size_y=250.0, - size_z=100.0, - num_items_x=6, - num_items_y=2, - position_spacing=35.0, - orientation="vertical", - ) + bottle_rack_6x2_2 = YIHUA_Electrolyte_12VialCarrier(name="bottle_rack_6x2_2") self.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=1765.0, y=869.0, z=0)) - # 将 ElectrodeSheet 放满 3x4 与 6x2 的所有孔位 - for idx in range(bottle_rack_2x4.num_items_x * bottle_rack_2x4.num_items_y): - sheet = ElectrodeSheet(name=f"sheet_3x4_{idx}", size_x=12, size_y=12, size_z=0.1) - bottle_rack_2x4.assign_child_resource(sheet, index=idx) - - for idx in range(bottle_rack_6x2.num_items_x * bottle_rack_6x2.num_items_y): - sheet = ElectrodeSheet(name=f"sheet_6x2_{idx}", size_x=12, size_y=12, size_z=0.1) - bottle_rack_6x2.assign_child_resource(sheet, index=idx) - tip_box = TipBox64(name="tip_box_64") self.assign_child_resource(tip_box, Coordinate(x=1938.0, y=743.0, z=0)) @@ -1278,7 +727,6 @@ class CoincellDeck(Deck): self.assign_child_resource(waste_tip_box, Coordinate(x=1960.0, y=639.0, z=0)) - if __name__ == "__main__": deck = CoincellDeck(setup=True) print(deck) \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index 21682a9..f7f8e33 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -112,19 +112,18 @@ class CoinCellAssemblyWorkstation(WorkstationBase): def __init__(self, config: dict = None, deck=None, - address: str = "172.21.33.176", - port: str = "502", - debug_mode: bool = False, + address: str = "172.21.33.176", + port: str = "502", + debug_mode: bool = False, *args, **kwargs): if deck is None and config: deck = config.get('deck') - else : + if deck is None: logger.info("没有传入依华deck,检查启动json文件") super().__init__(deck=deck, *args, **kwargs,) self.debug_mode = debug_mode - """ 连接初始化 """ modbus_client = TCPClient(addr=address, port=port) @@ -140,12 +139,14 @@ class CoinCellAssemblyWorkstation(WorkstationBase): time.sleep(2) if not modbus_client.client.is_socket_open(): raise ValueError('modbus tcp connection failed') + self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_a.csv')) + self.client = modbus_client.register_node_list(self.nodes) else: print("测试模式,跳过连接") + self.nodes, self.client = None, None """ 工站的配置 """ - self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_a.csv')) - self.client = modbus_client.register_node_list(self.nodes) + self.success = False self.allow_data_read = False #允许读取函数运行标志位 self.csv_export_thread = None @@ -153,8 +154,6 @@ class CoinCellAssemblyWorkstation(WorkstationBase): self.csv_export_file = None self.coin_num_N = 0 #已组装电池数量 - - def post_init(self, ros_node: ROS2WorkstationNode): self._ros_node = ros_node #self.deck = create_a_coin_cell_deck() diff --git a/unilabos/devices/workstation/workstation_material_management.py b/unilabos/devices/workstation/workstation_material_management.py deleted file mode 100644 index a922913..0000000 --- a/unilabos/devices/workstation/workstation_material_management.py +++ /dev/null @@ -1,583 +0,0 @@ -""" -工作站物料管理基类 -Workstation Material Management Base Class - -基于PyLabRobot的物料管理系统 -""" -from typing import Dict, Any, List, Optional, Union, Type -from abc import ABC, abstractmethod -import json - -from pylabrobot.resources import ( - Resource as PLRResource, - Container, - Deck, - Coordinate as PLRCoordinate, -) - -from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker -from unilabos.utils.log import logger -from unilabos.resources.graphio import resource_plr_to_ulab, resource_ulab_to_plr - - -class MaterialManagementBase(ABC): - """物料管理基类 - - 定义工作站物料管理的标准接口: - 1. 物料初始化 - 根据配置创建物料资源 - 2. 物料追踪 - 实时跟踪物料位置和状态 - 3. 物料查找 - 按类型、位置、状态查找物料 - 4. 物料转换 - PyLabRobot与UniLab资源格式转换 - """ - - def __init__( - self, - device_id: str, - deck_config: Dict[str, Any], - resource_tracker: DeviceNodeResourceTracker, - children_config: Dict[str, Dict[str, Any]] = None - ): - self.device_id = device_id - self.deck_config = deck_config - self.resource_tracker = resource_tracker - self.children_config = children_config or {} - - # 创建主台面 - self.plr_deck = self._create_deck() - - # 扩展ResourceTracker - self._extend_resource_tracker() - - # 注册deck到resource tracker - self.resource_tracker.add_resource(self.plr_deck) - - # 初始化子资源 - self.plr_resources = {} - self._initialize_materials() - - def _create_deck(self) -> Deck: - """创建主台面""" - return Deck( - name=f"{self.device_id}_deck", - size_x=self.deck_config.get("size_x", 1000.0), - size_y=self.deck_config.get("size_y", 1000.0), - size_z=self.deck_config.get("size_z", 500.0), - origin=PLRCoordinate(0, 0, 0) - ) - - def _extend_resource_tracker(self): - """扩展ResourceTracker以支持PyLabRobot特定功能""" - - def find_by_type(resource_type): - """按类型查找资源""" - return self._find_resources_by_type_recursive(self.plr_deck, resource_type) - - def find_by_category(category: str): - """按类别查找资源""" - found = [] - for resource in self._get_all_resources(): - if hasattr(resource, 'category') and resource.category == category: - found.append(resource) - return found - - def find_by_name_pattern(pattern: str): - """按名称模式查找资源""" - import re - found = [] - for resource in self._get_all_resources(): - if re.search(pattern, resource.name): - found.append(resource) - return found - - # 动态添加方法到resource_tracker - self.resource_tracker.find_by_type = find_by_type - self.resource_tracker.find_by_category = find_by_category - self.resource_tracker.find_by_name_pattern = find_by_name_pattern - - def _find_resources_by_type_recursive(self, resource, target_type): - """递归查找指定类型的资源""" - found = [] - if isinstance(resource, target_type): - found.append(resource) - - # 递归查找子资源 - children = getattr(resource, "children", []) - for child in children: - found.extend(self._find_resources_by_type_recursive(child, target_type)) - - return found - - def _get_all_resources(self) -> List[PLRResource]: - """获取所有资源""" - all_resources = [] - - def collect_resources(resource): - all_resources.append(resource) - children = getattr(resource, "children", []) - for child in children: - collect_resources(child) - - collect_resources(self.plr_deck) - return all_resources - - def _initialize_materials(self): - """初始化物料""" - try: - # 确定创建顺序,确保父资源先于子资源创建 - creation_order = self._determine_creation_order() - - # 按顺序创建资源 - for resource_id in creation_order: - config = self.children_config[resource_id] - self._create_plr_resource(resource_id, config) - - logger.info(f"物料管理系统初始化完成,共创建 {len(self.plr_resources)} 个资源") - - except Exception as e: - logger.error(f"物料初始化失败: {e}") - - def _determine_creation_order(self) -> List[str]: - """确定资源创建顺序""" - order = [] - visited = set() - - def visit(resource_id: str): - if resource_id in visited: - return - visited.add(resource_id) - - config = self.children_config.get(resource_id, {}) - parent_id = config.get("parent") - - # 如果有父资源,先访问父资源 - if parent_id and parent_id in self.children_config: - visit(parent_id) - - order.append(resource_id) - - for resource_id in self.children_config: - visit(resource_id) - - return order - - def _create_plr_resource(self, resource_id: str, config: Dict[str, Any]): - """创建PyLabRobot资源""" - try: - resource_type = config.get("type", "unknown") - data = config.get("data", {}) - location_config = config.get("location", {}) - - # 创建位置坐标 - location = PLRCoordinate( - x=location_config.get("x", 0.0), - y=location_config.get("y", 0.0), - z=location_config.get("z", 0.0) - ) - - # 根据类型创建资源 - resource = self._create_resource_by_type(resource_id, resource_type, config, data, location) - - if resource: - # 设置父子关系 - parent_id = config.get("parent") - if parent_id and parent_id in self.plr_resources: - parent_resource = self.plr_resources[parent_id] - parent_resource.assign_child_resource(resource, location) - else: - # 直接放在deck上 - self.plr_deck.assign_child_resource(resource, location) - - # 保存资源引用 - self.plr_resources[resource_id] = resource - - # 注册到resource tracker - self.resource_tracker.add_resource(resource) - - logger.debug(f"创建资源成功: {resource_id} ({resource_type})") - - except Exception as e: - logger.error(f"创建资源失败 {resource_id}: {e}") - - @abstractmethod - def _create_resource_by_type( - self, - resource_id: str, - resource_type: str, - config: Dict[str, Any], - data: Dict[str, Any], - location: PLRCoordinate - ) -> Optional[PLRResource]: - """根据类型创建资源 - 子类必须实现""" - pass - - # ============ 物料查找接口 ============ - - def find_materials_by_type(self, material_type: str) -> List[PLRResource]: - """按材料类型查找物料""" - return self.resource_tracker.find_by_category(material_type) - - def find_material_by_id(self, resource_id: str) -> Optional[PLRResource]: - """按ID查找物料""" - return self.plr_resources.get(resource_id) - - def find_available_positions(self, position_type: str) -> List[PLRResource]: - """查找可用位置""" - positions = self.resource_tracker.find_by_category(position_type) - available = [] - - for pos in positions: - if hasattr(pos, 'is_available') and pos.is_available(): - available.append(pos) - elif hasattr(pos, 'children') and len(pos.children) == 0: - available.append(pos) - - return available - - def get_material_inventory(self) -> Dict[str, int]: - """获取物料库存统计""" - inventory = {} - - for resource in self._get_all_resources(): - if hasattr(resource, 'category'): - category = resource.category - inventory[category] = inventory.get(category, 0) + 1 - - return inventory - - # ============ 物料状态更新接口 ============ - - def update_material_location(self, material_id: str, new_location: PLRCoordinate) -> bool: - """更新物料位置""" - try: - material = self.find_material_by_id(material_id) - if material: - material.location = new_location - return True - return False - except Exception as e: - logger.error(f"更新物料位置失败: {e}") - return False - - def move_material(self, material_id: str, target_container_id: str) -> bool: - """移动物料到目标容器""" - try: - material = self.find_material_by_id(material_id) - target = self.find_material_by_id(target_container_id) - - if material and target: - # 从原位置移除 - if material.parent: - material.parent.unassign_child_resource(material) - - # 添加到新位置 - target.assign_child_resource(material) - return True - - return False - - except Exception as e: - logger.error(f"移动物料失败: {e}") - return False - - # ============ 资源转换接口 ============ - - def convert_to_unilab_format(self, plr_resource: PLRResource) -> Dict[str, Any]: - """将PyLabRobot资源转换为UniLab格式""" - return resource_plr_to_ulab(plr_resource) - - def convert_from_unilab_format(self, unilab_resource: Dict[str, Any]) -> PLRResource: - """将UniLab格式转换为PyLabRobot资源""" - return resource_ulab_to_plr(unilab_resource) - - def get_deck_state(self) -> Dict[str, Any]: - """获取Deck状态""" - try: - return { - "deck_info": { - "name": self.plr_deck.name, - "size": { - "x": self.plr_deck.size_x, - "y": self.plr_deck.size_y, - "z": self.plr_deck.size_z - }, - "children_count": len(self.plr_deck.children) - }, - "resources": { - resource_id: self.convert_to_unilab_format(resource) - for resource_id, resource in self.plr_resources.items() - }, - "inventory": self.get_material_inventory() - } - except Exception as e: - logger.error(f"获取Deck状态失败: {e}") - return {"error": str(e)} - - # ============ 数据持久化接口 ============ - - def save_state_to_file(self, file_path: str) -> bool: - """保存状态到文件""" - try: - state = self.get_deck_state() - with open(file_path, 'w', encoding='utf-8') as f: - json.dump(state, f, indent=2, ensure_ascii=False) - logger.info(f"状态已保存到: {file_path}") - return True - except Exception as e: - logger.error(f"保存状态失败: {e}") - return False - - def load_state_from_file(self, file_path: str) -> bool: - """从文件加载状态""" - try: - with open(file_path, 'r', encoding='utf-8') as f: - state = json.load(f) - - # 重新创建资源 - self._recreate_resources_from_state(state) - logger.info(f"状态已从文件加载: {file_path}") - return True - - except Exception as e: - logger.error(f"加载状态失败: {e}") - return False - - def _recreate_resources_from_state(self, state: Dict[str, Any]): - """从状态重新创建资源""" - # 清除现有资源 - self.plr_resources.clear() - self.plr_deck.children.clear() - - # 从状态重新创建 - resources_data = state.get("resources", {}) - for resource_id, resource_data in resources_data.items(): - try: - plr_resource = self.convert_from_unilab_format(resource_data) - self.plr_resources[resource_id] = plr_resource - self.plr_deck.assign_child_resource(plr_resource) - except Exception as e: - logger.error(f"重新创建资源失败 {resource_id}: {e}") - - -class CoinCellMaterialManagement(MaterialManagementBase): - """纽扣电池物料管理类 - - 从 button_battery_station 抽取的物料管理功能 - """ - - def _create_resource_by_type( - self, - resource_id: str, - resource_type: str, - config: Dict[str, Any], - data: Dict[str, Any], - location: PLRCoordinate - ) -> Optional[PLRResource]: - """根据类型创建纽扣电池相关资源""" - - # 导入纽扣电池资源类 - from unilabos.device_comms.button_battery_station import ( - MaterialPlate, PlateSlot, ClipMagazine, BatteryPressSlot, - TipBox64, WasteTipBox, BottleRack, Battery, ElectrodeSheet - ) - - try: - if resource_type == "material_plate": - return self._create_material_plate(resource_id, config, data, location) - - elif resource_type == "plate_slot": - return self._create_plate_slot(resource_id, config, data, location) - - elif resource_type == "clip_magazine": - return self._create_clip_magazine(resource_id, config, data, location) - - elif resource_type == "battery_press_slot": - return self._create_battery_press_slot(resource_id, config, data, location) - - elif resource_type == "tip_box": - return self._create_tip_box(resource_id, config, data, location) - - elif resource_type == "waste_tip_box": - return self._create_waste_tip_box(resource_id, config, data, location) - - elif resource_type == "bottle_rack": - return self._create_bottle_rack(resource_id, config, data, location) - - elif resource_type == "battery": - return self._create_battery(resource_id, config, data, location) - - else: - logger.warning(f"未知的资源类型: {resource_type}") - return None - - except Exception as e: - logger.error(f"创建资源失败 {resource_id} ({resource_type}): {e}") - return None - - def _create_material_plate(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate): - """创建料板""" - from unilabos.device_comms.button_battery_station import MaterialPlate, ElectrodeSheet - - plate = MaterialPlate( - name=resource_id, - size_x=config.get("size_x", 80.0), - size_y=config.get("size_y", 80.0), - size_z=config.get("size_z", 10.0), - hole_diameter=config.get("hole_diameter", 15.0), - hole_depth=config.get("hole_depth", 8.0), - hole_spacing_x=config.get("hole_spacing_x", 20.0), - hole_spacing_y=config.get("hole_spacing_y", 20.0), - number=data.get("number", "") - ) - plate.location = location - - # 如果有预填充的极片数据,创建极片 - electrode_sheets = data.get("electrode_sheets", []) - for i, sheet_data in enumerate(electrode_sheets): - if i < len(plate.children): # 确保不超过洞位数量 - hole = plate.children[i] - sheet = ElectrodeSheet( - name=f"{resource_id}_sheet_{i}", - diameter=sheet_data.get("diameter", 14.0), - thickness=sheet_data.get("thickness", 0.1), - mass=sheet_data.get("mass", 0.01), - material_type=sheet_data.get("material_type", "cathode"), - info=sheet_data.get("info", "") - ) - hole.place_electrode_sheet(sheet) - - return plate - - def _create_plate_slot(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate): - """创建板槽位""" - from unilabos.device_comms.button_battery_station import PlateSlot - - slot = PlateSlot( - name=resource_id, - max_plates=config.get("max_plates", 8) - ) - slot.location = location - return slot - - def _create_clip_magazine(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate): - """创建子弹夹""" - from unilabos.device_comms.button_battery_station import ClipMagazine - - magazine = ClipMagazine( - name=resource_id, - size_x=config.get("size_x", 150.0), - size_y=config.get("size_y", 100.0), - size_z=config.get("size_z", 50.0), - hole_diameter=config.get("hole_diameter", 15.0), - hole_depth=config.get("hole_depth", 40.0), - hole_spacing=config.get("hole_spacing", 25.0), - max_sheets_per_hole=config.get("max_sheets_per_hole", 100) - ) - magazine.location = location - return magazine - - def _create_battery_press_slot(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate): - """创建电池压制槽""" - from unilabos.device_comms.button_battery_station import BatteryPressSlot - - slot = BatteryPressSlot( - name=resource_id, - diameter=config.get("diameter", 20.0), - depth=config.get("depth", 15.0) - ) - slot.location = location - return slot - - def _create_tip_box(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate): - """创建枪头盒""" - from unilabos.device_comms.button_battery_station import TipBox64 - - tip_box = TipBox64( - name=resource_id, - size_x=config.get("size_x", 127.8), - size_y=config.get("size_y", 85.5), - size_z=config.get("size_z", 60.0), - with_tips=data.get("with_tips", True) - ) - tip_box.location = location - return tip_box - - def _create_waste_tip_box(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate): - """创建废枪头盒""" - from unilabos.device_comms.button_battery_station import WasteTipBox - - waste_box = WasteTipBox( - name=resource_id, - size_x=config.get("size_x", 127.8), - size_y=config.get("size_y", 85.5), - size_z=config.get("size_z", 60.0), - max_tips=config.get("max_tips", 100) - ) - waste_box.location = location - return waste_box - - def _create_bottle_rack(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate): - """创建瓶架""" - from unilabos.device_comms.button_battery_station import BottleRack - - rack = BottleRack( - name=resource_id, - size_x=config.get("size_x", 210.0), - size_y=config.get("size_y", 140.0), - size_z=config.get("size_z", 100.0), - bottle_diameter=config.get("bottle_diameter", 30.0), - bottle_height=config.get("bottle_height", 100.0), - position_spacing=config.get("position_spacing", 35.0) - ) - rack.location = location - return rack - - def _create_battery(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate): - """创建电池""" - from unilabos.device_comms.button_battery_station import Battery - - battery = Battery( - name=resource_id, - diameter=config.get("diameter", 20.0), - height=config.get("height", 3.2), - max_volume=config.get("max_volume", 100.0), - barcode=data.get("barcode", "") - ) - battery.location = location - return battery - - # ============ 纽扣电池特定查找方法 ============ - - def find_material_plates(self): - """查找所有料板""" - from unilabos.device_comms.button_battery_station import MaterialPlate - return self.resource_tracker.find_by_type(MaterialPlate) - - def find_batteries(self): - """查找所有电池""" - from unilabos.device_comms.button_battery_station import Battery - return self.resource_tracker.find_by_type(Battery) - - def find_electrode_sheets(self): - """查找所有极片""" - found = [] - plates = self.find_material_plates() - for plate in plates: - for hole in plate.children: - if hasattr(hole, 'has_electrode_sheet') and hole.has_electrode_sheet(): - found.append(hole._electrode_sheet) - return found - - def find_plate_slots(self): - """查找所有板槽位""" - from unilabos.device_comms.button_battery_station import PlateSlot - return self.resource_tracker.find_by_type(PlateSlot) - - def find_clip_magazines(self): - """查找所有子弹夹""" - from unilabos.device_comms.button_battery_station import ClipMagazine - return self.resource_tracker.find_by_type(ClipMagazine) - - def find_press_slots(self): - """查找所有压制槽""" - from unilabos.device_comms.button_battery_station import BatteryPressSlot - return self.resource_tracker.find_by_type(BatteryPressSlot) diff --git a/unilabos/registry/resources/bioyond/deck.yaml b/unilabos/registry/resources/bioyond/deck.yaml index d28218b..07f78ea 100644 --- a/unilabos/registry/resources/bioyond/deck.yaml +++ b/unilabos/registry/resources/bioyond/deck.yaml @@ -46,6 +46,3 @@ CoincellDeck: init_param_schema: {} registry_type: resource version: 1.0.0 - - - diff --git a/unilabos/resources/battery/bottle_carriers.py b/unilabos/resources/battery/bottle_carriers.py new file mode 100644 index 0000000..9d9827c --- /dev/null +++ b/unilabos/resources/battery/bottle_carriers.py @@ -0,0 +1,56 @@ +from pylabrobot.resources import create_homogeneous_resources, Coordinate, ResourceHolder, create_ordered_items_2d + +from unilabos.resources.itemized_carrier import Bottle, BottleCarrier +from unilabos.resources.bioyond.YB_bottles import ( + YB_pei_ye_xiao_Bottle, +) +# 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial + + +def YIHUA_Electrolyte_12VialCarrier(name: str) -> BottleCarrier: + """12瓶载架 - 2x6布局""" + # 载架尺寸 (mm) + carrier_size_x = 120.0 + carrier_size_y = 250.0 + carrier_size_z = 50.0 + + # 瓶位尺寸 + bottle_diameter = 35.0 + bottle_spacing_x = 35.0 # X方向间距 + bottle_spacing_y = 35.0 # Y方向间距 + + # 计算起始位置 (居中排列) + start_x = (carrier_size_x - (2 - 1) * bottle_spacing_x - bottle_diameter) / 2 + start_y = (carrier_size_y - (6 - 1) * bottle_spacing_y - bottle_diameter) / 2 + + sites = create_ordered_items_2d( + klass=ResourceHolder, + num_items_x=2, + num_items_y=6, + dx=start_x, + dy=start_y, + dz=5.0, + item_dx=bottle_spacing_x, + item_dy=bottle_spacing_y, + + size_x=bottle_diameter, + size_y=bottle_diameter, + size_z=carrier_size_z, + ) + for k, v in sites.items(): + v.name = f"{name}_{v.name}" + + carrier = BottleCarrier( + name=name, + size_x=carrier_size_x, + size_y=carrier_size_y, + size_z=carrier_size_z, + sites=sites, + model="Electrolyte_12VialCarrier", + ) + carrier.num_items_x = 2 + carrier.num_items_y = 6 + carrier.num_items_z = 1 + for i in range(12): + carrier[i] = YB_pei_ye_xiao_Bottle(f"{name}_vial_{i+1}") + return carrier diff --git a/unilabos/resources/battery/magazine.py b/unilabos/resources/battery/magazine.py new file mode 100644 index 0000000..a5a15cc --- /dev/null +++ b/unilabos/resources/battery/magazine.py @@ -0,0 +1,284 @@ +from typing import Dict, List, Optional, OrderedDict, Union +import math + +from pylabrobot.resources.coordinate import Coordinate +from pylabrobot.resources import Resource, ResourceStack, ItemizedResource +from pylabrobot.resources.carrier import create_homogeneous_resources + + +class Magazine(ResourceStack): + """子弹夹洞位类""" + + def __init__( + self, + name: str, + direction: str = 'z', + resources: Optional[List[Resource]] = None, + max_sheets: int = 100, + **kwargs + ): + """初始化子弹夹洞位 + + Args: + name: 洞位名称 + direction: 堆叠方向 + resources: 资源列表 + max_sheets: 最大极片数量 + """ + super().__init__( + name=name, + direction=direction, + resources=resources, + ) + self.max_sheets = max_sheets + + +class MagazineHolder(ItemizedResource): + """子弹夹类 - 有多个洞位,每个洞位放多个极片""" + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + ordered_items: Optional[Dict[str, Magazine]] = None, + ordering: Optional[OrderedDict[str, str]] = None, + hole_diameter: float = 14.0, + hole_depth: float = 10.0, + max_sheets_per_hole: int = 100, + cross_section_type: str = "circle", + category: str = "magazine_holder", + model: Optional[str] = None, + ): + """初始化子弹夹 + + Args: + name: 子弹夹名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + hole_diameter: 洞直径 (mm) + hole_depth: 洞深度 (mm) + max_sheets_per_hole: 每个洞位最大极片数量 + category: 类别 + model: 型号 + """ + + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=ordered_items, + ordering=ordering, + category=category, + model=model, + ) + + # 保存洞位的直径和深度 + self.hole_diameter = hole_diameter + self.hole_depth = hole_depth + self.max_sheets_per_hole = max_sheets_per_hole + self.cross_section_type = cross_section_type + + def serialize(self) -> dict: + return { + **super().serialize(), + "hole_diameter": self.hole_diameter, + "hole_depth": self.hole_depth, + "max_sheets_per_hole": self.max_sheets_per_hole, + "cross_section_type": self.cross_section_type, + } + + +def magazine_factory( + name: str, + size_x: float, + size_y: float, + size_z: float, + locations: List[Coordinate], + hole_diameter: float = 14.0, + hole_depth: float = 10.0, + max_sheets_per_hole: int = 100, + category: str = "magazine_holder", + model: Optional[str] = None, +) -> 'MagazineHolder': + """工厂函数:创建子弹夹 + + Args: + name: 子弹夹名称 + size_x: 长度 (mm) + size_y: 宽度 (mm) + size_z: 高度 (mm) + locations: 洞位坐标列表 + hole_diameter: 洞直径 (mm) + hole_depth: 洞深度 (mm) + max_sheets_per_hole: 每个洞位最大极片数量 + category: 类别 + model: 型号 + """ + # 创建洞位 + _sites = create_homogeneous_resources( + klass=Magazine, + locations=locations, + resource_size_x=hole_diameter, + resource_size_y=hole_diameter, + name_prefix=name, + max_sheets=max_sheets_per_hole, + ) + + # 生成编号键 + keys = [f"A{i+1}" for i in range(len(locations))] + sites = dict(zip(keys, _sites.values())) + + return MagazineHolder( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + ordered_items=sites, + hole_diameter=hole_diameter, + hole_depth=hole_depth, + max_sheets_per_hole=max_sheets_per_hole, + category=category, + model=model, + ) + + +def MagazineHolder_4( + name: str, + size_x: float = 80.0, + size_y: float = 80.0, + size_z: float = 10.0, + hole_diameter: float = 14.0, + hole_depth: float = 10.0, + hole_spacing: float = 25.0, + max_sheets_per_hole: int = 100, +) -> MagazineHolder: + """创建4孔子弹夹 - 正方形四角排布""" + # 计算4个洞位的坐标(正方形四角排布) + center_x = size_x / 2 + center_y = size_y / 2 + offset = hole_spacing / 2 + + locations = [ + Coordinate(center_x - offset, center_y - offset, size_z - hole_depth), # 左下 + Coordinate(center_x + offset, center_y - offset, size_z - hole_depth), # 右下 + Coordinate(center_x - offset, center_y + offset, size_z - hole_depth), # 左上 + Coordinate(center_x + offset, center_y + offset, size_z - hole_depth), # 右上 + ] + + return magazine_factory( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + locations=locations, + hole_diameter=hole_diameter, + hole_depth=hole_depth, + max_sheets_per_hole=max_sheets_per_hole, + category="clip_magazine_four", + ) + + +def MagazineHolder_2( + name: str, + size_x: float = 80.0, + size_y: float = 80.0, + size_z: float = 10.0, + hole_diameter: float = 14.0, + hole_depth: float = 10.0, + hole_spacing: float = 25.0, + max_sheets_per_hole: int = 100, +) -> MagazineHolder: + """创建2孔子弹夹 - 竖向排布""" + # 计算2个洞位的坐标(竖向排布) + center_x = size_x / 2 + center_y = size_y / 2 + offset = hole_spacing / 2 + + locations = [ + Coordinate(center_x, center_y - offset, size_z - hole_depth), # 下方 + Coordinate(center_x, center_y + offset, size_z - hole_depth), # 上方 + ] + + return magazine_factory( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + locations=locations, + hole_diameter=hole_diameter, + hole_depth=hole_depth, + max_sheets_per_hole=max_sheets_per_hole, + category="clip_magazine_two", + ) + + +def MagazineHolder_1( + name: str, + size_x: float = 80.0, + size_y: float = 80.0, + size_z: float = 10.0, + hole_diameter: float = 14.0, + hole_depth: float = 10.0, + max_sheets_per_hole: int = 100, +) -> MagazineHolder: + """创建1孔子弹夹 - 中心单孔""" + # 计算1个洞位的坐标(中心位置) + center_x = size_x / 2 + center_y = size_y / 2 + + locations = [ + Coordinate(center_x, center_y, size_z - hole_depth), # 中心 + ] + + return magazine_factory( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + locations=locations, + hole_diameter=hole_diameter, + hole_depth=hole_depth, + max_sheets_per_hole=max_sheets_per_hole, + category="clip_magazine_one", + ) + + +def MagazineHolder_6( + name: str, + size_x: float = 80.0, + size_y: float = 80.0, + size_z: float = 40.0, + hole_diameter: float = 14.0, + hole_depth: float = 10.0, + hole_spacing: float = 20.0, + max_sheets_per_hole: int = 100, +) -> MagazineHolder: + """创建6孔子弹夹 - 六边形排布""" + # 计算6个洞位的坐标(六边形排布:中心1个,周围5个) + center_x = size_x / 2 + center_y = size_y / 2 + + locations = [] + + # 周围6个孔,按六边形排布 + for i in range(6): + angle = i * 60 * math.pi / 180 # 每60度一个孔 + x = center_x + hole_spacing * math.cos(angle) + y = center_y + hole_spacing * math.sin(angle) + locations.append(Coordinate(x, y, size_z - hole_depth)) + + return magazine_factory( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + locations=locations, + hole_diameter=hole_diameter, + hole_depth=hole_depth, + max_sheets_per_hole=max_sheets_per_hole, + category="clip_magazine_six", + ) \ No newline at end of file diff --git a/unilabos/resources/itemized_carrier.py b/unilabos/resources/itemized_carrier.py index fef09e2..831a073 100644 --- a/unilabos/resources/itemized_carrier.py +++ b/unilabos/resources/itemized_carrier.py @@ -29,7 +29,7 @@ class Bottle(Well): size_x: float = 0.0, size_y: float = 0.0, size_z: float = 0.0, - barcode: Optional[str] = "", + barcode: Optional[str] = None, category: str = "container", model: Optional[str] = None, **kwargs, From 38ab7d3e783094db2a354c7be665f49dd1d83403 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Fri, 31 Oct 2025 21:43:25 +0800 Subject: [PATCH 060/104] fix run async execution error --- unilabos/ros/nodes/base_device_node.py | 39 ++++++++------------------ unilabos/utils/async_util.py | 22 --------------- 2 files changed, 12 insertions(+), 49 deletions(-) delete mode 100644 unilabos/utils/async_util.py diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index f106312..edf41fb 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -53,7 +53,7 @@ from unilabos.ros.nodes.resource_tracker import ( ) from unilabos.ros.x.rclpyx import get_event_loop from unilabos.ros.utils.driver_creator import WorkstationNodeCreator, PyLabRobotCreator, DeviceClassCreator -from unilabos.utils.async_util import run_async_func +from rclpy.task import Task from unilabos.utils.import_manager import default_manager from unilabos.utils.log import info, debug, warning, error, critical, logger, trace from unilabos.utils.type_check import get_type_class, TypeEncoder, get_result_info_str @@ -1385,18 +1385,19 @@ class ROS2DeviceNode: 它不继承设备类,而是通过代理模式访问设备类的属性和方法。 """ - # 类变量,用于循环管理 - _loop = None - _loop_running = False - _loop_thread = None - @classmethod - def get_loop(cls): - return cls._loop + def run_async_func(cls, func, trace_error=True, **kwargs) -> Task: + def _handle_future_exception(fut): + try: + fut.result() + except Exception as e: + error(f"异步任务 {func.__name__} 报错了") + error(traceback.format_exc()) - @classmethod - def run_async_func(cls, func, trace_error=True, **kwargs): - return run_async_func(func, loop=cls._loop, trace_error=trace_error, **kwargs) + future = rclpy.get_global_executor().create_task(func(**kwargs)) + if trace_error: + future.add_done_callback(_handle_future_exception) + return future @property def driver_instance(self): @@ -1436,11 +1437,6 @@ class ROS2DeviceNode: print_publish: 是否打印发布信息 driver_is_ros: """ - # 在初始化时检查循环状态 - if ROS2DeviceNode._loop_running and ROS2DeviceNode._loop_thread is not None: - pass - elif ROS2DeviceNode._loop_thread is None: - self._start_loop() # 保存设备类是否支持异步上下文 self._has_async_context = hasattr(driver_class, "__aenter__") and hasattr(driver_class, "__aexit__") @@ -1529,17 +1525,6 @@ class ROS2DeviceNode: except Exception as e: self._ros_node.lab_logger().error(f"设备后初始化失败: {e}") - def _start_loop(self): - def run_event_loop(): - loop = asyncio.new_event_loop() - ROS2DeviceNode._loop = loop - asyncio.set_event_loop(loop) - loop.run_forever() - - ROS2DeviceNode._loop_thread = threading.Thread(target=run_event_loop, daemon=True, name="ROS2DeviceNodeLoop") - ROS2DeviceNode._loop_thread.start() - logger.info(f"循环线程已启动") - class DeviceInfoType(TypedDict): id: str diff --git a/unilabos/utils/async_util.py b/unilabos/utils/async_util.py deleted file mode 100644 index 0f50a73..0000000 --- a/unilabos/utils/async_util.py +++ /dev/null @@ -1,22 +0,0 @@ -import asyncio -import traceback -from asyncio import get_event_loop - -from unilabos.utils.log import error - - -def run_async_func(func, *, loop=None, trace_error=True, **kwargs): - if loop is None: - loop = get_event_loop() - - def _handle_future_exception(fut): - try: - fut.result() - except Exception as e: - error(f"异步任务 {func.__name__} 报错了") - error(traceback.format_exc()) - - future = asyncio.run_coroutine_threadsafe(func(**kwargs), loop) - if trace_error: - future.add_done_callback(_handle_future_exception) - return future From 5331d7bfba4491e56df7a7e26d2593e373575495 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Mon, 3 Nov 2025 15:42:12 +0800 Subject: [PATCH 061/104] support sleep and create_task in node --- .../devices/liquid_handling/prcxi/prcxi.py | 395 +++++++++++------- unilabos/ros/nodes/base_device_node.py | 19 +- unilabos/ros/nodes/presets/host_node.py | 3 +- 3 files changed, 257 insertions(+), 160 deletions(-) diff --git a/unilabos/devices/liquid_handling/prcxi/prcxi.py b/unilabos/devices/liquid_handling/prcxi/prcxi.py index d9c0433..a8677f4 100644 --- a/unilabos/devices/liquid_handling/prcxi/prcxi.py +++ b/unilabos/devices/liquid_handling/prcxi/prcxi.py @@ -30,6 +30,7 @@ from pylabrobot.liquid_handling.standard import ( from pylabrobot.resources import Tip, Deck, Plate, Well, TipRack, Resource, Container, Coordinate, TipSpot, Trash from unilabos.devices.liquid_handling.liquid_handler_abstract import LiquidHandlerAbstract +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode class PRCXIError(RuntimeError): @@ -162,6 +163,10 @@ class PRCXI9300Handler(LiquidHandlerAbstract): ) super().__init__(backend=self._unilabos_backend, deck=deck, simulator=simulator, channel_num=channel_num) + def post_init(self, ros_node: BaseROS2DeviceNode): + super().post_init(ros_node) + self._unilabos_backend.post_init(ros_node) + def set_liquid(self, wells: list[Well], liquid_names: list[str], volumes: list[float]): return super().set_liquid(wells, liquid_names, volumes) @@ -424,6 +429,7 @@ class PRCXI9300Backend(LiquidHandlerBackend): _num_channels = 8 # 默认通道数为 8 _is_reset_ok = False + _ros_node: BaseROS2DeviceNode @property def is_reset_ok(self) -> bool: @@ -456,6 +462,9 @@ class PRCXI9300Backend(LiquidHandlerBackend): self._execute_setup = setup self.debug = debug + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + def create_protocol(self, protocol_name): self.protocol_name = protocol_name self.steps_todo_list = [] @@ -500,7 +509,7 @@ class PRCXI9300Backend(LiquidHandlerBackend): self.api_client.call("IAutomation", "Reset") while not self.is_reset_ok: print("Waiting for PRCXI9300 to reset...") - await asyncio.sleep(1) + await self._ros_node.sleep(1) print("PRCXI9300 reset successfully.") except ConnectionRefusedError as e: raise RuntimeError( @@ -533,7 +542,9 @@ class PRCXI9300Backend(LiquidHandlerBackend): tipspot_index = tipspot.parent.children.index(tipspot) tip_columns.append(tipspot_index // 8) if len(set(tip_columns)) != 1: - raise ValueError("All pickups must be from the same tip column. Found different columns: " + str(tip_columns)) + raise ValueError( + "All pickups must be from the same tip column. Found different columns: " + str(tip_columns) + ) PlateNo = plate_indexes[0] + 1 hole_col = tip_columns[0] + 1 hole_row = 1 @@ -1109,12 +1120,15 @@ class PRCXI9300Api: "LiquidDispensingMethod": liquid_method, } + class DefaultLayout: def __init__(self, product_name: str = "PRCXI9300"): self.labresource = {} if product_name not in ["PRCXI9300", "PRCXI9320"]: - raise ValueError(f"Unsupported product_name: {product_name}. Only 'PRCXI9300' and 'PRCXI9320' are supported.") + raise ValueError( + f"Unsupported product_name: {product_name}. Only 'PRCXI9300' and 'PRCXI9320' are supported." + ) if product_name == "PRCXI9300": self.rows = 2 @@ -1129,25 +1143,93 @@ class DefaultLayout: self.layout = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] self.trash_slot = 16 self.waste_liquid_slot = 12 - self.default_layout = {"MatrixId":f"{time.time()}","MatrixName":f"{time.time()}","MatrixCount":16,"WorkTablets": - [{"Number": 1, "Code": "T1", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 2, "Code": "T2", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 3, "Code": "T3", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 4, "Code": "T4", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 5, "Code": "T5", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 6, "Code": "T6", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 7, "Code": "T7", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 8, "Code": "T8", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 9, "Code": "T9", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 10, "Code": "T10", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 11, "Code": "T11", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 12, "Code": "T12", "Material": {"uuid": "730067cf07ae43849ddf4034299030e9", "materialEnum": 0}}, # 这个设置成废液槽,用储液槽表示 - {"Number": 13, "Code": "T13", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 14, "Code": "T14", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 15, "Code": "T15", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 16, "Code": "T16", "Material": {"uuid": "730067cf07ae43849ddf4034299030e9", "materialEnum": 0}} # 这个设置成垃圾桶,用储液槽表示 -] -} + self.default_layout = { + "MatrixId": f"{time.time()}", + "MatrixName": f"{time.time()}", + "MatrixCount": 16, + "WorkTablets": [ + { + "Number": 1, + "Code": "T1", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 2, + "Code": "T2", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 3, + "Code": "T3", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 4, + "Code": "T4", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 5, + "Code": "T5", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 6, + "Code": "T6", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 7, + "Code": "T7", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 8, + "Code": "T8", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 9, + "Code": "T9", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 10, + "Code": "T10", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 11, + "Code": "T11", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 12, + "Code": "T12", + "Material": {"uuid": "730067cf07ae43849ddf4034299030e9", "materialEnum": 0}, + }, # 这个设置成废液槽,用储液槽表示 + { + "Number": 13, + "Code": "T13", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 14, + "Code": "T14", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 15, + "Code": "T15", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 16, + "Code": "T16", + "Material": {"uuid": "730067cf07ae43849ddf4034299030e9", "materialEnum": 0}, + }, # 这个设置成垃圾桶,用储液槽表示 + ], + } def get_layout(self) -> Dict[str, Any]: return { @@ -1155,7 +1237,7 @@ class DefaultLayout: "columns": self.columns, "layout": self.layout, "trash_slot": self.trash_slot, - "waste_liquid_slot": self.waste_liquid_slot + "waste_liquid_slot": self.waste_liquid_slot, } def get_trash_slot(self) -> int: @@ -1178,17 +1260,19 @@ class DefaultLayout: reserved_positions = {12, 16} available_positions = [i for i in range(1, 17) if i not in reserved_positions] - # 计算总需求 + # 计算总需求 total_needed = sum(count for _, _, count in needs) if total_needed > len(available_positions): - raise ValueError(f"需要 {total_needed} 个位置,但只有 {len(available_positions)} 个可用位置(排除位置12和16)") + raise ValueError( + f"需要 {total_needed} 个位置,但只有 {len(available_positions)} 个可用位置(排除位置12和16)" + ) # 依次分配位置 current_pos = 0 for reagent_name, material_name, count in needs: - material_uuid = self.labresource[material_name]['uuid'] - material_enum = self.labresource[material_name]['materialEnum'] + material_uuid = self.labresource[material_name]["uuid"] + material_enum = self.labresource[material_name]["materialEnum"] for _ in range(count): if current_pos >= len(available_positions): @@ -1196,17 +1280,18 @@ class DefaultLayout: position = available_positions[current_pos] # 找到对应的tablet并更新 - for tablet in self.default_layout['WorkTablets']: - if tablet['Number'] == position: - tablet['Material']['uuid'] = material_uuid - tablet['Material']['materialEnum'] = material_enum - layout_list.append(dict(reagent_name=reagent_name, material_name=material_name, positions=position)) + for tablet in self.default_layout["WorkTablets"]: + if tablet["Number"] == position: + tablet["Material"]["uuid"] = material_uuid + tablet["Material"]["materialEnum"] = material_enum + layout_list.append( + dict(reagent_name=reagent_name, material_name=material_name, positions=position) + ) break current_pos += 1 return self.default_layout, layout_list - if __name__ == "__main__": # Example usage # 1. 用导出的json,给每个T1 T2板子设定相应的物料,如果是孔板和枪头盒,要对应区分 @@ -1302,10 +1387,7 @@ if __name__ == "__main__": # # # plate2.set_well_liquids(plate_2_liquids) - - - - # handler = PRCXI9300Handler(deck=deck, host="10.181.214.132", port=9999, + # handler = PRCXI9300Handler(deck=deck, host="10.181.214.132", port=9999, # timeout=10.0, setup=False, debug=False, # simulator=True, # matrix_id="71593", @@ -1391,10 +1473,7 @@ if __name__ == "__main__": # # input("Press Enter to continue...") # Wait for user input before proceeding # # print("PRCXI9300Handler initialized with deck and host settings.") - - -### 9320 ### - + ### 9320 ### deck = PRCXI9300Deck(name="PRCXI_Deck", size_x=100, size_y=100, size_z=100) @@ -1412,12 +1491,15 @@ if __name__ == "__main__": new_plate: PRCXI9300Container = PRCXI9300Container.deserialize(well_containers) return new_plate - def get_tip_rack(name: str, child_prefix: str="tip") -> PRCXI9300Container: + def get_tip_rack(name: str, child_prefix: str = "tip") -> PRCXI9300Container: tip_racks = opentrons_96_tiprack_10ul(name).serialize() tip_rack = PRCXI9300Container( - name=name, size_x=50, size_y=50, size_z=10, category="tip_rack", ordering=collections.OrderedDict({ - k: f"{child_prefix}_{k}" for k, v in tip_racks["ordering"].items() - }) + name=name, + size_x=50, + size_y=50, + size_z=10, + category="tip_rack", + ordering=collections.OrderedDict({k: f"{child_prefix}_{k}" for k, v in tip_racks["ordering"].items()}), ) tip_rack_serialized = tip_rack.serialize() tip_rack_serialized["parent_name"] = deck.name @@ -1629,6 +1711,7 @@ if __name__ == "__main__": ) backend: PRCXI9300Backend = handler.backend from pylabrobot.resources import set_volume_tracking + set_volume_tracking(enabled=True) # res = backend.api_client.get_all_materials() asyncio.run(handler.setup()) # Initialize the handler and setup the connection @@ -1640,10 +1723,10 @@ if __name__ == "__main__": for well in plate13.get_all_items(): # well_pos = well.name.split("_")[1] # 走一行 - # if well_pos.startswith("A"): - if well.name.startswith("PlateT13"): # 走整个Plate + # if well_pos.startswith("A"): + if well.name.startswith("PlateT13"): # 走整个Plate asyncio.run(handler.dispense([well], [0.01], [0])) - + # asyncio.run(handler.dispense([plate10.get_item("H12")], [1], [0])) # asyncio.run(handler.dispense([plate13.get_item("A1")], [1], [0])) # asyncio.run(handler.dispense([plate14.get_item("C5")], [1], [0])) @@ -1652,26 +1735,25 @@ if __name__ == "__main__": asyncio.run(handler.run_protocol()) time.sleep(5) os._exit(0) -# 第一种情景:一个孔往多个孔加液 + # 第一种情景:一个孔往多个孔加液 # plate_2_liquids = handler.set_group("water", [plate2.children[0]], [300]) # plate5_liquids = handler.set_group("master_mix", plate5.children[:23], [100]*23) -# 第二个情景:多个孔往多个孔加液(但是个数得对应) - plate_2_liquids = handler.set_group("water", plate2.children[:23], [300]*23) - plate5_liquids = handler.set_group("master_mix", plate5.children[:23], [100]*23) + # 第二个情景:多个孔往多个孔加液(但是个数得对应) + plate_2_liquids = handler.set_group("water", plate2.children[:23], [300] * 23) + plate5_liquids = handler.set_group("master_mix", plate5.children[:23], [100] * 23) # plate11.set_well_liquids([("Water", 100) if (i % 8 == 0 and i // 8 < 6) else (None, 100) for i in range(96)]) # Set liquids for every 8 wells in plate8 # plate11.set_well_liquids([("Water", 100) if (i % 8 == 0 and i // 8 < 6) else (None, 100) for i in range(96)]) # Set liquids for every 8 wells in plate8 -# A = tree_to_list([resource_plr_to_ulab(deck)]) -# # with open("deck.json", "w", encoding="utf-8") as f: -# # json.dump(A, f, indent=4, ensure_ascii=False) + # A = tree_to_list([resource_plr_to_ulab(deck)]) + # # with open("deck.json", "w", encoding="utf-8") as f: + # # json.dump(A, f, indent=4, ensure_ascii=False) -# print(plate11.get_well(0).tracker.get_used_volume()) - # Initialize the backend and setup the connection + # print(plate11.get_well(0).tracker.get_used_volume()) + # Initialize the backend and setup the connection asyncio.run(handler.transfer_group("water", "master_mix", 10)) # Reset tip tracking - # asyncio.run(handler.pick_up_tips([plate8.children[8]],[0])) # print(plate8.children[8]) # asyncio.run(handler.run_protocol()) @@ -1685,121 +1767,118 @@ if __name__ == "__main__": # print(plate1.children[0]) # asyncio.run(handler.discard_tips([0])) -# asyncio.run(handler.add_liquid( -# asp_vols=[10]*7, -# dis_vols=[10]*7, -# reagent_sources=plate11.children[:7], -# targets=plate1.children[2:9], -# use_channels=[0], -# flow_rates=[None] * 7, -# offsets=[Coordinate(0, 0, 0)] * 7, -# liquid_height=[None] * 7, -# blow_out_air_volume=[None] * 2, -# delays=None, -# mix_time=3, -# mix_vol=5, -# spread="custom", -# )) + # asyncio.run(handler.add_liquid( + # asp_vols=[10]*7, + # dis_vols=[10]*7, + # reagent_sources=plate11.children[:7], + # targets=plate1.children[2:9], + # use_channels=[0], + # flow_rates=[None] * 7, + # offsets=[Coordinate(0, 0, 0)] * 7, + # liquid_height=[None] * 7, + # blow_out_air_volume=[None] * 2, + # delays=None, + # mix_time=3, + # mix_vol=5, + # spread="custom", + # )) # asyncio.run(handler.run_protocol()) # Run the protocol + # # # asyncio.run(handler.transfer_liquid( + # # # asp_vols=[10]*2, + # # # dis_vols=[10]*2, + # # # sources=plate11.children[:2], + # # # targets=plate11.children[-2:], + # # # use_channels=[0], + # # # offsets=[Coordinate(0, 0, 0)] * 4, + # # # liquid_height=[None] * 2, + # # # blow_out_air_volume=[None] * 2, + # # # delays=None, + # # # mix_times=3, + # # # mix_vol=5, + # # # spread="wide", + # # # tip_racks=[plate8] + # # # )) + # # # asyncio.run(handler.remove_liquid( + # # # vols=[10]*2, + # # # sources=plate11.children[:2], + # # # waste_liquid=plate11.children[43], + # # # use_channels=[0], + # # # offsets=[Coordinate(0, 0, 0)] * 4, + # # # liquid_height=[None] * 2, + # # # blow_out_air_volume=[None] * 2, + # # # delays=None, + # # # spread="wide" + # # # )) + # # asyncio.run(handler.run_protocol()) + # # # asyncio.run(handler.discard_tips()) + # # # asyncio.run(handler.mix(well_containers.children[:8 + # # # ], mix_time=3, mix_vol=50, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100)) + # # #print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info -# # # asyncio.run(handler.transfer_liquid( -# # # asp_vols=[10]*2, -# # # dis_vols=[10]*2, -# # # sources=plate11.children[:2], -# # # targets=plate11.children[-2:], -# # # use_channels=[0], -# # # offsets=[Coordinate(0, 0, 0)] * 4, -# # # liquid_height=[None] * 2, -# # # blow_out_air_volume=[None] * 2, -# # # delays=None, -# # # mix_times=3, -# # # mix_vol=5, -# # # spread="wide", -# # # tip_racks=[plate8] -# # # )) - -# # # asyncio.run(handler.remove_liquid( -# # # vols=[10]*2, -# # # sources=plate11.children[:2], -# # # waste_liquid=plate11.children[43], -# # # use_channels=[0], -# # # offsets=[Coordinate(0, 0, 0)] * 4, -# # # liquid_height=[None] * 2, -# # # blow_out_air_volume=[None] * 2, -# # # delays=None, -# # # spread="wide" -# # # )) -# # asyncio.run(handler.run_protocol()) - -# # # asyncio.run(handler.discard_tips()) -# # # asyncio.run(handler.mix(well_containers.children[:8 -# # # ], mix_time=3, mix_vol=50, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100)) -# # #print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info - - -# # # asyncio.run(handler.remove_liquid( -# # # vols=[100]*16, -# # # sources=well_containers.children[-16:], -# # # waste_liquid=well_containers.children[:16], # 这个有些奇怪,但是好像也只能这么写 -# # # use_channels=[0, 1, 2, 3, 4, 5, 6, 7], -# # # flow_rates=[None] * 32, -# # # offsets=[Coordinate(0, 0, 0)] * 32, -# # # liquid_height=[None] * 32, -# # # blow_out_air_volume=[None] * 32, -# # # spread="wide", -# # # )) -# # # asyncio.run(handler.transfer_liquid( -# # # asp_vols=[100]*16, -# # # dis_vols=[100]*16, -# # # tip_racks=[tip_rack], -# # # sources=well_containers.children[-16:], -# # # targets=well_containers.children[:16], -# # # use_channels=[0, 1, 2, 3, 4, 5, 6, 7], -# # # offsets=[Coordinate(0, 0, 0)] * 32, -# # # asp_flow_rates=[None] * 16, -# # # dis_flow_rates=[None] * 16, -# # # liquid_height=[None] * 32, -# # # blow_out_air_volume=[None] * 32, -# # # mix_times=3, -# # # mix_vol=50, -# # # spread="wide", -# # # )) -# # print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info -# # # input("pick_up_tips add step") - #asyncio.run(handler.run_protocol()) # Run the protocol -# # # input("Running protocol...") -# # # input("Press Enter to continue...") # Wait for user input before proceeding -# # # print("PRCXI9300Handler initialized with deck and host settings.") - - -# 一些推荐版位组合的测试样例: - -# 一些推荐版位组合的测试样例: + # # # asyncio.run(handler.remove_liquid( + # # # vols=[100]*16, + # # # sources=well_containers.children[-16:], + # # # waste_liquid=well_containers.children[:16], # 这个有些奇怪,但是好像也只能这么写 + # # # use_channels=[0, 1, 2, 3, 4, 5, 6, 7], + # # # flow_rates=[None] * 32, + # # # offsets=[Coordinate(0, 0, 0)] * 32, + # # # liquid_height=[None] * 32, + # # # blow_out_air_volume=[None] * 32, + # # # spread="wide", + # # # )) + # # # asyncio.run(handler.transfer_liquid( + # # # asp_vols=[100]*16, + # # # dis_vols=[100]*16, + # # # tip_racks=[tip_rack], + # # # sources=well_containers.children[-16:], + # # # targets=well_containers.children[:16], + # # # use_channels=[0, 1, 2, 3, 4, 5, 6, 7], + # # # offsets=[Coordinate(0, 0, 0)] * 32, + # # # asp_flow_rates=[None] * 16, + # # # dis_flow_rates=[None] * 16, + # # # liquid_height=[None] * 32, + # # # blow_out_air_volume=[None] * 32, + # # # mix_times=3, + # # # mix_vol=50, + # # # spread="wide", + # # # )) + # # print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info + # # # input("pick_up_tips add step") + # asyncio.run(handler.run_protocol()) # Run the protocol + # # # input("Running protocol...") + # # # input("Press Enter to continue...") # Wait for user input before proceeding + # # # print("PRCXI9300Handler initialized with deck and host settings.") + # 一些推荐版位组合的测试样例: + # 一些推荐版位组合的测试样例: with open("prcxi_material.json", "r") as f: material_info = json.load(f) layout = DefaultLayout("PRCXI9320") layout.add_lab_resource(material_info) - MatrixLayout_1, dict_1 = layout.recommend_layout([ - ("reagent_1", "96 细胞培养皿", 3), - ("reagent_2", "12道储液槽", 1), - ("reagent_3", "200μL Tip头", 7), - ("reagent_4", "10μL加长 Tip头", 1), - ]) + MatrixLayout_1, dict_1 = layout.recommend_layout( + [ + ("reagent_1", "96 细胞培养皿", 3), + ("reagent_2", "12道储液槽", 1), + ("reagent_3", "200μL Tip头", 7), + ("reagent_4", "10μL加长 Tip头", 1), + ] + ) print(dict_1) - MatrixLayout_2, dict_2 = layout.recommend_layout([ - ("reagent_1", "96深孔板", 4), - ("reagent_2", "12道储液槽", 1), - ("reagent_3", "200μL Tip头", 1), - ("reagent_4", "10μL加长 Tip头", 1), - ]) + MatrixLayout_2, dict_2 = layout.recommend_layout( + [ + ("reagent_1", "96深孔板", 4), + ("reagent_2", "12道储液槽", 1), + ("reagent_3", "200μL Tip头", 1), + ("reagent_4", "10μL加长 Tip头", 1), + ] + ) # with open("prcxi_material.json", "r") as f: # material_info = json.load(f) diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index edf41fb..2fc7ea7 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -53,7 +53,7 @@ from unilabos.ros.nodes.resource_tracker import ( ) from unilabos.ros.x.rclpyx import get_event_loop from unilabos.ros.utils.driver_creator import WorkstationNodeCreator, PyLabRobotCreator, DeviceClassCreator -from rclpy.task import Task +from rclpy.task import Task, Future from unilabos.utils.import_manager import default_manager from unilabos.utils.log import info, debug, warning, error, critical, logger, trace from unilabos.utils.type_check import get_type_class, TypeEncoder, get_result_info_str @@ -555,6 +555,15 @@ class BaseROS2DeviceNode(Node, Generic[T]): rclpy.get_global_executor().add_node(self) self.lab_logger().debug(f"ROS节点初始化完成") + async def sleep(self, rel_time: float, callback_group=None): + if callback_group is None: + callback_group = self.callback_group + await ROS2DeviceNode.async_wait_for(self, rel_time, callback_group) + + @classmethod + async def create_task(cls, func, trace_error=True, **kwargs) -> Task: + return ROS2DeviceNode.run_async_func(func, trace_error, **kwargs) + async def update_resource(self, resources: List["ResourcePLR"]): r = SerialCommand.Request() tree_set = ResourceTreeSet.from_plr_resources(resources) @@ -1399,6 +1408,14 @@ class ROS2DeviceNode: future.add_done_callback(_handle_future_exception) return future + @classmethod + async def async_wait_for(cls, node: Node, wait_time: float, callback_group=None): + future = Future() + timer = node.create_timer(wait_time, lambda : future.set_result(None), callback_group=callback_group, clock=node.get_clock()) + await future + timer.cancel() + node.destroy_timer(timer) + @property def driver_instance(self): return self._driver_instance diff --git a/unilabos/ros/nodes/presets/host_node.py b/unilabos/ros/nodes/presets/host_node.py index 43d16e8..346cf9c 100644 --- a/unilabos/ros/nodes/presets/host_node.py +++ b/unilabos/ros/nodes/presets/host_node.py @@ -18,7 +18,8 @@ from unilabos_msgs.srv import ( ResourceDelete, ResourceUpdate, ResourceList, - SerialCommand, ResourceGet, + SerialCommand, + ResourceGet, ) # type: ignore from unilabos_msgs.srv._serial_command import SerialCommand_Request, SerialCommand_Response from unique_identifier_msgs.msg import UUID From bed453034fab0a60d346d2a361d69d9ba1278d2d Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Mon, 3 Nov 2025 15:49:11 +0800 Subject: [PATCH 062/104] modify devices to use correct executor (sleep, create_task) --- test/registry/example_devices.py | 33 +- unilabos/devices/cnc/grbl_async.py | 11 +- unilabos/devices/cnc/mock.py | 9 +- .../laiyu_liquid/core/laiyu_liquid_main.py | 423 +++++++++--------- .../liquid_handler_abstract.py | 10 +- .../devices/pump_and_valve/runze_async.py | 11 +- .../devices/virtual/virtual_centrifuge.py | 9 +- unilabos/devices/virtual/virtual_column.py | 9 +- unilabos/devices/virtual/virtual_filter.py | 202 ++++----- unilabos/devices/virtual/virtual_heatchill.py | 9 +- unilabos/devices/virtual/virtual_rotavap.py | 273 ++++++----- unilabos/devices/virtual/virtual_separator.py | 11 +- .../devices/virtual/virtual_solenoid_valve.py | 10 +- .../virtual/virtual_solid_dispenser.py | 9 +- unilabos/devices/virtual/virtual_stirrer.py | 11 +- .../devices/virtual/virtual_transferpump.py | 13 +- 16 files changed, 597 insertions(+), 456 deletions(-) diff --git a/test/registry/example_devices.py b/test/registry/example_devices.py index d5b26b2..d41c7b4 100644 --- a/test/registry/example_devices.py +++ b/test/registry/example_devices.py @@ -3,7 +3,8 @@ """ import asyncio -from typing import Dict, Any, Optional, List +from typing import Dict, Any, List +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode class SmartPumpController: @@ -14,6 +15,8 @@ class SmartPumpController: 适用于实验室自动化系统中的液体处理任务。 """ + _ros_node: BaseROS2DeviceNode + def __init__(self, device_id: str = "smart_pump_01", port: str = "/dev/ttyUSB0"): """ 初始化智能泵控制器 @@ -30,6 +33,9 @@ class SmartPumpController: self.calibration_factor = 1.0 self.pump_mode = "continuous" # continuous, volume, rate + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + def connect_device(self, timeout: int = 10) -> bool: """ 连接到泵设备 @@ -90,7 +96,7 @@ class SmartPumpController: pump_time = (volume / flow_rate) * 60 # 转换为秒 self.current_flow_rate = flow_rate - await asyncio.sleep(min(pump_time, 3.0)) # 模拟泵送过程 + await self._ros_node.sleep(min(pump_time, 3.0)) # 模拟泵送过程 self.total_volume_pumped += volume self.current_flow_rate = 0.0 @@ -170,6 +176,8 @@ class AdvancedTemperatureController: 适用于需要精确温度控制的化学反应和材料处理过程。 """ + _ros_node: BaseROS2DeviceNode + def __init__(self, controller_id: str = "temp_controller_01"): """ 初始化温度控制器 @@ -185,6 +193,9 @@ class AdvancedTemperatureController: self.pid_enabled = True self.temperature_history: List[Dict] = [] + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + def set_target_temperature(self, temperature: float, rate: float = 10.0) -> bool: """ 设置目标温度 @@ -238,7 +249,7 @@ class AdvancedTemperatureController: } ) - await asyncio.sleep(step_time) + await self._ros_node.sleep(step_time) # 保持历史记录不超过100条 if len(self.temperature_history) > 100: @@ -330,6 +341,8 @@ class MultiChannelAnalyzer: 常用于光谱分析、电化学测量等应用场景。 """ + _ros_node: BaseROS2DeviceNode + def __init__(self, analyzer_id: str = "analyzer_01", channels: int = 8): """ 初始化多通道分析仪 @@ -344,6 +357,9 @@ class MultiChannelAnalyzer: self.is_measuring = False self.sample_rate = 1000 # Hz + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + def configure_channel(self, channel: int, enabled: bool = True, unit: str = "V") -> bool: """ 配置通道 @@ -376,7 +392,7 @@ class MultiChannelAnalyzer: # 模拟数据采集 measurements = [] - for second in range(duration): + for _ in range(duration): timestamp = asyncio.get_event_loop().time() frame_data = {} @@ -391,7 +407,7 @@ class MultiChannelAnalyzer: measurements.append({"timestamp": timestamp, "data": frame_data}) - await asyncio.sleep(1.0) # 每秒采集一次 + await self._ros_node.sleep(1.0) # 每秒采集一次 self.is_measuring = False @@ -465,6 +481,8 @@ class AutomatedDispenser: 集成称重功能,确保分配精度和重现性。 """ + _ros_node: BaseROS2DeviceNode + def __init__(self, dispenser_id: str = "dispenser_01"): """ 初始化自动分配器 @@ -479,6 +497,9 @@ class AutomatedDispenser: self.container_capacity = 1000.0 # mL self.precision_mode = True + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + def move_to_position(self, x: float, y: float, z: float) -> bool: """ 移动到指定位置 @@ -517,7 +538,7 @@ class AutomatedDispenser: if viscosity == "high": dispense_time *= 2 # 高粘度液体需要更长时间 - await asyncio.sleep(min(dispense_time, 5.0)) # 最多等待5秒 + await self._ros_node.sleep(min(dispense_time, 5.0)) # 最多等待5秒 self.dispensed_total += volume diff --git a/unilabos/devices/cnc/grbl_async.py b/unilabos/devices/cnc/grbl_async.py index 7e5ac7f..3ecd4ba 100644 --- a/unilabos/devices/cnc/grbl_async.py +++ b/unilabos/devices/cnc/grbl_async.py @@ -12,6 +12,7 @@ from serial import Serial from serial.serialutil import SerialException from unilabos.messages import Point3D +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode class GrblCNCConnectionError(Exception): @@ -32,6 +33,7 @@ class GrblCNCInfo: class GrblCNCAsync: _status: str = "Offline" _position: Point3D = Point3D(x=0.0, y=0.0, z=0.0) + _ros_node: BaseROS2DeviceNode def __init__(self, port: str, address: str = "1", limits: tuple[int, int, int, int, int, int] = (-150, 150, -200, 0, 0, 60)): self.port = port @@ -58,6 +60,9 @@ class GrblCNCAsync: self._run_future: Optional[Future[Any]] = None self._run_lock = Lock() + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + def _read_all(self): data = self._serial.read_until(b"\n") data_decoded = data.decode() @@ -148,7 +153,7 @@ class GrblCNCAsync: try: await self._query(command) while True: - await asyncio.sleep(0.2) # Wait for 0.5 seconds before polling again + await self._ros_node.sleep(0.2) # Wait for 0.5 seconds before polling again status = await self.get_status() if "Idle" in status: @@ -214,7 +219,7 @@ class GrblCNCAsync: self._pose_number = i self.pose_number_remaining = len(points) - i await self.set_position(point) - await asyncio.sleep(0.5) + await self._ros_node.sleep(0.5) self._step_number = -1 async def stop_operation(self): @@ -235,7 +240,7 @@ class GrblCNCAsync: async def open(self): if self._read_task: raise GrblCNCConnectionError - self._read_task = asyncio.create_task(self._read_loop()) + self._read_task = self._ros_node.create_task(self._read_loop()) try: await self.get_status() diff --git a/unilabos/devices/cnc/mock.py b/unilabos/devices/cnc/mock.py index b8c52f1..ebe9683 100644 --- a/unilabos/devices/cnc/mock.py +++ b/unilabos/devices/cnc/mock.py @@ -2,6 +2,8 @@ import time import asyncio from pydantic import BaseModel +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class Point3D(BaseModel): x: float @@ -14,9 +16,14 @@ def d(a: Point3D, b: Point3D) -> float: class MockCNCAsync: + _ros_node: BaseROS2DeviceNode["MockCNCAsync"] + def __init__(self): self._position: Point3D = Point3D(x=0.0, y=0.0, z=0.0) self._status = "Idle" + + def post_create(self, ros_node): + self._ros_node = ros_node @property def position(self) -> Point3D: @@ -38,5 +45,5 @@ class MockCNCAsync: self._position.x = current_pos.x + (position.x - current_pos.x) / 20 * (i+1) self._position.y = current_pos.y + (position.y - current_pos.y) / 20 * (i+1) self._position.z = current_pos.z + (position.z - current_pos.z) / 20 * (i+1) - await asyncio.sleep(move_time / 20) + await self._ros_node.sleep(move_time / 20) self._status = "Idle" diff --git a/unilabos/devices/laiyu_liquid/core/laiyu_liquid_main.py b/unilabos/devices/laiyu_liquid/core/laiyu_liquid_main.py index 9609255..f369a20 100644 --- a/unilabos/devices/laiyu_liquid/core/laiyu_liquid_main.py +++ b/unilabos/devices/laiyu_liquid/core/laiyu_liquid_main.py @@ -15,108 +15,113 @@ from typing import List, Optional, Dict, Any, Union, Tuple from dataclasses import dataclass from abc import ABC, abstractmethod +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + # 基础导入 try: from pylabrobot.resources import Deck, Plate, TipRack, Tip, Resource, Well + PYLABROBOT_AVAILABLE = True except ImportError: # 如果 pylabrobot 不可用,创建基础的模拟类 PYLABROBOT_AVAILABLE = False - + class Resource: def __init__(self, name: str): self.name = name - + class Deck(Resource): pass - + class Plate(Resource): pass - + class TipRack(Resource): pass - + class Tip(Resource): pass - + class Well(Resource): pass + # LaiYu_Liquid 控制器导入 try: - from .controllers.pipette_controller import ( - PipetteController, TipStatus, LiquidClass, LiquidParameters - ) - from .controllers.xyz_controller import ( - XYZController, MachineConfig, CoordinateOrigin, MotorAxis - ) + from .controllers.pipette_controller import PipetteController, TipStatus, LiquidClass, LiquidParameters + from .controllers.xyz_controller import XYZController, MachineConfig, CoordinateOrigin, MotorAxis + CONTROLLERS_AVAILABLE = True except ImportError: CONTROLLERS_AVAILABLE = False + # 创建模拟的控制器类 class PipetteController: def __init__(self, *args, **kwargs): pass - + def connect(self): return True - + def initialize(self): return True - + class XYZController: def __init__(self, *args, **kwargs): pass - + def connect_device(self): return True + logger = logging.getLogger(__name__) class LaiYuLiquidError(RuntimeError): """LaiYu_Liquid 设备异常""" + pass @dataclass class LaiYuLiquidConfig: """LaiYu_Liquid 设备配置""" + port: str = "/dev/cu.usbserial-3130" # RS485转USB端口 address: int = 1 # 设备地址 baudrate: int = 9600 # 波特率 timeout: float = 5.0 # 通信超时时间 - + # 工作台尺寸 deck_width: float = 340.0 # 工作台宽度 (mm) deck_height: float = 250.0 # 工作台高度 (mm) deck_depth: float = 160.0 # 工作台深度 (mm) - + # 移液参数 max_volume: float = 1000.0 # 最大体积 (μL) min_volume: float = 0.1 # 最小体积 (μL) - + # 运动参数 max_speed: float = 100.0 # 最大速度 (mm/s) acceleration: float = 50.0 # 加速度 (mm/s²) - + # 安全参数 safe_height: float = 50.0 # 安全高度 (mm) tip_pickup_depth: float = 10.0 # 吸头拾取深度 (mm) liquid_detection: bool = True # 液面检测 - + # 取枪头相关参数 tip_pickup_speed: int = 30 # 取枪头时的移动速度 (rpm) tip_pickup_acceleration: int = 500 # 取枪头时的加速度 (rpm/s) tip_approach_height: float = 5.0 # 接近枪头时的高度 (mm) tip_pickup_force_depth: float = 2.0 # 强制插入深度 (mm) tip_pickup_retract_height: float = 20.0 # 取枪头后的回退高度 (mm) - + # 丢弃枪头相关参数 tip_drop_height: float = 10.0 # 丢弃枪头时的高度 (mm) tip_drop_speed: int = 50 # 丢弃枪头时的移动速度 (rpm) trash_position: Tuple[float, float, float] = (300.0, 200.0, 0.0) # 垃圾桶位置 (mm) - + # 安全范围配置 deck_width: float = 300.0 # 工作台宽度 (mm) deck_height: float = 200.0 # 工作台高度 (mm) @@ -128,25 +133,25 @@ class LaiYuLiquidConfig: class LaiYuLiquidDeck: """LaiYu_Liquid 工作台管理""" - + def __init__(self, config: LaiYuLiquidConfig): self.config = config self.resources: Dict[str, Resource] = {} self.positions: Dict[str, Tuple[float, float, float]] = {} - + def add_resource(self, name: str, resource: Resource, position: Tuple[float, float, float]): """添加资源到工作台""" self.resources[name] = resource self.positions[name] = position - + def get_resource(self, name: str) -> Optional[Resource]: """获取资源""" return self.resources.get(name) - + def get_position(self, name: str) -> Optional[Tuple[float, float, float]]: """获取资源位置""" return self.positions.get(name) - + def list_resources(self) -> List[str]: """列出所有资源""" return list(self.resources.keys()) @@ -154,8 +159,18 @@ class LaiYuLiquidDeck: class LaiYuLiquidContainer: """LaiYu_Liquid 容器类""" - - def __init__(self, name: str, size_x: float = 0, size_y: float = 0, size_z: float = 0, container_type: str = "", volume: float = 0.0, max_volume: float = 1000.0, lid_height: float = 0.0): + + def __init__( + self, + name: str, + size_x: float = 0, + size_y: float = 0, + size_z: float = 0, + container_type: str = "", + volume: float = 0.0, + max_volume: float = 1000.0, + lid_height: float = 0.0, + ): self.name = name self.size_x = size_x self.size_y = size_y @@ -166,19 +181,19 @@ class LaiYuLiquidContainer: self.max_volume = max_volume self.last_updated = time.time() self.child_resources = {} # 存储子资源 - + @property def is_empty(self) -> bool: return self.volume <= 0.0 - + @property def is_full(self) -> bool: return self.volume >= self.max_volume - + @property def available_volume(self) -> float: return max(0.0, self.max_volume - self.volume) - + def add_volume(self, volume: float) -> bool: """添加体积""" if self.volume + volume <= self.max_volume: @@ -186,7 +201,7 @@ class LaiYuLiquidContainer: self.last_updated = time.time() return True return False - + def remove_volume(self, volume: float) -> bool: """移除体积""" if self.volume >= volume: @@ -194,20 +209,25 @@ class LaiYuLiquidContainer: self.last_updated = time.time() return True return False - + def assign_child_resource(self, resource, location=None): """分配子资源 - 与 PyLabRobot 资源管理系统兼容""" - if hasattr(resource, 'name'): - self.child_resources[resource.name] = { - 'resource': resource, - 'location': location - } + if hasattr(resource, "name"): + self.child_resources[resource.name] = {"resource": resource, "location": location} class LaiYuLiquidTipRack: """LaiYu_Liquid 吸头架类""" - - def __init__(self, name: str, size_x: float = 0, size_y: float = 0, size_z: float = 0, tip_count: int = 96, tip_volume: float = 1000.0): + + def __init__( + self, + name: str, + size_x: float = 0, + size_y: float = 0, + size_z: float = 0, + tip_count: int = 96, + tip_volume: float = 1000.0, + ): self.name = name self.size_x = size_x self.size_y = size_y @@ -216,34 +236,31 @@ class LaiYuLiquidTipRack: self.tip_volume = tip_volume self.tips_available = [True] * tip_count self.child_resources = {} # 存储子资源 - + @property def available_tips(self) -> int: return sum(self.tips_available) - + @property def is_empty(self) -> bool: return self.available_tips == 0 - + def pick_tip(self, position: int) -> bool: """拾取吸头""" if 0 <= position < self.tip_count and self.tips_available[position]: self.tips_available[position] = False return True return False - + def has_tip(self, position: int) -> bool: """检查位置是否有吸头""" if 0 <= position < self.tip_count: return self.tips_available[position] return False - + def assign_child_resource(self, resource, location=None): """分配子资源到指定位置""" - self.child_resources[resource.name] = { - 'resource': resource, - 'location': location - } + self.child_resources[resource.name] = {"resource": resource, "location": location} def get_module_info(): @@ -253,36 +270,32 @@ def get_module_info(): "version": "1.0.0", "description": "LaiYu液体处理工作站模块,提供移液器控制、XYZ轴控制和资源管理功能", "author": "UniLabOS Team", - "capabilities": [ - "移液器控制", - "XYZ轴运动控制", - "吸头架管理", - "板和容器管理", - "资源位置管理" - ], - "dependencies": { - "required": ["serial"], - "optional": ["pylabrobot"] - } + "capabilities": ["移液器控制", "XYZ轴运动控制", "吸头架管理", "板和容器管理", "资源位置管理"], + "dependencies": {"required": ["serial"], "optional": ["pylabrobot"]}, } class LaiYuLiquidBackend: """LaiYu_Liquid 硬件通信后端""" - - def __init__(self, config: LaiYuLiquidConfig, deck: Optional['LaiYuLiquidDeck'] = None): + + _ros_node: BaseROS2DeviceNode + + def __init__(self, config: LaiYuLiquidConfig, deck: Optional["LaiYuLiquidDeck"] = None): self.config = config self.deck = deck # 工作台引用,用于获取资源位置信息 self.pipette_controller = None self.xyz_controller = None self.is_connected = False self.is_initialized = False - + # 状态跟踪 self.current_position = (0.0, 0.0, 0.0) self.tip_attached = False self.current_volume = 0.0 - + + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + def _validate_position(self, x: float, y: float, z: float) -> bool: """验证位置是否在安全范围内""" try: @@ -290,71 +303,71 @@ class LaiYuLiquidBackend: if not (0 <= x <= self.config.deck_width): logger.error(f"X轴位置 {x:.2f}mm 超出范围 [0, {self.config.deck_width}]") return False - + # 检查Y轴范围 if not (0 <= y <= self.config.deck_height): logger.error(f"Y轴位置 {y:.2f}mm 超出范围 [0, {self.config.deck_height}]") return False - + # 检查Z轴范围(负值表示向下,0为工作台表面) if not (-self.config.deck_depth <= z <= self.config.safe_height): logger.error(f"Z轴位置 {z:.2f}mm 超出安全范围 [{-self.config.deck_depth}, {self.config.safe_height}]") return False - + return True except Exception as e: logger.error(f"位置验证失败: {e}") return False - + def _check_hardware_ready(self) -> bool: """检查硬件是否准备就绪""" if not self.is_connected: logger.error("设备未连接") return False - + if CONTROLLERS_AVAILABLE: if self.xyz_controller is None: logger.error("XYZ控制器未初始化") return False - + return True - + async def emergency_stop(self) -> bool: """紧急停止所有运动""" try: logger.warning("执行紧急停止") - + if CONTROLLERS_AVAILABLE and self.xyz_controller: # 停止XYZ控制器 await self.xyz_controller.stop_all_motion() logger.info("XYZ控制器已停止") - + if self.pipette_controller: # 停止移液器控制器 await self.pipette_controller.stop() logger.info("移液器控制器已停止") - + return True except Exception as e: logger.error(f"紧急停止失败: {e}") return False - + async def move_to_safe_position(self) -> bool: """移动到安全位置""" try: if not self._check_hardware_ready(): return False - + safe_position = ( self.config.deck_width / 2, # 工作台中心X self.config.deck_height / 2, # 工作台中心Y - self.config.safe_height # 安全高度Z + self.config.safe_height, # 安全高度Z ) - + if not self._validate_position(*safe_position): logger.error("安全位置无效") return False - + if CONTROLLERS_AVAILABLE and self.xyz_controller: await self.xyz_controller.move_to_work_coord(*safe_position) self.current_position = safe_position @@ -365,33 +378,28 @@ class LaiYuLiquidBackend: self.current_position = safe_position logger.info("模拟移动到安全位置") return True - + except Exception as e: logger.error(f"移动到安全位置失败: {e}") return False - + async def setup(self) -> bool: """设置硬件连接""" try: if CONTROLLERS_AVAILABLE: # 初始化移液器控制器 - self.pipette_controller = PipetteController( - port=self.config.port, - address=self.config.address - ) - + self.pipette_controller = PipetteController(port=self.config.port, address=self.config.address) + # 初始化XYZ控制器 machine_config = MachineConfig() self.xyz_controller = XYZController( - port=self.config.port, - baudrate=self.config.baudrate, - machine_config=machine_config + port=self.config.port, baudrate=self.config.baudrate, machine_config=machine_config ) - + # 连接设备 pipette_connected = await asyncio.to_thread(self.pipette_controller.connect) xyz_connected = await asyncio.to_thread(self.xyz_controller.connect_device) - + if pipette_connected and xyz_connected: self.is_connected = True logger.info("LaiYu_Liquid 硬件连接成功") @@ -404,124 +412,123 @@ class LaiYuLiquidBackend: logger.info("LaiYu_Liquid 运行在模拟模式") self.is_connected = True return True - + except Exception as e: logger.error(f"LaiYu_Liquid 设置失败: {e}") return False - + async def stop(self): """停止设备""" try: - if self.pipette_controller and hasattr(self.pipette_controller, 'disconnect'): + if self.pipette_controller and hasattr(self.pipette_controller, "disconnect"): await asyncio.to_thread(self.pipette_controller.disconnect) - - if self.xyz_controller and hasattr(self.xyz_controller, 'disconnect'): + + if self.xyz_controller and hasattr(self.xyz_controller, "disconnect"): await asyncio.to_thread(self.xyz_controller.disconnect) - + self.is_connected = False self.is_initialized = False logger.info("LaiYu_Liquid 已停止") - + except Exception as e: logger.error(f"LaiYu_Liquid 停止失败: {e}") - + async def move_to(self, x: float, y: float, z: float) -> bool: """移动到指定位置""" try: if not self.is_connected: raise LaiYuLiquidError("设备未连接") - + # 模拟移动 - await asyncio.sleep(0.1) # 模拟移动时间 + await self._ros_node.sleep(0.1) # 模拟移动时间 self.current_position = (x, y, z) logger.debug(f"移动到位置: ({x}, {y}, {z})") return True - + except Exception as e: logger.error(f"移动失败: {e}") return False - + async def pick_up_tip(self, tip_rack: str, position: int) -> bool: """拾取吸头 - 包含真正的Z轴下降控制""" try: # 硬件准备检查 if not self._check_hardware_ready(): return False - + if self.tip_attached: logger.warning("已有吸头附着,无法拾取新吸头") return False - + logger.info(f"开始从 {tip_rack} 位置 {position} 拾取吸头") - + # 获取枪头架位置信息 if self.deck is None: logger.error("工作台未初始化") return False - + tip_position = self.deck.get_position(tip_rack) if tip_position is None: logger.error(f"未找到枪头架 {tip_rack} 的位置信息") return False - + # 计算具体枪头位置(这里简化处理,实际应根据position计算偏移) tip_x, tip_y, tip_z = tip_position - + # 验证所有关键位置的安全性 safe_z = tip_z + self.config.tip_approach_height pickup_z = tip_z - self.config.tip_pickup_force_depth retract_z = tip_z + self.config.tip_pickup_retract_height - - if not (self._validate_position(tip_x, tip_y, safe_z) and - self._validate_position(tip_x, tip_y, pickup_z) and - self._validate_position(tip_x, tip_y, retract_z)): + + if not ( + self._validate_position(tip_x, tip_y, safe_z) + and self._validate_position(tip_x, tip_y, pickup_z) + and self._validate_position(tip_x, tip_y, retract_z) + ): logger.error("枪头拾取位置超出安全范围") return False - + if CONTROLLERS_AVAILABLE and self.xyz_controller: # 真实硬件控制流程 logger.info("使用真实XYZ控制器进行枪头拾取") - + try: # 1. 移动到枪头上方的安全位置 safe_z = tip_z + self.config.tip_approach_height logger.info(f"移动到枪头上方安全位置: ({tip_x:.2f}, {tip_y:.2f}, {safe_z:.2f})") move_success = await asyncio.to_thread( - self.xyz_controller.move_to_work_coord, - tip_x, tip_y, safe_z + self.xyz_controller.move_to_work_coord, tip_x, tip_y, safe_z ) if not move_success: logger.error("移动到枪头上方失败") return False - + # 2. Z轴下降到枪头位置 pickup_z = tip_z - self.config.tip_pickup_force_depth logger.info(f"Z轴下降到枪头拾取位置: {pickup_z:.2f}mm") z_down_success = await asyncio.to_thread( - self.xyz_controller.move_to_work_coord, - tip_x, tip_y, pickup_z + self.xyz_controller.move_to_work_coord, tip_x, tip_y, pickup_z ) if not z_down_success: logger.error("Z轴下降到枪头位置失败") return False - + # 3. 等待一小段时间确保枪头牢固附着 - await asyncio.sleep(0.2) - + await self._ros_node.sleep(0.2) + # 4. Z轴上升到回退高度 retract_z = tip_z + self.config.tip_pickup_retract_height logger.info(f"Z轴上升到回退高度: {retract_z:.2f}mm") z_up_success = await asyncio.to_thread( - self.xyz_controller.move_to_work_coord, - tip_x, tip_y, retract_z + self.xyz_controller.move_to_work_coord, tip_x, tip_y, retract_z ) if not z_up_success: logger.error("Z轴上升失败") return False - + # 5. 更新当前位置 self.current_position = (tip_x, tip_y, retract_z) - + except Exception as move_error: logger.error(f"枪头拾取过程中发生错误: {move_error}") # 尝试移动到安全位置 @@ -529,35 +536,35 @@ class LaiYuLiquidBackend: await self.emergency_stop() await self.move_to_safe_position() return False - + else: # 模拟模式 logger.info("模拟模式:执行枪头拾取动作") - await asyncio.sleep(1.0) # 模拟整个拾取过程的时间 + await self._ros_node.sleep(1.0) # 模拟整个拾取过程的时间 self.current_position = (tip_x, tip_y, tip_z + self.config.tip_pickup_retract_height) - + # 6. 标记枪头已附着 self.tip_attached = True logger.info("吸头拾取成功") return True - + except Exception as e: logger.error(f"拾取吸头失败: {e}") return False - + async def drop_tip(self, location: str = "trash") -> bool: """丢弃吸头 - 包含真正的Z轴控制""" try: # 硬件准备检查 if not self._check_hardware_ready(): return False - + if not self.tip_attached: logger.warning("没有吸头附着,无需丢弃") return True - + logger.info(f"开始丢弃吸头到 {location}") - + # 确定丢弃位置 if location == "trash": # 使用配置中的垃圾桶位置 @@ -567,48 +574,48 @@ class LaiYuLiquidBackend: if self.deck is None: logger.error("工作台未初始化") return False - + drop_position = self.deck.get_position(location) if drop_position is None: logger.error(f"未找到丢弃位置 {location} 的信息") return False drop_x, drop_y, drop_z = drop_position - + # 验证丢弃位置的安全性 safe_z = drop_z + self.config.safe_height drop_height_z = drop_z + self.config.tip_drop_height - - if not (self._validate_position(drop_x, drop_y, safe_z) and - self._validate_position(drop_x, drop_y, drop_height_z)): + + if not ( + self._validate_position(drop_x, drop_y, safe_z) + and self._validate_position(drop_x, drop_y, drop_height_z) + ): logger.error("枪头丢弃位置超出安全范围") return False - + if CONTROLLERS_AVAILABLE and self.xyz_controller: # 真实硬件控制流程 logger.info("使用真实XYZ控制器进行枪头丢弃") - + try: # 1. 移动到丢弃位置上方的安全高度 safe_z = drop_z + self.config.tip_drop_height logger.info(f"移动到丢弃位置上方: ({drop_x:.2f}, {drop_y:.2f}, {safe_z:.2f})") move_success = await asyncio.to_thread( - self.xyz_controller.move_to_work_coord, - drop_x, drop_y, safe_z + self.xyz_controller.move_to_work_coord, drop_x, drop_y, safe_z ) if not move_success: logger.error("移动到丢弃位置上方失败") return False - + # 2. Z轴下降到丢弃高度 logger.info(f"Z轴下降到丢弃高度: {drop_z:.2f}mm") z_down_success = await asyncio.to_thread( - self.xyz_controller.move_to_work_coord, - drop_x, drop_y, drop_z + self.xyz_controller.move_to_work_coord, drop_x, drop_y, drop_z ) if not z_down_success: logger.error("Z轴下降到丢弃位置失败") return False - + # 3. 执行枪头弹出动作(如果有移液器控制器) if self.pipette_controller: try: @@ -617,23 +624,22 @@ class LaiYuLiquidBackend: logger.info("执行枪头弹出命令") except Exception as e: logger.warning(f"枪头弹出命令失败: {e}") - + # 4. 等待一小段时间确保枪头完全脱离 - await asyncio.sleep(0.3) - + await self._ros_node.sleep(0.3) + # 5. Z轴上升到安全高度 logger.info(f"Z轴上升到安全高度: {safe_z:.2f}mm") z_up_success = await asyncio.to_thread( - self.xyz_controller.move_to_work_coord, - drop_x, drop_y, safe_z + self.xyz_controller.move_to_work_coord, drop_x, drop_y, safe_z ) if not z_up_success: logger.error("Z轴上升失败") return False - + # 6. 更新当前位置 self.current_position = (drop_x, drop_y, safe_z) - + except Exception as drop_error: logger.error(f"枪头丢弃过程中发生错误: {drop_error}") # 尝试移动到安全位置 @@ -641,63 +647,63 @@ class LaiYuLiquidBackend: await self.emergency_stop() await self.move_to_safe_position() return False - + else: # 模拟模式 logger.info("模拟模式:执行枪头丢弃动作") - await asyncio.sleep(0.8) # 模拟整个丢弃过程的时间 + await self._ros_node.sleep(0.8) # 模拟整个丢弃过程的时间 self.current_position = (drop_x, drop_y, drop_z + self.config.tip_drop_height) - + # 7. 标记枪头已脱离,清空体积 self.tip_attached = False self.current_volume = 0.0 logger.info("吸头丢弃成功") return True - + except Exception as e: logger.error(f"丢弃吸头失败: {e}") return False - + async def aspirate(self, volume: float, location: str) -> bool: """吸取液体""" try: if not self.is_connected: raise LaiYuLiquidError("设备未连接") - + if not self.tip_attached: raise LaiYuLiquidError("没有吸头附着") - + if volume <= 0 or volume > self.config.max_volume: raise LaiYuLiquidError(f"体积超出范围: {volume}") - + # 模拟吸取 - await asyncio.sleep(0.3) + await self._ros_node.sleep(0.3) self.current_volume += volume logger.debug(f"从 {location} 吸取 {volume} μL") return True - + except Exception as e: logger.error(f"吸取失败: {e}") return False - + async def dispense(self, volume: float, location: str) -> bool: """分配液体""" try: if not self.is_connected: raise LaiYuLiquidError("设备未连接") - + if not self.tip_attached: raise LaiYuLiquidError("没有吸头附着") - + if volume <= 0 or volume > self.current_volume: raise LaiYuLiquidError(f"分配体积无效: {volume}") - + # 模拟分配 - await asyncio.sleep(0.3) + await self._ros_node.sleep(0.3) self.current_volume -= volume logger.debug(f"向 {location} 分配 {volume} μL") return True - + except Exception as e: logger.error(f"分配失败: {e}") return False @@ -705,7 +711,7 @@ class LaiYuLiquidBackend: class LaiYuLiquid: """LaiYu_Liquid 主要接口类""" - + def __init__(self, config: Optional[LaiYuLiquidConfig] = None, **kwargs): # 如果传入了关键字参数,创建配置对象 if kwargs and config is None: @@ -717,37 +723,37 @@ class LaiYuLiquid: self.config = LaiYuLiquidConfig(**config_params) else: self.config = config or LaiYuLiquidConfig() - + # 先创建deck,然后传递给backend self.deck = LaiYuLiquidDeck(self.config) self.backend = LaiYuLiquidBackend(self.config, self.deck) self.is_setup = False - + @property def current_position(self) -> Tuple[float, float, float]: """获取当前位置""" return self.backend.current_position - + @property def current_volume(self) -> float: """获取当前体积""" return self.backend.current_volume - + @property def is_connected(self) -> bool: """获取连接状态""" return self.backend.is_connected - + @property def is_initialized(self) -> bool: """获取初始化状态""" return self.backend.is_initialized - + @property def tip_attached(self) -> bool: """获取吸头附着状态""" return self.backend.tip_attached - + async def setup(self) -> bool: """设置液体处理器""" try: @@ -759,27 +765,28 @@ class LaiYuLiquid: except Exception as e: logger.error(f"LaiYu_Liquid 设置失败: {e}") return False - + async def stop(self): """停止液体处理器""" await self.backend.stop() self.is_setup = False - - async def transfer(self, source: str, target: str, volume: float, - tip_rack: str = "tip_rack_1", tip_position: int = 0) -> bool: + + async def transfer( + self, source: str, target: str, volume: float, tip_rack: str = "tip_rack_1", tip_position: int = 0 + ) -> bool: """液体转移""" try: if not self.is_setup: raise LaiYuLiquidError("设备未设置") - + # 获取源和目标位置 source_pos = self.deck.get_position(source) target_pos = self.deck.get_position(target) tip_pos = self.deck.get_position(tip_rack) - + if not all([source_pos, target_pos, tip_pos]): raise LaiYuLiquidError("位置信息不完整") - + # 执行转移步骤 steps = [ ("移动到吸头架", self.backend.move_to(*tip_pos)), @@ -788,22 +795,22 @@ class LaiYuLiquid: ("吸取液体", self.backend.aspirate(volume, source)), ("移动到目标位置", self.backend.move_to(*target_pos)), ("分配液体", self.backend.dispense(volume, target)), - ("丢弃吸头", self.backend.drop_tip()) + ("丢弃吸头", self.backend.drop_tip()), ] - + for step_name, step_coro in steps: logger.debug(f"执行步骤: {step_name}") success = await step_coro if not success: raise LaiYuLiquidError(f"步骤失败: {step_name}") - + logger.info(f"液体转移完成: {source} -> {target}, {volume} μL") return True - + except Exception as e: logger.error(f"液体转移失败: {e}") return False - + def add_resource(self, name: str, resource_type: str, position: Tuple[float, float, float]): """添加资源到工作台""" if resource_type == "plate": @@ -812,9 +819,9 @@ class LaiYuLiquid: resource = TipRack(name) else: resource = Resource(name) - + self.deck.add_resource(name, resource, position) - + def get_status(self) -> Dict[str, Any]: """获取设备状态""" return { @@ -823,59 +830,59 @@ class LaiYuLiquid: "current_position": self.backend.current_position, "tip_attached": self.backend.tip_attached, "current_volume": self.backend.current_volume, - "resources": self.deck.list_resources() + "resources": self.deck.list_resources(), } def create_quick_setup() -> LaiYuLiquidDeck: """ 创建快速设置的LaiYu液体处理工作站 - + Returns: LaiYuLiquidDeck: 配置好的工作台实例 """ # 创建默认配置 config = LaiYuLiquidConfig() - + # 创建工作台 deck = LaiYuLiquidDeck(config) - + # 导入资源创建函数 try: from .laiyu_liquid_res import ( create_tip_rack_1000ul, create_tip_rack_200ul, create_96_well_plate, - create_waste_container + create_waste_container, ) - + # 添加基本资源 tip_rack_1000 = create_tip_rack_1000ul("tip_rack_1000") tip_rack_200 = create_tip_rack_200ul("tip_rack_200") plate_96 = create_96_well_plate("plate_96") waste = create_waste_container("waste") - + # 添加到工作台 deck.add_resource("tip_rack_1000", tip_rack_1000, (50, 50, 0)) deck.add_resource("tip_rack_200", tip_rack_200, (150, 50, 0)) deck.add_resource("plate_96", plate_96, (250, 50, 0)) deck.add_resource("waste", waste, (50, 150, 0)) - + except ImportError: # 如果资源模块不可用,创建空的工作台 logger.warning("资源模块不可用,创建空的工作台") - + return deck __all__ = [ "LaiYuLiquid", - "LaiYuLiquidBackend", + "LaiYuLiquidBackend", "LaiYuLiquidConfig", "LaiYuLiquidDeck", "LaiYuLiquidContainer", "LaiYuLiquidTipRack", "LaiYuLiquidError", "create_quick_setup", - "get_module_info" -] \ No newline at end of file + "get_module_info", +] diff --git a/unilabos/devices/liquid_handling/liquid_handler_abstract.py b/unilabos/devices/liquid_handling/liquid_handler_abstract.py index 69b757b..32e370f 100644 --- a/unilabos/devices/liquid_handling/liquid_handler_abstract.py +++ b/unilabos/devices/liquid_handling/liquid_handler_abstract.py @@ -25,6 +25,8 @@ from pylabrobot.resources import ( Tip, ) +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class LiquidHandlerMiddleware(LiquidHandler): def __init__(self, backend: LiquidHandlerBackend, deck: Deck, simulator: bool = False, channel_num: int = 8): @@ -536,6 +538,7 @@ class LiquidHandlerMiddleware(LiquidHandler): class LiquidHandlerAbstract(LiquidHandlerMiddleware): """Extended LiquidHandler with additional operations.""" support_touch_tip = True + _ros_node: BaseROS2DeviceNode def __init__(self, backend: LiquidHandlerBackend, deck: Deck, simulator: bool=False, channel_num:int = 8): """Initialize a LiquidHandler. @@ -548,8 +551,11 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware): self.group_info = dict() super().__init__(backend, deck, simulator, channel_num) + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + @classmethod - def set_liquid(self, wells: list[Well], liquid_names: list[str], volumes: list[float]): + def set_liquid(cls, wells: list[Well], liquid_names: list[str], volumes: list[float]): """Set the liquid in a well.""" for well, liquid_name, volume in zip(wells, liquid_names, volumes): well.set_liquids([(liquid_name, volume)]) # type: ignore @@ -1081,7 +1087,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware): print(f"Waiting time: {msg}") print(f"Current time: {time.strftime('%H:%M:%S')}") print(f"Time to finish: {time.strftime('%H:%M:%S', time.localtime(time.time() + seconds))}") - await asyncio.sleep(seconds) + await self._ros_node.sleep(seconds) if msg: print(f"Done: {msg}") print(f"Current time: {time.strftime('%H:%M:%S')}") diff --git a/unilabos/devices/pump_and_valve/runze_async.py b/unilabos/devices/pump_and_valve/runze_async.py index 9b8d649..7bc1115 100644 --- a/unilabos/devices/pump_and_valve/runze_async.py +++ b/unilabos/devices/pump_and_valve/runze_async.py @@ -8,6 +8,8 @@ import serial.tools.list_ports from serial import Serial from serial.serialutil import SerialException +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class RunzeSyringePumpMode(Enum): Normal = 0 @@ -77,6 +79,8 @@ class RunzeSyringePumpInfo: class RunzeSyringePumpAsync: + _ros_node: BaseROS2DeviceNode + def __init__(self, port: str, address: str = "1", volume: float = 25000, mode: RunzeSyringePumpMode = None): self.port = port self.address = address @@ -102,6 +106,9 @@ class RunzeSyringePumpAsync: self._run_future: Optional[Future[Any]] = None self._run_lock = Lock() + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + def _adjust_total_steps(self): self.total_steps = 6000 if self.mode == RunzeSyringePumpMode.Normal else 48000 self.total_steps_vel = 48000 if self.mode == RunzeSyringePumpMode.AccuratePosVel else 6000 @@ -182,7 +189,7 @@ class RunzeSyringePumpAsync: try: await self._query(command) while True: - await asyncio.sleep(0.5) # Wait for 0.5 seconds before polling again + await self._ros_node.sleep(0.5) # Wait for 0.5 seconds before polling again status = await self.query_device_status() if status == '`': @@ -364,7 +371,7 @@ class RunzeSyringePumpAsync: if self._read_task: raise RunzeSyringePumpConnectionError - self._read_task = asyncio.create_task(self._read_loop()) + self._read_task = self._ros_node.create_task(self._read_loop()) try: await self.query_device_status() diff --git a/unilabos/devices/virtual/virtual_centrifuge.py b/unilabos/devices/virtual/virtual_centrifuge.py index 79f9dce..afce45a 100644 --- a/unilabos/devices/virtual/virtual_centrifuge.py +++ b/unilabos/devices/virtual/virtual_centrifuge.py @@ -3,9 +3,13 @@ import logging import time as time_module from typing import Dict, Any, Optional +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class VirtualCentrifuge: """Virtual centrifuge device - 简化版,只保留核心功能""" + + _ros_node: BaseROS2DeviceNode def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs): # 处理可能的不同调用方式 @@ -32,6 +36,9 @@ class VirtualCentrifuge: for key, value in kwargs.items(): if key not in skip_keys and not hasattr(self, key): setattr(self, key, value) + + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node async def initialize(self) -> bool: """Initialize virtual centrifuge""" @@ -132,7 +139,7 @@ class VirtualCentrifuge: break # 每秒更新一次 - await asyncio.sleep(1.0) + await self._ros_node.sleep(1.0) # 离心完成 self.data.update({ diff --git a/unilabos/devices/virtual/virtual_column.py b/unilabos/devices/virtual/virtual_column.py index 892a320..539f302 100644 --- a/unilabos/devices/virtual/virtual_column.py +++ b/unilabos/devices/virtual/virtual_column.py @@ -2,9 +2,13 @@ import asyncio import logging from typing import Dict, Any, Optional +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class VirtualColumn: """Virtual column device for RunColumn protocol 🏛️""" + _ros_node: BaseROS2DeviceNode + def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs): # 处理可能的不同调用方式 if device_id is None and 'id' in kwargs: @@ -28,6 +32,9 @@ class VirtualColumn: print(f"🏛️ === 虚拟色谱柱 {self.device_id} 已创建 === ✨") print(f"📏 柱参数: 流速={self._max_flow_rate}mL/min | 长度={self._column_length}cm | 直径={self._column_diameter}cm 🔬") + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + async def initialize(self) -> bool: """Initialize virtual column 🚀""" self.logger.info(f"🔧 初始化虚拟色谱柱 {self.device_id} ✨") @@ -101,7 +108,7 @@ class VirtualColumn: step_time = separation_time / steps for i in range(steps): - await asyncio.sleep(step_time) + await self._ros_node.sleep(step_time) progress = (i + 1) / steps * 100 volume_processed = (i + 1) * 5.0 # 假设每步处理5mL diff --git a/unilabos/devices/virtual/virtual_filter.py b/unilabos/devices/virtual/virtual_filter.py index ffd8f54..98effc9 100644 --- a/unilabos/devices/virtual/virtual_filter.py +++ b/unilabos/devices/virtual/virtual_filter.py @@ -4,70 +4,76 @@ import time as time_module from typing import Dict, Any, Optional from unilabos.compile.utils.vessel_parser import get_vessel +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode class VirtualFilter: """Virtual filter device - 完全按照 Filter.action 规范 🌊""" - + + _ros_node: BaseROS2DeviceNode + def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs): - if device_id is None and 'id' in kwargs: - device_id = kwargs.pop('id') - if config is None and 'config' in kwargs: - config = kwargs.pop('config') - + if device_id is None and "id" in kwargs: + device_id = kwargs.pop("id") + if config is None and "config" in kwargs: + config = kwargs.pop("config") + self.device_id = device_id or "unknown_filter" self.config = config or {} self.logger = logging.getLogger(f"VirtualFilter.{self.device_id}") self.data = {} - + # 从config或kwargs中获取配置参数 - self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL') - self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 100.0) - self._max_stir_speed = self.config.get('max_stir_speed') or kwargs.get('max_stir_speed', 1000.0) - self._max_volume = self.config.get('max_volume') or kwargs.get('max_volume', 500.0) - + self.port = self.config.get("port") or kwargs.get("port", "VIRTUAL") + self._max_temp = self.config.get("max_temp") or kwargs.get("max_temp", 100.0) + self._max_stir_speed = self.config.get("max_stir_speed") or kwargs.get("max_stir_speed", 1000.0) + self._max_volume = self.config.get("max_volume") or kwargs.get("max_volume", 500.0) + # 处理其他kwargs参数 - skip_keys = {'port', 'max_temp', 'max_stir_speed', 'max_volume'} + skip_keys = {"port", "max_temp", "max_stir_speed", "max_volume"} for key, value in kwargs.items(): if key not in skip_keys and not hasattr(self, key): setattr(self, key, value) - + + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + async def initialize(self) -> bool: """Initialize virtual filter 🚀""" self.logger.info(f"🔧 初始化虚拟过滤器 {self.device_id} ✨") - + # 按照 Filter.action 的 feedback 字段初始化 - self.data.update({ - "status": "Idle", - "progress": 0.0, # Filter.action feedback - "current_temp": 25.0, # Filter.action feedback - "filtered_volume": 0.0, # Filter.action feedback - "message": "Ready for filtration" - }) - + self.data.update( + { + "status": "Idle", + "progress": 0.0, # Filter.action feedback + "current_temp": 25.0, # Filter.action feedback + "filtered_volume": 0.0, # Filter.action feedback + "message": "Ready for filtration", + } + ) + self.logger.info(f"✅ 过滤器 {self.device_id} 初始化完成 🌊") return True - + async def cleanup(self) -> bool: """Cleanup virtual filter 🧹""" self.logger.info(f"🧹 清理虚拟过滤器 {self.device_id} 🔚") - - self.data.update({ - "status": "Offline" - }) - + + self.data.update({"status": "Offline"}) + self.logger.info(f"✅ 过滤器 {self.device_id} 清理完成 💤") return True - + async def filter( - self, + self, vessel: dict, filtrate_vessel: dict = {}, - stir: bool = False, - stir_speed: float = 300.0, - temp: float = 25.0, - continue_heatchill: bool = False, - volume: float = 0.0 + stir: bool = False, + stir_speed: float = 300.0, + temp: float = 25.0, + continue_heatchill: bool = False, + volume: float = 0.0, ) -> bool: """Execute filter action - 完全按照 Filter.action 参数 🌊""" vessel_id, _ = get_vessel(vessel) @@ -79,59 +85,52 @@ class VirtualFilter: temp = 25.0 # 0度自动设置为室温 self.logger.info(f"🌡️ 温度自动调整: {original_temp}°C → {temp}°C (室温) 🏠") elif temp < 4.0: - temp = 4.0 # 小于4度自动设置为4度 + temp = 4.0 # 小于4度自动设置为4度 self.logger.info(f"🌡️ 温度自动调整: {original_temp}°C → {temp}°C (最低温度) ❄️") - + self.logger.info(f"🌊 开始过滤操作: {vessel_id} → {filtrate_vessel_id} 🚰") self.logger.info(f" 🌪️ 搅拌: {stir} ({stir_speed} RPM)") self.logger.info(f" 🌡️ 温度: {temp}°C") self.logger.info(f" 💧 体积: {volume}mL") self.logger.info(f" 🔥 保持加热: {continue_heatchill}") - + # 验证参数 if temp > self._max_temp or temp < 4.0: error_msg = f"🌡️ 温度 {temp}°C 超出范围 (4-{self._max_temp}°C) ⚠️" self.logger.error(f"❌ {error_msg}") - self.data.update({ - "status": f"Error: 温度超出范围 ⚠️", - "message": error_msg - }) + self.data.update({"status": f"Error: 温度超出范围 ⚠️", "message": error_msg}) return False - + if stir and stir_speed > self._max_stir_speed: error_msg = f"🌪️ 搅拌速度 {stir_speed} RPM 超出范围 (0-{self._max_stir_speed} RPM) ⚠️" self.logger.error(f"❌ {error_msg}") - self.data.update({ - "status": f"Error: 搅拌速度超出范围 ⚠️", - "message": error_msg - }) + self.data.update({"status": f"Error: 搅拌速度超出范围 ⚠️", "message": error_msg}) return False - + if volume > self._max_volume: error_msg = f"💧 过滤体积 {volume} mL 超出范围 (0-{self._max_volume} mL) ⚠️" self.logger.error(f"❌ {error_msg}") - self.data.update({ - "status": f"Error", - "message": error_msg - }) + self.data.update({"status": f"Error", "message": error_msg}) return False - + # 开始过滤 filter_volume = volume if volume > 0 else 50.0 self.logger.info(f"🚀 开始过滤 {filter_volume}mL 液体 💧") - - self.data.update({ - "status": f"Running", - "current_temp": temp, - "filtered_volume": 0.0, - "progress": 0.0, - "message": f"🚀 Starting filtration: {vessel_id} → {filtrate_vessel_id}" - }) - + + self.data.update( + { + "status": f"Running", + "current_temp": temp, + "filtered_volume": 0.0, + "progress": 0.0, + "message": f"🚀 Starting filtration: {vessel_id} → {filtrate_vessel_id}", + } + ) + try: # 过滤过程 - 实时更新进度 start_time = time_module.time() - + # 根据体积和搅拌估算过滤时间 base_time = filter_volume / 5.0 # 5mL/s 基础速度 if stir: @@ -140,78 +139,79 @@ class VirtualFilter: if temp > 50.0: base_time *= 0.7 # 高温加速过滤 self.logger.info(f"🔥 高温加速过滤,预计时间减少30% ⚡") - + filter_time = max(base_time, 10.0) # 最少10秒 self.logger.info(f"⏱️ 预计过滤时间: {filter_time:.1f}秒 ⌛") - + while True: current_time = time_module.time() elapsed = current_time - start_time remaining = max(0, filter_time - elapsed) progress = min(100.0, (elapsed / filter_time) * 100) current_filtered = (progress / 100.0) * filter_volume - + # 更新状态 - 按照 Filter.action feedback 字段 status_msg = f"🌊 过滤中: {vessel}" if stir: status_msg += f" | 🌪️ 搅拌: {stir_speed} RPM" status_msg += f" | 🌡️ {temp}°C | 📊 {progress:.1f}% | 💧 已过滤: {current_filtered:.1f}mL" - - self.data.update({ - "progress": progress, # Filter.action feedback - "current_temp": temp, # Filter.action feedback - "filtered_volume": current_filtered, # Filter.action feedback - "status": "Running", - "message": f"🌊 Filtering: {progress:.1f}% complete, {current_filtered:.1f}mL filtered" - }) - + + self.data.update( + { + "progress": progress, # Filter.action feedback + "current_temp": temp, # Filter.action feedback + "filtered_volume": current_filtered, # Filter.action feedback + "status": "Running", + "message": f"🌊 Filtering: {progress:.1f}% complete, {current_filtered:.1f}mL filtered", + } + ) + # 进度日志(每25%打印一次) if progress >= 25 and progress % 25 < 1: self.logger.info(f"📊 过滤进度: {progress:.0f}% | 💧 {current_filtered:.1f}mL 完成 ✨") - + if remaining <= 0: break - - await asyncio.sleep(1.0) - + + await self._ros_node.sleep(1.0) + # 过滤完成 final_temp = temp if continue_heatchill else 25.0 final_status = f"✅ 过滤完成: {vessel} | 💧 {filter_volume}mL → {filtrate_vessel}" if continue_heatchill: final_status += " | 🔥 继续加热搅拌" self.logger.info(f"🔥 继续保持加热搅拌状态 🌪️") - - self.data.update({ - "status": final_status, - "progress": 100.0, # Filter.action feedback - "current_temp": final_temp, # Filter.action feedback - "filtered_volume": filter_volume, # Filter.action feedback - "message": f"✅ Filtration completed: {filter_volume}mL filtered from {vessel_id}" - }) - + + self.data.update( + { + "status": final_status, + "progress": 100.0, # Filter.action feedback + "current_temp": final_temp, # Filter.action feedback + "filtered_volume": filter_volume, # Filter.action feedback + "message": f"✅ Filtration completed: {filter_volume}mL filtered from {vessel_id}", + } + ) + self.logger.info(f"🎉 过滤完成! 💧 {filter_volume}mL 从 {vessel_id} 过滤到 {filtrate_vessel_id} ✨") self.logger.info(f"📊 最终状态: 温度 {final_temp}°C | 进度 100% | 体积 {filter_volume}mL 🏁") return True - + except Exception as e: error_msg = f"过滤过程中发生错误: {str(e)} 💥" self.logger.error(f"❌ {error_msg}") - self.data.update({ - "status": f"Error", - "message": f"❌ Filtration failed: {str(e)}" - }) + self.data.update({"status": f"Error", "message": f"❌ Filtration failed: {str(e)}"}) return False - + # === 核心状态属性 - 按照 Filter.action feedback 字段 === @property def status(self) -> str: return self.data.get("status", "❓ Unknown") - + @property def progress(self) -> float: """Filter.action feedback 字段 📊""" return self.data.get("progress", 0.0) - + @property def current_temp(self) -> float: """Filter.action feedback 字段 🌡️""" @@ -230,15 +230,15 @@ class VirtualFilter: @property def message(self) -> str: return self.data.get("message", "") - + @property def max_temp(self) -> float: return self._max_temp - + @property def max_stir_speed(self) -> float: return self._max_stir_speed - + @property def max_volume(self) -> float: - return self._max_volume \ No newline at end of file + return self._max_volume diff --git a/unilabos/devices/virtual/virtual_heatchill.py b/unilabos/devices/virtual/virtual_heatchill.py index 2f7e555..29a9fd2 100644 --- a/unilabos/devices/virtual/virtual_heatchill.py +++ b/unilabos/devices/virtual/virtual_heatchill.py @@ -3,9 +3,13 @@ import logging import time as time_module # 重命名time模块,避免与参数冲突 from typing import Dict, Any +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class VirtualHeatChill: """Virtual heat chill device for HeatChillProtocol testing 🌡️""" + _ros_node: BaseROS2DeviceNode + def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs): # 处理可能的不同调用方式 if device_id is None and 'id' in kwargs: @@ -35,6 +39,9 @@ class VirtualHeatChill: print(f"🌡️ === 虚拟温控设备 {self.device_id} 已创建 === ✨") print(f"🔥 温度范围: {self._min_temp}°C ~ {self._max_temp}°C | 🌪️ 最大搅拌: {self._max_stir_speed} RPM") + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + async def initialize(self) -> bool: """Initialize virtual heat chill 🚀""" self.logger.info(f"🔧 初始化虚拟温控设备 {self.device_id} ✨") @@ -177,7 +184,7 @@ class VirtualHeatChill: break # 等待1秒后再次检查 - await asyncio.sleep(1.0) + await self._ros_node.sleep(1.0) # 操作完成 final_stir_info = f" | 🌪️ 搅拌: {stir_speed} RPM" if stir else "" diff --git a/unilabos/devices/virtual/virtual_rotavap.py b/unilabos/devices/virtual/virtual_rotavap.py index 23e24b7..5e85d35 100644 --- a/unilabos/devices/virtual/virtual_rotavap.py +++ b/unilabos/devices/virtual/virtual_rotavap.py @@ -3,13 +3,19 @@ import logging import time as time_module from typing import Dict, Any, Optional +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + + def debug_print(message): """调试输出 🔍""" print(f"🌪️ [ROTAVAP] {message}", flush=True) + class VirtualRotavap: """Virtual rotary evaporator device - 简化版,只保留核心功能 🌪️""" + _ros_node: BaseROS2DeviceNode + def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs): # 处理可能的不同调用方式 if device_id is None and "id" in kwargs: @@ -38,56 +44,65 @@ class VirtualRotavap: print(f"🌪️ === 虚拟旋转蒸发仪 {self.device_id} 已创建 === ✨") print(f"🔥 温度范围: 10°C ~ {self._max_temp}°C | 🌀 转速范围: 10 ~ {self._max_rotation_speed} RPM") + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + async def initialize(self) -> bool: """Initialize virtual rotary evaporator 🚀""" self.logger.info(f"🔧 初始化虚拟旋转蒸发仪 {self.device_id} ✨") - + # 只保留核心状态 - self.data.update({ - "status": "🏠 待机中", - "rotavap_state": "Ready", # Ready, Evaporating, Completed, Error - "current_temp": 25.0, - "target_temp": 25.0, - "rotation_speed": 0.0, - "vacuum_pressure": 1.0, # 大气压 - "evaporated_volume": 0.0, - "progress": 0.0, - "remaining_time": 0.0, - "message": "🌪️ Ready for evaporation" - }) - + self.data.update( + { + "status": "🏠 待机中", + "rotavap_state": "Ready", # Ready, Evaporating, Completed, Error + "current_temp": 25.0, + "target_temp": 25.0, + "rotation_speed": 0.0, + "vacuum_pressure": 1.0, # 大气压 + "evaporated_volume": 0.0, + "progress": 0.0, + "remaining_time": 0.0, + "message": "🌪️ Ready for evaporation", + } + ) + self.logger.info(f"✅ 旋转蒸发仪 {self.device_id} 初始化完成 🌪️") - self.logger.info(f"📊 设备规格: 温度范围 10°C ~ {self._max_temp}°C | 转速范围 10 ~ {self._max_rotation_speed} RPM") + self.logger.info( + f"📊 设备规格: 温度范围 10°C ~ {self._max_temp}°C | 转速范围 10 ~ {self._max_rotation_speed} RPM" + ) return True async def cleanup(self) -> bool: """Cleanup virtual rotary evaporator 🧹""" self.logger.info(f"🧹 清理虚拟旋转蒸发仪 {self.device_id} 🔚") - - self.data.update({ - "status": "💤 离线", - "rotavap_state": "Offline", - "current_temp": 25.0, - "rotation_speed": 0.0, - "vacuum_pressure": 1.0, - "message": "💤 System offline" - }) - + + self.data.update( + { + "status": "💤 离线", + "rotavap_state": "Offline", + "current_temp": 25.0, + "rotation_speed": 0.0, + "vacuum_pressure": 1.0, + "message": "💤 System offline", + } + ) + self.logger.info(f"✅ 旋转蒸发仪 {self.device_id} 清理完成 💤") return True async def evaporate( - self, - vessel: str, - pressure: float = 0.1, - temp: float = 60.0, + self, + vessel: str, + pressure: float = 0.1, + temp: float = 60.0, time: float = 180.0, stir_speed: float = 100.0, solvent: str = "", - **kwargs + **kwargs, ) -> bool: """Execute evaporate action - 简化版 🌪️""" - + # 🔧 新增:确保time参数是数值类型 if isinstance(time, str): try: @@ -98,31 +113,31 @@ class VirtualRotavap: elif not isinstance(time, (int, float)): self.logger.error(f"❌ 时间参数类型无效: {type(time)},使用默认值180.0秒") time = 180.0 - + # 确保time是float类型; 并加速 time = float(time) / 10.0 - + # 🔧 简化处理:如果vessel就是设备自己,直接操作 if vessel == self.device_id: debug_print(f"🎯 在设备 {self.device_id} 上直接执行蒸发操作") actual_vessel = self.device_id else: actual_vessel = vessel - + # 参数预处理 if solvent: self.logger.info(f"🧪 识别到溶剂: {solvent}") # 根据溶剂调整参数 solvent_lower = solvent.lower() - if any(s in solvent_lower for s in ['water', 'aqueous']): + if any(s in solvent_lower for s in ["water", "aqueous"]): temp = max(temp, 80.0) pressure = max(pressure, 0.2) self.logger.info(f"💧 水系溶剂:调整参数 → 温度 {temp}°C, 压力 {pressure} bar") - elif any(s in solvent_lower for s in ['ethanol', 'methanol', 'acetone']): + elif any(s in solvent_lower for s in ["ethanol", "methanol", "acetone"]): temp = min(temp, 50.0) pressure = min(pressure, 0.05) self.logger.info(f"⚡ 易挥发溶剂:调整参数 → 温度 {temp}°C, 压力 {pressure} bar") - + self.logger.info(f"🌪️ 开始蒸发操作: {actual_vessel}") self.logger.info(f" 🥽 容器: {actual_vessel}") self.logger.info(f" 🌡️ 温度: {temp}°C") @@ -131,126 +146,140 @@ class VirtualRotavap: self.logger.info(f" 🌀 转速: {stir_speed} RPM") if solvent: self.logger.info(f" 🧪 溶剂: {solvent}") - + # 验证参数 if temp > self._max_temp or temp < 10.0: error_msg = f"🌡️ 温度 {temp}°C 超出范围 (10-{self._max_temp}°C) ⚠️" self.logger.error(f"❌ {error_msg}") - self.data.update({ - "status": f"❌ 错误: 温度超出范围", - "rotavap_state": "Error", - "current_temp": 25.0, - "progress": 0.0, - "evaporated_volume": 0.0, - "message": error_msg - }) + self.data.update( + { + "status": f"❌ 错误: 温度超出范围", + "rotavap_state": "Error", + "current_temp": 25.0, + "progress": 0.0, + "evaporated_volume": 0.0, + "message": error_msg, + } + ) return False if stir_speed > self._max_rotation_speed or stir_speed < 10.0: error_msg = f"🌀 旋转速度 {stir_speed} RPM 超出范围 (10-{self._max_rotation_speed} RPM) ⚠️" self.logger.error(f"❌ {error_msg}") - self.data.update({ - "status": f"❌ 错误: 转速超出范围", - "rotavap_state": "Error", - "current_temp": 25.0, - "progress": 0.0, - "evaporated_volume": 0.0, - "message": error_msg - }) + self.data.update( + { + "status": f"❌ 错误: 转速超出范围", + "rotavap_state": "Error", + "current_temp": 25.0, + "progress": 0.0, + "evaporated_volume": 0.0, + "message": error_msg, + } + ) return False if pressure < 0.01 or pressure > 1.0: error_msg = f"💨 真空度 {pressure} bar 超出范围 (0.01-1.0 bar) ⚠️" self.logger.error(f"❌ {error_msg}") - self.data.update({ - "status": f"❌ 错误: 压力超出范围", - "rotavap_state": "Error", - "current_temp": 25.0, - "progress": 0.0, - "evaporated_volume": 0.0, - "message": error_msg - }) + self.data.update( + { + "status": f"❌ 错误: 压力超出范围", + "rotavap_state": "Error", + "current_temp": 25.0, + "progress": 0.0, + "evaporated_volume": 0.0, + "message": error_msg, + } + ) return False # 开始蒸发 - 🔧 现在time已经确保是float类型 self.logger.info(f"🚀 启动蒸发程序! 预计用时 {time/60:.1f}分钟 ⏱️") - - self.data.update({ - "status": f"🌪️ 蒸发中: {actual_vessel}", - "rotavap_state": "Evaporating", - "current_temp": temp, - "target_temp": temp, - "rotation_speed": stir_speed, - "vacuum_pressure": pressure, - "remaining_time": time, - "progress": 0.0, - "evaporated_volume": 0.0, - "message": f"🌪️ Evaporating {actual_vessel} at {temp}°C, {pressure} bar, {stir_speed} RPM" - }) + + self.data.update( + { + "status": f"🌪️ 蒸发中: {actual_vessel}", + "rotavap_state": "Evaporating", + "current_temp": temp, + "target_temp": temp, + "rotation_speed": stir_speed, + "vacuum_pressure": pressure, + "remaining_time": time, + "progress": 0.0, + "evaporated_volume": 0.0, + "message": f"🌪️ Evaporating {actual_vessel} at {temp}°C, {pressure} bar, {stir_speed} RPM", + } + ) try: # 蒸发过程 - 实时更新进度 start_time = time_module.time() total_time = time last_logged_progress = 0 - + while True: current_time = time_module.time() elapsed = current_time - start_time remaining = max(0, total_time - elapsed) progress = min(100.0, (elapsed / total_time) * 100) - + # 模拟蒸发体积 - 根据溶剂类型调整 - if solvent and any(s in solvent.lower() for s in ['water', 'aqueous']): + if solvent and any(s in solvent.lower() for s in ["water", "aqueous"]): evaporated_vol = progress * 0.6 # 水系溶剂蒸发慢 - elif solvent and any(s in solvent.lower() for s in ['ethanol', 'methanol', 'acetone']): + elif solvent and any(s in solvent.lower() for s in ["ethanol", "methanol", "acetone"]): evaporated_vol = progress * 1.0 # 易挥发溶剂蒸发快 else: evaporated_vol = progress * 0.8 # 默认蒸发量 - + # 🔧 更新状态 - 确保包含所有必需字段 status_msg = f"🌪️ 蒸发中: {actual_vessel} | 🌡️ {temp}°C | 💨 {pressure} bar | 🌀 {stir_speed} RPM | 📊 {progress:.1f}% | ⏰ 剩余: {remaining:.0f}s" - - self.data.update({ - "remaining_time": remaining, - "progress": progress, - "evaporated_volume": evaporated_vol, - "current_temp": temp, - "status": status_msg, - "message": f"🌪️ Evaporating: {progress:.1f}% complete, 💧 {evaporated_vol:.1f}mL evaporated, ⏰ {remaining:.0f}s remaining" - }) - + + self.data.update( + { + "remaining_time": remaining, + "progress": progress, + "evaporated_volume": evaporated_vol, + "current_temp": temp, + "status": status_msg, + "message": f"🌪️ Evaporating: {progress:.1f}% complete, 💧 {evaporated_vol:.1f}mL evaporated, ⏰ {remaining:.0f}s remaining", + } + ) + # 进度日志(每25%打印一次) if progress >= 25 and int(progress) % 25 == 0 and int(progress) != last_logged_progress: - self.logger.info(f"📊 蒸发进度: {progress:.0f}% | 💧 已蒸发: {evaporated_vol:.1f}mL | ⏰ 剩余: {remaining:.0f}s ✨") + self.logger.info( + f"📊 蒸发进度: {progress:.0f}% | 💧 已蒸发: {evaporated_vol:.1f}mL | ⏰ 剩余: {remaining:.0f}s ✨" + ) last_logged_progress = int(progress) - + # 时间到了,退出循环 if remaining <= 0: break - + # 每秒更新一次 - await asyncio.sleep(1.0) - + await self._ros_node.sleep(1.0) + # 蒸发完成 - if solvent and any(s in solvent.lower() for s in ['water', 'aqueous']): + if solvent and any(s in solvent.lower() for s in ["water", "aqueous"]): final_evaporated = 60.0 # 水系溶剂 - elif solvent and any(s in solvent.lower() for s in ['ethanol', 'methanol', 'acetone']): + elif solvent and any(s in solvent.lower() for s in ["ethanol", "methanol", "acetone"]): final_evaporated = 100.0 # 易挥发溶剂 else: final_evaporated = 80.0 # 默认 - - self.data.update({ - "status": f"✅ 蒸发完成: {actual_vessel} | 💧 蒸发量: {final_evaporated:.1f}mL", - "rotavap_state": "Completed", - "evaporated_volume": final_evaporated, - "progress": 100.0, - "current_temp": temp, - "remaining_time": 0.0, - "rotation_speed": 0.0, - "vacuum_pressure": 1.0, - "message": f"✅ Evaporation completed: {final_evaporated}mL evaporated from {actual_vessel}" - }) + + self.data.update( + { + "status": f"✅ 蒸发完成: {actual_vessel} | 💧 蒸发量: {final_evaporated:.1f}mL", + "rotavap_state": "Completed", + "evaporated_volume": final_evaporated, + "progress": 100.0, + "current_temp": temp, + "remaining_time": 0.0, + "rotation_speed": 0.0, + "vacuum_pressure": 1.0, + "message": f"✅ Evaporation completed: {final_evaporated}mL evaporated from {actual_vessel}", + } + ) self.logger.info(f"🎉 蒸发操作完成! ✨") self.logger.info(f"📊 蒸发结果:") @@ -262,24 +291,26 @@ class VirtualRotavap: self.logger.info(f" ⏱️ 总用时: {total_time:.0f}s") if solvent: self.logger.info(f" 🧪 处理溶剂: {solvent} 🏁") - + return True except Exception as e: # 出错处理 error_msg = f"蒸发过程中发生错误: {str(e)} 💥" self.logger.error(f"❌ {error_msg}") - - self.data.update({ - "status": f"❌ 蒸发错误: {str(e)}", - "rotavap_state": "Error", - "current_temp": 25.0, - "progress": 0.0, - "evaporated_volume": 0.0, - "rotation_speed": 0.0, - "vacuum_pressure": 1.0, - "message": f"❌ Evaporation failed: {str(e)}" - }) + + self.data.update( + { + "status": f"❌ 蒸发错误: {str(e)}", + "rotavap_state": "Error", + "current_temp": 25.0, + "progress": 0.0, + "evaporated_volume": 0.0, + "rotation_speed": 0.0, + "vacuum_pressure": 1.0, + "message": f"❌ Evaporation failed: {str(e)}", + } + ) return False # === 核心状态属性 === diff --git a/unilabos/devices/virtual/virtual_separator.py b/unilabos/devices/virtual/virtual_separator.py index e1c4612..0f266ce 100644 --- a/unilabos/devices/virtual/virtual_separator.py +++ b/unilabos/devices/virtual/virtual_separator.py @@ -2,9 +2,13 @@ import asyncio import logging from typing import Dict, Any, Optional +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class VirtualSeparator: """Virtual separator device for SeparateProtocol testing""" + + _ros_node: BaseROS2DeviceNode def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs): # 处理可能的不同调用方式 @@ -35,6 +39,9 @@ class VirtualSeparator: for key, value in kwargs.items(): if key not in skip_keys and not hasattr(self, key): setattr(self, key, value) + + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node async def initialize(self) -> bool: """Initialize virtual separator""" @@ -119,14 +126,14 @@ class VirtualSeparator: for repeat in range(repeats): # 搅拌阶段 for progress in range(0, 51, 10): - await asyncio.sleep(simulation_time / (repeats * 10)) + await self._ros_node.sleep(simulation_time / (repeats * 10)) overall_progress = ((repeat * 100) + (progress * 0.5)) / repeats self.data["progress"] = overall_progress self.data["message"] = f"第{repeat+1}次分离 - 搅拌中 ({progress}%)" # 静置分相阶段 for progress in range(50, 101, 10): - await asyncio.sleep(simulation_time / (repeats * 10)) + await self._ros_node.sleep(simulation_time / (repeats * 10)) overall_progress = ((repeat * 100) + (progress * 0.5)) / repeats self.data["progress"] = overall_progress self.data["message"] = f"第{repeat+1}次分离 - 静置分相中 ({progress}%)" diff --git a/unilabos/devices/virtual/virtual_solenoid_valve.py b/unilabos/devices/virtual/virtual_solenoid_valve.py index e019424..26970cb 100644 --- a/unilabos/devices/virtual/virtual_solenoid_valve.py +++ b/unilabos/devices/virtual/virtual_solenoid_valve.py @@ -2,11 +2,16 @@ import time import asyncio from typing import Union +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class VirtualSolenoidValve: """ 虚拟电磁阀门 - 简单的开关型阀门,只有开启和关闭两个状态 """ + + _ros_node: BaseROS2DeviceNode + def __init__(self, device_id: str = None, config: dict = None, **kwargs): # 从配置中获取参数,提供默认值 if config is None: @@ -21,6 +26,9 @@ class VirtualSolenoidValve: self._status = "Idle" self._valve_state = "Closed" # "Open" or "Closed" self._is_open = False + + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node async def initialize(self) -> bool: """初始化设备""" @@ -63,7 +71,7 @@ class VirtualSolenoidValve: self._status = "Busy" # 模拟阀门响应时间 - await asyncio.sleep(self.response_time) + await self._ros_node.sleep(self.response_time) # 处理不同的命令格式 if isinstance(command, str): diff --git a/unilabos/devices/virtual/virtual_solid_dispenser.py b/unilabos/devices/virtual/virtual_solid_dispenser.py index f8c14a7..6318261 100644 --- a/unilabos/devices/virtual/virtual_solid_dispenser.py +++ b/unilabos/devices/virtual/virtual_solid_dispenser.py @@ -3,6 +3,8 @@ import logging import re from typing import Dict, Any, Optional +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class VirtualSolidDispenser: """ 虚拟固体粉末加样器 - 用于处理 Add Protocol 中的固体试剂添加 ⚗️ @@ -13,6 +15,8 @@ class VirtualSolidDispenser: - 简单反馈:成功/失败 + 消息 📊 """ + _ros_node: BaseROS2DeviceNode + def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs): self.device_id = device_id or "virtual_solid_dispenser" self.config = config or {} @@ -32,6 +36,9 @@ class VirtualSolidDispenser: print(f"⚗️ === 虚拟固体分配器 {self.device_id} 创建成功! === ✨") print(f"📊 设备规格: 最大容量 {self.max_capacity}g | 精度 {self.precision}g 🎯") + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + async def initialize(self) -> bool: """初始化固体加样器 🚀""" self.logger.info(f"🔧 初始化固体分配器 {self.device_id} ✨") @@ -263,7 +270,7 @@ class VirtualSolidDispenser: for i in range(steps): progress = (i + 1) / steps * 100 - await asyncio.sleep(step_time) + await self._ros_node.sleep(step_time) if i % 2 == 0: # 每隔一步显示进度 self.logger.debug(f"📊 加样进度: {progress:.0f}% | {amount_emoji} 正在分配 {reagent}...") diff --git a/unilabos/devices/virtual/virtual_stirrer.py b/unilabos/devices/virtual/virtual_stirrer.py index cccf61e..8e95617 100644 --- a/unilabos/devices/virtual/virtual_stirrer.py +++ b/unilabos/devices/virtual/virtual_stirrer.py @@ -3,9 +3,13 @@ import logging import time as time_module from typing import Dict, Any +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class VirtualStirrer: """Virtual stirrer device for StirProtocol testing - 功能完整版 🌪️""" + _ros_node: BaseROS2DeviceNode + def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs): # 处理可能的不同调用方式 if device_id is None and 'id' in kwargs: @@ -34,6 +38,9 @@ class VirtualStirrer: print(f"🌪️ === 虚拟搅拌器 {self.device_id} 已创建 === ✨") print(f"🔧 速度范围: {self._min_speed} ~ {self._max_speed} RPM | 📱 端口: {self.port}") + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + async def initialize(self) -> bool: """Initialize virtual stirrer 🚀""" self.logger.info(f"🔧 初始化虚拟搅拌器 {self.device_id} ✨") @@ -134,7 +141,7 @@ class VirtualStirrer: if remaining <= 0: break - await asyncio.sleep(1.0) + await self._ros_node.sleep(1.0) self.logger.info(f"✅ 搅拌阶段完成! 🌪️ {stir_speed} RPM × {stir_time}s") @@ -176,7 +183,7 @@ class VirtualStirrer: if remaining <= 0: break - await asyncio.sleep(1.0) + await self._ros_node.sleep(1.0) self.logger.info(f"✅ 沉降阶段完成! 🛑 静置 {settling_time}s") diff --git a/unilabos/devices/virtual/virtual_transferpump.py b/unilabos/devices/virtual/virtual_transferpump.py index 1187db5..7b8eea8 100644 --- a/unilabos/devices/virtual/virtual_transferpump.py +++ b/unilabos/devices/virtual/virtual_transferpump.py @@ -4,6 +4,8 @@ from enum import Enum from typing import Union, Optional import logging +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class VirtualPumpMode(Enum): Normal = 0 @@ -14,6 +16,8 @@ class VirtualPumpMode(Enum): class VirtualTransferPump: """虚拟转移泵类 - 模拟泵的基本功能,无需实际硬件 🚰""" + _ros_node: BaseROS2DeviceNode + def __init__(self, device_id: str = None, config: dict = None, **kwargs): """ 初始化虚拟转移泵 @@ -53,6 +57,9 @@ class VirtualTransferPump: print(f"💨 快速模式: {'启用' if self._fast_mode else '禁用'} | 移动时间: {self._fast_move_time}s | 喷射时间: {self._fast_dispense_time}s") print(f"📊 最大容量: {self.max_volume}mL | 端口: {self.port}") + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + async def initialize(self) -> bool: """初始化虚拟泵 🚀""" self.logger.info(f"🔧 初始化虚拟转移泵 {self.device_id} ✨") @@ -104,7 +111,7 @@ class VirtualTransferPump: async def _simulate_operation(self, duration: float): """模拟操作延时 ⏱️""" self._status = "Busy" - await asyncio.sleep(duration) + await self._ros_node.sleep(duration) self._status = "Idle" def _calculate_duration(self, volume: float, velocity: float = None) -> float: @@ -223,7 +230,7 @@ class VirtualTransferPump: # 等待一小步时间 if i < steps and step_duration > 0: - await asyncio.sleep(step_duration) + await self._ros_node.sleep(step_duration) else: # 移动距离很小,直接完成 self._position = target_position @@ -341,7 +348,7 @@ class VirtualTransferPump: # 短暂停顿 self.logger.debug("⏸️ 短暂停顿...") - await asyncio.sleep(0.1) + await self._ros_node.sleep(0.1) # 排液 await self.dispense(volume, dispense_velocity) From 6d934e354cd6d47967da9f521e1a2ddccbd8f413 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:31:37 +0800 Subject: [PATCH 063/104] adjust with_children param --- unilabos/ros/nodes/base_device_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index 2fc7ea7..19503fd 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -656,7 +656,7 @@ class BaseROS2DeviceNode(Node, Generic[T]): ].call_async( SerialCommand.Request( command=json.dumps( - {"data": {"data": resources_uuid, "with_children": False}, "action": "get"} + {"data": {"data": resources_uuid, "with_children": True if action == "add" else "update"}, "action": "get"} ) ) ) # type: ignore From 86c7880b5c11b7d5c5052a38a3d9c6680ed10d04 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Fri, 7 Nov 2025 14:46:27 +0800 Subject: [PATCH 064/104] disable slave connect websocket --- unilabos/app/main.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/unilabos/app/main.py b/unilabos/app/main.py index c646518..db15e2c 100644 --- a/unilabos/app/main.py +++ b/unilabos/app/main.py @@ -375,22 +375,23 @@ def main(): args_dict["bridges"] = [] - # 获取通信客户端(仅支持WebSocket) - comm_client = get_communication_client() - - if "websocket" in args_dict["app_bridges"]: - args_dict["bridges"].append(comm_client) if "fastapi" in args_dict["app_bridges"]: args_dict["bridges"].append(http_client) - if "websocket" in args_dict["app_bridges"]: + # 获取通信客户端(仅支持WebSocket) + if BasicConfig.is_host_mode: + comm_client = get_communication_client() + if "websocket" in args_dict["app_bridges"]: + args_dict["bridges"].append(comm_client) + def _exit(signum, frame): + comm_client.stop() + sys.exit(0) - def _exit(signum, frame): - comm_client.stop() - sys.exit(0) + signal.signal(signal.SIGINT, _exit) + signal.signal(signal.SIGTERM, _exit) + comm_client.start() + else: + print_status("SlaveMode跳过Websocket连接") - signal.signal(signal.SIGINT, _exit) - signal.signal(signal.SIGTERM, _exit) - comm_client.start() args_dict["resources_mesh_config"] = {} args_dict["resources_edge_config"] = resource_edge_info # web visiualize 2D From 83854a741df4befebae46029a38c95690545f901 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Fri, 7 Nov 2025 14:52:09 +0800 Subject: [PATCH 065/104] correct remove_resource stats --- unilabos/ros/nodes/resource_tracker.py | 41 ++++++++++++++++++++------ 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/unilabos/ros/nodes/resource_tracker.py b/unilabos/ros/nodes/resource_tracker.py index c958fe7..fc3ca7a 100644 --- a/unilabos/ros/nodes/resource_tracker.py +++ b/unilabos/ros/nodes/resource_tracker.py @@ -2,7 +2,7 @@ import traceback import uuid from pydantic import BaseModel, field_serializer, field_validator from pydantic import Field -from typing import List, Tuple, Any, Dict, Literal, Optional, cast, TYPE_CHECKING +from typing import List, Tuple, Any, Dict, Literal, Optional, cast, TYPE_CHECKING, Union from unilabos.utils.log import logger @@ -927,7 +927,7 @@ class DeviceNodeResourceTracker(object): """ 递归遍历资源树,更新所有节点的uuid - Args:0 + Args: resource: 资源对象(可以是dict或实例) uuid_map: uuid映射字典,{old_uuid: new_uuid} @@ -952,6 +952,27 @@ class DeviceNodeResourceTracker(object): return self._traverse_and_process(resource, process) + def loop_gather_uuid(self, resource) -> List[str]: + """ + 递归遍历资源树,收集所有节点的uuid + + Args: + resource: 资源对象(可以是dict或实例) + + Returns: + 收集到的uuid列表 + """ + uuid_list = [] + + def process(res): + current_uuid = self._get_resource_attr(res, "uuid", "unilabos_uuid") + if current_uuid: + uuid_list.append(current_uuid) + return 0 + + self._traverse_and_process(resource, process) + return uuid_list + def _collect_uuid_mapping(self, resource): """ 递归收集资源的 uuid 映射到 uuid_to_resources @@ -972,7 +993,7 @@ class DeviceNodeResourceTracker(object): self._traverse_and_process(resource, process) - def _remove_uuid_mapping(self, resource): + def _remove_uuid_mapping(self, resource) -> int: """ 递归清除资源的 uuid 映射 @@ -985,9 +1006,10 @@ class DeviceNodeResourceTracker(object): if current_uuid and current_uuid in self.uuid_to_resources: self.uuid_to_resources.pop(current_uuid) logger.debug(f"移除资源UUID映射: {current_uuid} -> {res}") + return 1 return 0 - self._traverse_and_process(resource, process) + return self._traverse_and_process(resource, process) def parent_resource(self, resource): if id(resource) in self.resource2parent_resource: @@ -1042,13 +1064,12 @@ class DeviceNodeResourceTracker(object): removed = True break - if not removed: + # 递归清除uuid映射 + count = self._remove_uuid_mapping(resource) + if not count: logger.warning(f"尝试移除不存在的资源: {resource}") return False - # 递归清除uuid映射 - self._remove_uuid_mapping(resource) - # 清除 resource2parent_resource 中与该资源相关的映射 # 需要清除:1) 该资源作为 key 的映射 2) 该资源作为 value 的映射 keys_to_remove = [] @@ -1071,7 +1092,9 @@ class DeviceNodeResourceTracker(object): self.uuid_to_resources.clear() self.resource2parent_resource.clear() - def figure_resource(self, query_resource, try_mode=False): + def figure_resource( + self, query_resource: Union[List[Union[dict, "PLRResource"]], dict, "PLRResource"], try_mode=False + ) -> Union[List[Union[dict, "PLRResource", List[Union[dict, "PLRResource"]]]], dict, "PLRResource"]: if isinstance(query_resource, list): return [self.figure_resource(r, try_mode) for r in query_resource] elif ( From baa107c2300579a1f64aa678b47f9e68b0845486 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Fri, 7 Nov 2025 15:04:53 +0800 Subject: [PATCH 066/104] change uuid logger to trace level --- .../devices/liquid_handling/liquid_handler_abstract.py | 10 +++++----- unilabos/ros/nodes/resource_tracker.py | 8 +++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/unilabos/devices/liquid_handling/liquid_handler_abstract.py b/unilabos/devices/liquid_handling/liquid_handler_abstract.py index 32e370f..6aefaf3 100644 --- a/unilabos/devices/liquid_handling/liquid_handler_abstract.py +++ b/unilabos/devices/liquid_handling/liquid_handler_abstract.py @@ -1,11 +1,11 @@ from __future__ import annotations -import re -import traceback -from typing import List, Sequence, Optional, Literal, Union, Iterator, Dict, Any, Callable, Set, cast -from collections import Counter + import asyncio import time -import pprint as pp +import traceback +from collections import Counter +from typing import List, Sequence, Optional, Literal, Union, Iterator, Dict, Any, Callable, Set, cast + from pylabrobot.liquid_handling import LiquidHandler, LiquidHandlerBackend, LiquidHandlerChatterboxBackend, Strictness from pylabrobot.liquid_handling.liquid_handler import TipPresenceProbingMethod from pylabrobot.liquid_handling.standard import GripDirection diff --git a/unilabos/ros/nodes/resource_tracker.py b/unilabos/ros/nodes/resource_tracker.py index fc3ca7a..085ce02 100644 --- a/unilabos/ros/nodes/resource_tracker.py +++ b/unilabos/ros/nodes/resource_tracker.py @@ -894,7 +894,7 @@ class DeviceNodeResourceTracker(object): new_uuid = name_to_uuid_map[resource_name] self.set_resource_uuid(res, new_uuid) self.uuid_to_resources[new_uuid] = res - logger.debug(f"设置资源UUID: {resource_name} -> {new_uuid}") + logger.trace(f"设置资源UUID: {resource_name} -> {new_uuid}") return 1 return 0 @@ -917,7 +917,8 @@ class DeviceNodeResourceTracker(object): if resource_name and resource_name in name_to_extra_map: extra = name_to_extra_map[resource_name] self.set_resource_extra(res, extra) - logger.debug(f"设置资源Extra: {resource_name} -> {extra}") + if len(extra): + logger.debug(f"设置资源Extra: {resource_name} -> {extra}") return 1 return 0 @@ -986,9 +987,10 @@ class DeviceNodeResourceTracker(object): if current_uuid: old = self.uuid_to_resources.get(current_uuid) self.uuid_to_resources[current_uuid] = res - logger.debug( + logger.trace( f"收集资源UUID映射: {current_uuid} -> {res} {'' if old is None else f'(覆盖旧值: {old})'}" ) + return 1 return 0 self._traverse_and_process(resource, process) From af2fb7f34a65eed7fe68c8e01c6cde56b6559c52 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Fri, 7 Nov 2025 16:11:27 +0800 Subject: [PATCH 067/104] enable slave mode --- unilabos/app/backend.py | 4 +- unilabos/app/register.py | 5 +- unilabos/ros/main_slave_run.py | 130 +++++++++++++----------- unilabos/ros/nodes/presets/host_node.py | 21 ++-- unilabos/ros/nodes/resource_tracker.py | 2 +- unilabos/utils/log.py | 3 +- 6 files changed, 90 insertions(+), 75 deletions(-) diff --git a/unilabos/app/backend.py b/unilabos/app/backend.py index d43b954..b2bc0af 100644 --- a/unilabos/app/backend.py +++ b/unilabos/app/backend.py @@ -13,7 +13,7 @@ def start_backend( graph=None, controllers_config: dict = {}, bridges=[], - without_host: bool = False, + is_slave: bool = False, visual: str = "None", resources_mesh_config: dict = {}, **kwargs, @@ -32,7 +32,7 @@ def start_backend( raise ValueError(f"Unsupported backend: {backend}") backend_thread = threading.Thread( - target=main if not without_host else slave, + target=main if not is_slave else slave, args=( devices_config, resources_config, diff --git a/unilabos/app/register.py b/unilabos/app/register.py index f456183..633df98 100644 --- a/unilabos/app/register.py +++ b/unilabos/app/register.py @@ -1,11 +1,12 @@ import json import time +from typing import Optional, Tuple, Dict, Any from unilabos.utils.log import logger from unilabos.utils.type_check import TypeEncoder -def register_devices_and_resources(lab_registry): +def register_devices_and_resources(lab_registry, gather_only=False) -> Optional[Tuple[Dict[str, Any], Dict[str, Any]]]: """ 注册设备和资源到服务器(仅支持HTTP) """ @@ -28,6 +29,8 @@ def register_devices_and_resources(lab_registry): resources_to_register[resource_info["id"]] = resource_info logger.debug(f"[UniLab Register] 收集资源: {resource_info['id']}") + if gather_only: + return devices_to_register, resources_to_register # 注册设备 if devices_to_register: try: diff --git a/unilabos/ros/main_slave_run.py b/unilabos/ros/main_slave_run.py index d9ad368..1ded6da 100644 --- a/unilabos/ros/main_slave_run.py +++ b/unilabos/ros/main_slave_run.py @@ -6,11 +6,12 @@ from typing import Optional, Dict, Any, List import rclpy from unilabos_msgs.srv._serial_command import SerialCommand_Response +from unilabos.app.register import register_devices_and_resources from unilabos.ros.nodes.presets.resource_mesh_manager import ResourceMeshManager from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker, ResourceTreeSet from unilabos.devices.ros_dev.liquid_handler_joint_publisher import LiquidHandlerJointPublisher from unilabos_msgs.srv import SerialCommand # type: ignore -from rclpy.executors import MultiThreadedExecutor, SingleThreadedExecutor +from rclpy.executors import MultiThreadedExecutor from rclpy.node import Node from rclpy.timer import Timer @@ -108,66 +109,51 @@ def slave( rclpy_init_args: List[str] = ["--log-level", "debug"], ) -> None: """从节点函数""" + # 1. 初始化 ROS2 if not rclpy.ok(): rclpy.init(args=rclpy_init_args) executor = rclpy.__executor if not executor: executor = rclpy.__executor = MultiThreadedExecutor() - devices_instances = {} - for device_config in devices_config.root_nodes: - device_id = device_config.res_content.id - if device_config.res_content.type != "device": - d = initialize_device_from_dict(device_id, device_config.get_nested_dict()) - devices_instances[device_id] = d - # 默认初始化 - # if d is not None and isinstance(d, Node): - # executor.add_node(d) - # else: - # print(f"Warning: Device {device_id} could not be initialized or is not a valid Node") - n = Node(f"slaveMachine_{BasicConfig.machine_name}", parameter_overrides=[]) - executor.add_node(n) - - if visual != "disable": - from unilabos.ros.nodes.presets.joint_republisher import JointRepublisher - - resource_mesh_manager = ResourceMeshManager( - resources_mesh_config, - resources_config, # type: ignore FIXME - resource_tracker=DeviceNodeResourceTracker(), - device_id="resource_mesh_manager", - ) - joint_republisher = JointRepublisher("joint_republisher", DeviceNodeResourceTracker()) - - executor.add_node(resource_mesh_manager) - executor.add_node(joint_republisher) + # 1.5 启动 executor 线程 thread = threading.Thread(target=executor.spin, daemon=True, name="slave_executor_thread") thread.start() + # 2. 创建 Slave Machine Node + n = Node(f"slaveMachine_{BasicConfig.machine_name}", parameter_overrides=[]) + executor.add_node(n) + + # 3. 向 Host 报送节点信息和物料,获取 UUID 映射 + uuid_mapping = {} if not BasicConfig.slave_no_host: + # 3.1 报送节点信息 sclient = n.create_client(SerialCommand, "/node_info_update") sclient.wait_for_service() + registry_config = {} + devices_to_register, resources_to_register = register_devices_and_resources(lab_registry, True) + registry_config.update(devices_to_register) + registry_config.update(resources_to_register) request = SerialCommand.Request() request.command = json.dumps( { "machine_name": BasicConfig.machine_name, "type": "slave", "devices_config": devices_config.dump(), - "registry_config": lab_registry.obtain_registry_device_info(), + "registry_config": registry_config, }, ensure_ascii=False, cls=TypeEncoder, ) - response = sclient.call_async(request).result() + sclient.call_async(request).result() logger.info(f"Slave node info updated.") - # 使用新的 c2s_update_resource_tree 服务 - rclient = n.create_client(SerialCommand, "/c2s_update_resource_tree") - rclient.wait_for_service() - - # 序列化 ResourceTreeSet 为 JSON + # 3.2 报送物料树,获取 UUID 映射 if resources_config: + rclient = n.create_client(SerialCommand, "/c2s_update_resource_tree") + rclient.wait_for_service() + request = SerialCommand.Request() request.command = json.dumps( { @@ -180,35 +166,61 @@ def slave( }, ensure_ascii=False, ) - tree_response: SerialCommand_Response = rclient.call_async(request).result() + tree_response: SerialCommand_Response = rclient.call(request) uuid_mapping = json.loads(tree_response.response) - # 创建反向映射:new_uuid -> old_uuid - reverse_uuid_mapping = {new_uuid: old_uuid for old_uuid, new_uuid in uuid_mapping.items()} - for node in resources_config.root_nodes: - if node.res_content.type == "device": - for sub_node in node.children: - # 只有二级子设备 - if sub_node.res_content.type != "device": - device_tracker = devices_instances[node.res_content.id].resource_tracker - # sub_node.res_content.uuid 已经是新UUID,需要用旧UUID去查找 - old_uuid = reverse_uuid_mapping.get(sub_node.res_content.uuid) - if old_uuid: - # 找到旧UUID,使用UUID查找 - resource_instance = device_tracker.figure_resource({"uuid": old_uuid}) - else: - # 未找到旧UUID,使用name查找 - resource_instance = device_tracker.figure_resource({"name": sub_node.res_content.name}) - device_tracker.loop_update_uuid(resource_instance, uuid_mapping) + logger.info(f"Slave resource tree added. UUID mapping: {len(uuid_mapping)} nodes") + + # 3.3 使用 UUID 映射更新 resources_config 的 UUID(参考 client.py 逻辑) + old_uuids = {node.res_content.uuid: node for node in resources_config.all_nodes} + for old_uuid, node in old_uuids.items(): + if old_uuid in uuid_mapping: + new_uuid = uuid_mapping[old_uuid] + node.res_content.uuid = new_uuid + # 更新所有子节点的 parent_uuid + for child in node.children: + child.res_content.parent_uuid = new_uuid else: - logger.error("Slave模式不允许新增非设备节点下的物料") - continue - if tree_response: - logger.info(f"Slave resource tree added. Response: {tree_response.response}") - else: - logger.warning("Slave resource tree add response is None") + logger.warning(f"资源UUID未更新: {old_uuid}") else: logger.info("No resources to add.") + # 4. 初始化所有设备实例(此时 resources_config 的 UUID 已更新) + devices_instances = {} + for device_config in devices_config.root_nodes: + device_id = device_config.res_content.id + if device_config.res_content.type == "device": + d = initialize_device_from_dict(device_id, device_config.get_nested_dict()) + if d is not None: + devices_instances[device_id] = d + logger.info(f"Device {device_id} initialized.") + else: + logger.warning(f"Device {device_id} initialization failed.") + + # 5. 如果启用可视化,创建可视化相关节点 + if visual != "disable": + from unilabos.ros.nodes.presets.joint_republisher import JointRepublisher + + # 将 ResourceTreeSet 转换为 list 用于 visual 组件 + resources_list = ( + [node.res_content.model_dump(by_alias=True) for node in resources_config.all_nodes] + if resources_config + else [] + ) + resource_mesh_manager = ResourceMeshManager( + resources_mesh_config, + resources_list, + resource_tracker=DeviceNodeResourceTracker(), + device_id="resource_mesh_manager", + ) + joint_republisher = JointRepublisher("joint_republisher", DeviceNodeResourceTracker()) + lh_joint_pub = LiquidHandlerJointPublisher( + resources_config=resources_list, resource_tracker=DeviceNodeResourceTracker() + ) + executor.add_node(resource_mesh_manager) + executor.add_node(joint_republisher) + executor.add_node(lh_joint_pub) + + # 7. 保持运行 while True: time.sleep(1) diff --git a/unilabos/ros/nodes/presets/host_node.py b/unilabos/ros/nodes/presets/host_node.py index 346cf9c..5c43a5d 100644 --- a/unilabos/ros/nodes/presets/host_node.py +++ b/unilabos/ros/nodes/presets/host_node.py @@ -878,11 +878,10 @@ class HostNode(BaseROS2DeviceNode): success = False uuid_mapping = {} if len(self.bridges) > 0: - from unilabos.app.web.client import HTTPClient + from unilabos.app.web.client import HTTPClient, http_client - client: HTTPClient = self.bridges[-1] resource_start_time = time.time() - uuid_mapping = client.resource_tree_add(resource_tree_set, mount_uuid, first_add) + uuid_mapping = http_client.resource_tree_add(resource_tree_set, mount_uuid, first_add) success = True resource_end_time = time.time() self.lab_logger().info( @@ -990,9 +989,10 @@ class HostNode(BaseROS2DeviceNode): """ 更新节点信息回调 """ - self.lab_logger().info(f"[Host Node] Node info update request received: {request}") + # self.lab_logger().info(f"[Host Node] Node info update request received: {request}") try: from unilabos.app.communication import get_communication_client + from unilabos.app.web.client import HTTPClient, http_client info = json.loads(request.command) if "SYNC_SLAVE_NODE_INFO" in info: @@ -1001,10 +1001,10 @@ class HostNode(BaseROS2DeviceNode): edge_device_id = info["edge_device_id"] self.device_machine_names[edge_device_id] = machine_name else: - comm_client = get_communication_client() - registry_config = info["registry_config"] - for device_config in registry_config: - comm_client.publish_registry(device_config["id"], device_config) + devices_config = info.pop("devices_config") + registry_config = info.pop("registry_config") + if registry_config: + http_client.resource_registry({"resources": registry_config}) self.lab_logger().debug(f"[Host Node] Node info update: {info}") response.response = "OK" except Exception as e: @@ -1030,10 +1030,9 @@ class HostNode(BaseROS2DeviceNode): success = False if len(self.bridges) > 0: # 边的提交待定 - from unilabos.app.web.client import HTTPClient + from unilabos.app.web.client import HTTPClient, http_client - client: HTTPClient = self.bridges[-1] - r = client.resource_add(add_schema(resources)) + r = http_client.resource_add(add_schema(resources)) success = bool(r) response.success = success diff --git a/unilabos/ros/nodes/resource_tracker.py b/unilabos/ros/nodes/resource_tracker.py index 085ce02..ce23a5b 100644 --- a/unilabos/ros/nodes/resource_tracker.py +++ b/unilabos/ros/nodes/resource_tracker.py @@ -1007,7 +1007,7 @@ class DeviceNodeResourceTracker(object): current_uuid = self._get_resource_attr(res, "uuid", "unilabos_uuid") if current_uuid and current_uuid in self.uuid_to_resources: self.uuid_to_resources.pop(current_uuid) - logger.debug(f"移除资源UUID映射: {current_uuid} -> {res}") + logger.trace(f"移除资源UUID映射: {current_uuid} -> {res}") return 1 return 0 diff --git a/unilabos/utils/log.py b/unilabos/utils/log.py index 74442a6..3894233 100644 --- a/unilabos/utils/log.py +++ b/unilabos/utils/log.py @@ -191,7 +191,8 @@ def configure_logger(loglevel=None): # 添加处理器到根日志记录器 root_logger.addHandler(console_handler) - + logging.getLogger("asyncio").setLevel(logging.INFO) + logging.getLogger("urllib3").setLevel(logging.INFO) # 配置日志系统 configure_logger() From 5f859917d48779b3cc83e7cc8331413267c0f726 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Fri, 31 Oct 2025 19:57:16 +0800 Subject: [PATCH 068/104] support name change during materials change --- unilabos/ros/nodes/base_device_node.py | 221 ++++++++++++++++++------- 1 file changed, 160 insertions(+), 61 deletions(-) diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index 19503fd..febc95f 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -638,6 +638,147 @@ class BaseROS2DeviceNode(Node, Generic[T]): - remove: 从资源树中移除资源 """ from pylabrobot.resources.resource import Resource as ResourcePLR + + def _handle_add( + plr_resources: List[ResourcePLR], tree_set: ResourceTreeSet, additional_add_params: Dict[str, Any] + ) -> Dict[str, Any]: + """ + 处理资源添加操作的内部函数 + + Args: + plr_resources: PLR资源列表 + tree_set: 资源树集合 + additional_add_params: 额外的添加参数 + + Returns: + 操作结果字典 + """ + for plr_resource, tree in zip(plr_resources, tree_set.trees): + self.resource_tracker.add_resource(plr_resource) + self.transfer_to_new_resource(plr_resource, tree, additional_add_params) + + func = getattr(self.driver_instance, "resource_tree_add", None) + if callable(func): + func(plr_resources) + + return {"success": True, "action": "add"} + + def _handle_remove(resources_uuid: List[str]) -> Dict[str, Any]: + """ + 处理资源移除操作的内部函数 + + Args: + resources_uuid: 要移除的资源UUID列表 + + Returns: + 操作结果字典,包含移除的资源列表 + """ + found_resources: List[List[Union[ResourcePLR, dict]]] = self.resource_tracker.figure_resource( + [{"uuid": uid} for uid in resources_uuid], try_mode=True + ) + found_plr_resources = [] + other_plr_resources = [] + + for found_resource in found_resources: + for resource in found_resource: + if issubclass(resource.__class__, ResourcePLR): + found_plr_resources.append(resource) + else: + other_plr_resources.append(resource) + + # 调用driver的remove回调 + func = getattr(self.driver_instance, "resource_tree_remove", None) + if callable(func): + func(found_plr_resources) + + # 从parent卸载并从tracker移除 + for plr_resource in found_plr_resources: + if plr_resource.parent is not None: + plr_resource.parent.unassign_child_resource(plr_resource) + self.resource_tracker.remove_resource(plr_resource) + self.lab_logger().info(f"移除物料 {plr_resource} 及其子节点") + + for other_plr_resource in other_plr_resources: + self.resource_tracker.remove_resource(other_plr_resource) + self.lab_logger().info(f"移除物料 {other_plr_resource} 及其子节点") + + return { + "success": True, + "action": "remove", + "removed_plr": found_plr_resources, + "removed_other": other_plr_resources, + } + + def _handle_update( + plr_resources: List[ResourcePLR], tree_set: ResourceTreeSet, additional_add_params: Dict[str, Any] + ) -> Dict[str, Any]: + """ + 处理资源更新操作的内部函数 + + Args: + plr_resources: PLR资源列表(包含新状态) + tree_set: 资源树集合 + additional_add_params: 额外的参数 + + Returns: + 操作结果字典 + """ + for plr_resource, tree in zip(plr_resources, tree_set.trees): + states = plr_resource.serialize_all_state() + original_instance: ResourcePLR = self.resource_tracker.figure_resource( + {"uuid": tree.root_node.res_content.uuid}, try_mode=False + ) + + # Update操作中包含改名:需要先remove再add + if original_instance.name != plr_resource.name: + old_name = original_instance.name + new_name = plr_resource.name + self.lab_logger().info(f"物料改名操作:{old_name} -> {new_name}") + + # 收集所有相关的uuid(包括子节点) + all_uuids = self.resource_tracker.loop_gather_uuid(original_instance) + _handle_remove(all_uuids) + original_instance.name = new_name + _handle_add([original_instance], tree_set, additional_add_params) + + self.lab_logger().info(f"物料改名完成:{old_name} -> {new_name}") + continue + + # 常规更新:不涉及改名 + original_parent_resource = original_instance.parent + original_parent_resource_uuid = getattr(original_parent_resource, "unilabos_uuid", None) + target_parent_resource_uuid = tree.root_node.res_content.uuid_parent + + self.lab_logger().info( + f"物料{original_instance} 原始父节点{original_parent_resource_uuid} " + f"目标父节点{target_parent_resource_uuid} 更新" + ) + + # 更新extra + if getattr(plr_resource, "unilabos_extra", None) is not None: + original_instance.unilabos_extra = getattr(plr_resource, "unilabos_extra") # type: ignore # noqa: E501 + + # 如果父节点变化,需要重新挂载 + if ( + original_parent_resource_uuid != target_parent_resource_uuid + and original_parent_resource is not None + ): + self.transfer_to_new_resource(original_instance, tree, additional_add_params) + + # 加载状态 + original_instance.load_all_state(states) + child_count = len(original_instance.get_all_children()) + self.lab_logger().info( + f"更新了资源属性 {plr_resource}[{tree.root_node.res_content.uuid}] " f"及其子节点 {child_count} 个" + ) + + # 调用driver的update回调 + func = getattr(self.driver_instance, "resource_tree_update", None) + if callable(func): + func(plr_resources) + + return {"success": True, "action": "update"} + try: data = json.loads(req.command) results = [] @@ -664,68 +805,20 @@ class BaseROS2DeviceNode(Node, Generic[T]): tree_set = ResourceTreeSet.from_raw_list(raw_nodes) try: if action == "add": - # 添加资源到资源跟踪器 + if tree_set is None: + raise ValueError("tree_set不能为None") plr_resources = tree_set.to_plr_resources() - for plr_resource, tree in zip(plr_resources, tree_set.trees): - self.resource_tracker.add_resource(plr_resource) - self.transfer_to_new_resource(plr_resource, tree, additional_add_params) - func = getattr(self.driver_instance, "resource_tree_add", None) - if callable(func): - func(plr_resources) - results.append({"success": True, "action": "add"}) + result = _handle_add(plr_resources, tree_set, additional_add_params) + results.append(result) elif action == "update": - # 更新资源 + if tree_set is None: + raise ValueError("tree_set不能为None") plr_resources = tree_set.to_plr_resources() - for plr_resource, tree in zip(plr_resources, tree_set.trees): - states = plr_resource.serialize_all_state() - original_instance: ResourcePLR = self.resource_tracker.figure_resource( - {"uuid": tree.root_node.res_content.uuid}, try_mode=False - ) - original_parent_resource = original_instance.parent - original_parent_resource_uuid = getattr(original_parent_resource, "unilabos_uuid", None) - target_parent_resource_uuid = tree.root_node.res_content.uuid_parent - self.lab_logger().info( - f"物料{original_instance} 原始父节点{original_parent_resource_uuid} 目标父节点{target_parent_resource_uuid} 更新" - ) - # todo: 对extra进行update - if getattr(plr_resource, "unilabos_extra", None) is not None: - original_instance.unilabos_extra = getattr(plr_resource, "unilabos_extra") - if original_parent_resource_uuid != target_parent_resource_uuid and original_parent_resource is not None: - self.transfer_to_new_resource(original_instance, tree, additional_add_params) - original_instance.load_all_state(states) - self.lab_logger().info( - f"更新了资源属性 {plr_resource}[{tree.root_node.res_content.uuid}] 及其子节点 {len(original_instance.get_all_children())} 个" - ) - - func = getattr(self.driver_instance, "resource_tree_update", None) - if callable(func): - func(plr_resources) - results.append({"success": True, "action": "update"}) + result = _handle_update(plr_resources, tree_set, additional_add_params) + results.append(result) elif action == "remove": - # 移除资源 - found_resources: List[List[Union[ResourcePLR, dict]]] = self.resource_tracker.figure_resource( - [{"uuid": uid} for uid in resources_uuid], try_mode=True - ) - found_plr_resources = [] - other_plr_resources = [] - for found_resource in found_resources: - for resource in found_resource: - if issubclass(resource.__class__, ResourcePLR): - found_plr_resources.append(resource) - else: - other_plr_resources.append(resource) - func = getattr(self.driver_instance, "resource_tree_remove", None) - if callable(func): - func(found_plr_resources) - for plr_resource in found_plr_resources: - if plr_resource.parent is not None: - plr_resource.parent.unassign_child_resource(plr_resource) - self.resource_tracker.remove_resource(plr_resource) - self.lab_logger().info(f"移除物料 {plr_resource} 及其子节点") - for other_plr_resource in other_plr_resources: - self.resource_tracker.remove_resource(other_plr_resource) - self.lab_logger().info(f"移除物料 {other_plr_resource} 及其子节点") - results.append({"success": True, "action": "remove"}) + result = _handle_remove(resources_uuid) + results.append(result) except Exception as e: error_msg = f"Error processing {action} operation: {str(e)}" self.lab_logger().error(f"[Resource Tree Update] {error_msg}") @@ -1004,9 +1097,14 @@ class BaseROS2DeviceNode(Node, Generic[T]): # 通过资源跟踪器获取本地实例 final_resources = queried_resources if is_sequence else queried_resources[0] - final_resources = self.resource_tracker.figure_resource({"name": final_resources.name}, try_mode=False) if not is_sequence else [ - self.resource_tracker.figure_resource({"name": res.name}, try_mode=False) for res in queried_resources - ] + final_resources = ( + self.resource_tracker.figure_resource({"name": final_resources.name}, try_mode=False) + if not is_sequence + else [ + self.resource_tracker.figure_resource({"name": res.name}, try_mode=False) + for res in queried_resources + ] + ) action_kwargs[k] = final_resources except Exception as e: @@ -1227,6 +1325,7 @@ class BaseROS2DeviceNode(Node, Generic[T]): raise JsonCommandInitError( f"执行动作时JSON缺少function_name或function_args: {ex}\n原JSON: {string}\n{traceback.format_exc()}" ) + def _convert_resource_sync(self, resource_data: Dict[str, Any]): """同步转换资源数据为实例""" # 创建资源查询请求 From 8b5653d8013fc8b544263d05dd8317d5c5e4e5f3 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:12:54 +0800 Subject: [PATCH 069/104] fix json dumps --- unilabos/ros/nodes/base_device_node.py | 10 +++----- unilabos/ros/nodes/presets/host_node.py | 33 ++++++++----------------- 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index febc95f..e52ea80 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -705,8 +705,8 @@ class BaseROS2DeviceNode(Node, Generic[T]): return { "success": True, "action": "remove", - "removed_plr": found_plr_resources, - "removed_other": other_plr_resources, + # "removed_plr": found_plr_resources, + # "removed_other": other_plr_resources, } def _handle_update( @@ -736,13 +736,11 @@ class BaseROS2DeviceNode(Node, Generic[T]): self.lab_logger().info(f"物料改名操作:{old_name} -> {new_name}") # 收集所有相关的uuid(包括子节点) - all_uuids = self.resource_tracker.loop_gather_uuid(original_instance) - _handle_remove(all_uuids) + _handle_remove([original_instance.unilabos_uuid]) original_instance.name = new_name _handle_add([original_instance], tree_set, additional_add_params) self.lab_logger().info(f"物料改名完成:{old_name} -> {new_name}") - continue # 常规更新:不涉及改名 original_parent_resource = original_instance.parent @@ -827,7 +825,7 @@ class BaseROS2DeviceNode(Node, Generic[T]): # 返回处理结果 result_json = {"results": results, "total": len(data)} - res.response = json.dumps(result_json, ensure_ascii=False) + res.response = json.dumps(result_json, ensure_ascii=False, cls=TypeEncoder) self.lab_logger().info(f"[Resource Tree Update] Completed processing {len(data)} operations") except json.JSONDecodeError as e: diff --git a/unilabos/ros/nodes/presets/host_node.py b/unilabos/ros/nodes/presets/host_node.py index 5c43a5d..7a8806d 100644 --- a/unilabos/ros/nodes/presets/host_node.py +++ b/unilabos/ros/nodes/presets/host_node.py @@ -165,29 +165,16 @@ class HostNode(BaseROS2DeviceNode): # resources_config 的 root node 是 # # 创建反向映射:new_uuid -> old_uuid # reverse_uuid_mapping = {new_uuid: old_uuid for old_uuid, new_uuid in uuid_mapping.items()} - # for tree in resources_config.trees: - # node = tree.root_node - # if node.res_content.type == "device": - # if node.res_content.id == "host_node": - # continue - # # slave节点走c2s更新接口,拿到add自行update uuid - # device_tracker = self.devices_instances[node.res_content.id].resource_tracker - # old_uuid = reverse_uuid_mapping.get(node.res_content.uuid) - # if old_uuid: - # # 找到旧UUID,使用UUID查找 - # resource_instance = device_tracker.uuid_to_resources.get(old_uuid) - # else: - # # 未找到旧UUID,使用name查找 - # resource_instance = device_tracker.figure_resource( - # {"name": node.res_content.name} - # ) - # device_tracker.loop_update_uuid(resource_instance, uuid_mapping) - # else: - # try: - # for plr_resource in ResourceTreeSet([tree]).to_plr_resources(): - # self.resource_tracker.add_resource(plr_resource) - # except Exception as ex: - # self.lab_logger().warning("[Host Node-Resource] 根节点物料序列化失败!") + for tree in resources_config.trees: + node = tree.root_node + if node.res_content.type == "device": + continue + else: + try: + for plr_resource in ResourceTreeSet([tree]).to_plr_resources(): + self._resource_tracker.add_resource(plr_resource) + except Exception as ex: + self.lab_logger().warning(f"[Host Node-Resource] 根节点物料{tree}序列化失败!") except Exception as ex: logger.error(f"[Host Node-Resource] 添加物料出错!\n{traceback.format_exc()}") # 初始化Node基类,传递空参数覆盖列表 From 200105f647d9f7288fea7abda9160a34859e2bc8 Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Sat, 8 Nov 2025 13:35:47 +0800 Subject: [PATCH 070/104] Add debug prints to create_orders and add resource_tree_transfer method --- .../bioyond_cell/2025092701.xlsx | Bin 18159 -> 18162 bytes .../bioyond_cell/bioyond_cell_workstation.py | 71 +++++++++++++++++- .../workstation/bioyond_studio/config.py | 6 +- .../workstation/bioyond_studio/station.py | 13 +++- .../workstation/coin_cell_assembly.zip | Bin 19794 -> 0 bytes .../coin_cell_assembly/coin_cell_assembly.py | 6 +- .../coin_cell_assembly_1105.csv | 64 ++++++++++++++++ .../resources/bioyond/YB_bottle_carriers.py | 4 +- unilabos/resources/bioyond/YB_bottles.py | 2 +- 9 files changed, 152 insertions(+), 14 deletions(-) delete mode 100644 unilabos/devices/workstation/coin_cell_assembly.zip create mode 100644 unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_1105.csv diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx index 4147095ed2784e56ccc6f637aec02c2a575c2cc8..9d0696191d2c76ff1788e485ed89d498ebf0f503 100644 GIT binary patch delta 162 zcmaFg%lN66aYMfR)60|-knO!jk?+Pq!Wg^kg8@*izEW&=IL$k|HHLn=(6)7AJijMz78J`iDh1 z;pR`ab`58gojlpqhVjzm3qZ1dGOwEj9Znlh*CbziRF-lK9?54(+=)wR502!e# A2LJ#7 delta 163 zcmey=%lN*RaYMflRTwRyX$3mc=^A)Q1f1v3L1{W3cn{bW0vWQ8JIB{yRwg%Vq(;*ugIJDV~)qsgxNI*fjs zi}VkRa>6Z`Z08!zC^dPSs}1A%$(Mm-&13;L3&xL=J>6^>`zN=%*)d8?KIW#z7U#kM F1OQjPG1CA5 diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index ee82579..4b27572 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -3,6 +3,7 @@ from cgi import print_arguments from doctest import debug from typing import Dict, Any, List, Optional import requests +from pylabrobot.resources.resource import Resource as ResourcePLR from pathlib import Path import pandas as pd import time @@ -254,7 +255,7 @@ class BioyondCellWorkstation(BioyondWorkstation): def auto_feeding4to3( self, # ★ 修改点:默认模板路径 - xlsx_path: Optional[str] = "C:/ML/GitHub/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx", + xlsx_path: Optional[str] = "/Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx", # ---------------- WH4 - 加样头面 (Z=1, 12个点位) ---------------- WH4_x1_y1_z1_1_materialName: str = "", WH4_x1_y1_z1_1_quantity: float = 0.0, WH4_x2_y1_z1_2_materialName: str = "", WH4_x2_y1_z1_2_quantity: float = 0.0, @@ -306,7 +307,7 @@ class BioyondCellWorkstation(BioyondWorkstation): # ---------- 模式 1: Excel 导入 ---------- if xlsx_path: - path = Path(xlsx_path) + path = Path(__file__).parent / Path(xlsx_path) if path.exists(): # ★ 修改点:路径存在才加载 try: df = pd.read_excel(path, sheet_name=0, header=None, engine="openpyxl") @@ -471,14 +472,23 @@ class BioyondCellWorkstation(BioyondWorkstation): - totalMass 自动计算为所有物料质量之和 - createTime 缺失或为空时自动填充为当前日期(YYYY/M/D) """ - path = Path(xlsx_path) + default_path = Path("/Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx") + path = Path(xlsx_path) if xlsx_path else default_path + print(f"[create_orders] 使用 Excel 路径: {path}") + if path != default_path: + print("[create_orders] 来源: 调用方传入自定义路径") + else: + print("[create_orders] 来源: 使用默认模板路径") + if not path.exists(): + print(f"[create_orders] ⚠️ Excel 文件不存在: {path}") raise FileNotFoundError(f"未找到 Excel 文件:{path}") try: df = pd.read_excel(path, sheet_name=0, engine="openpyxl") except Exception as e: raise RuntimeError(f"读取 Excel 失败:{e}") + print(f"[create_orders] Excel 读取成功,行数: {len(df)}, 列: {list(df.columns)}") # 列名容错:返回可选列名,找不到则返回 None def _pick(col_names: List[str]) -> Optional[str]: @@ -495,9 +505,20 @@ class BioyondCellWorkstation(BioyondWorkstation): col_pouch = _pick(["软包组装分液体积", "pouchCellInfo"]) col_cond = _pick(["电导测试分液体积", "conductivityInfo"]) col_cond_cnt = _pick(["电导测试分液瓶数", "conductivityBottleCount"]) + print("[create_orders] 列匹配结果:", { + "order_name": col_order_name, + "create_time": col_create_time, + "bottle_type": col_bottle_type, + "mix_time": col_mix_time, + "load": col_load, + "pouch": col_pouch, + "conductivity": col_cond, + "conductivity_bottle_count": col_cond_cnt, + }) # 物料列:所有以 (g) 结尾 material_cols = [c for c in df.columns if isinstance(c, str) and c.endswith("(g)")] + print(f"[create_orders] 识别到的物料列: {material_cols}") if not material_cols: raise KeyError("未发现任何以“(g)”结尾的物料列,请检查表头。") @@ -545,6 +566,9 @@ class BioyondCellWorkstation(BioyondWorkstation): if mass > 0: mats.append({"name": mcol.replace("(g)", ""), "mass": mass}) total_mass += mass + else: + if mass < 0: + print(f"[create_orders] 第 {idx+1} 行物料 {mcol} 数值为负数: {mass}") order_data = { "batchId": batch_id, @@ -559,11 +583,22 @@ class BioyondCellWorkstation(BioyondWorkstation): "materialInfos": mats, "totalMass": round(total_mass, 4) # 自动汇总 } + print(f"[create_orders] 第 {idx+1} 行解析结果: orderName={order_data['orderName']}, " + f"loadShedding={order_data['loadSheddingInfo']}, pouchCell={order_data['pouchCellInfo']}, " + f"conductivity={order_data['conductivityInfo']}, totalMass={order_data['totalMass']}, " + f"material_count={len(mats)}") + + if order_data["totalMass"] <= 0: + print(f"[create_orders] ⚠️ 第 {idx+1} 行总质量 <= 0,可能导致 LIMS 校验失败") + if not mats: + print(f"[create_orders] ⚠️ 第 {idx+1} 行未找到有效物料") + orders.append(order_data) + print(f"[create_orders] 即将提交订单数量: {len(orders)}") response = self._post_lims("/api/lims/order/orders", orders) - print(response) + print(f"[create_orders] 接口返回: {response}") # 等待任务报送成功 data_list = response.get("data", []) if data_list: @@ -1014,7 +1049,35 @@ class BioyondCellWorkstation(BioyondWorkstation): "create_result": create_result, "inbound_result": inbound_result, } + def resource_tree_transfer(self, old_parent: ResourcePLR, plr_resource: ResourcePLR, parent_resource: ResourcePLR): + # ROS2DeviceNode.run_async_func(self._ros_node.resource_tree_transfer, True, **{ + # "old_parent": old_parent, + # "plr_resource": plr_resource, + # "parent_resource": parent_resource, + # }) + print("resource_tree_transfer", plr_resource, parent_resource) + if hasattr(plr_resource, "unilabos_extra") and plr_resource.unilabos_extra: + if "update_resource_site" in plr_resource.unilabos_extra: + site = plr_resource.unilabos_extra["update_resource_site"] + plr_model = plr_resource.model + board_type = None + for key, (moudle_name,moudle_uuid) in MATERIAL_TYPE_MAPPINGS.items(): + if plr_model == moudle_name: + board_type = key + break + if board_type is None: + pass + bottle1 = plr_resource.children[0] + bottle_moudle = bottle1.model + bottle_type = None + for key, (moudle_name, moudle_uuid) in MATERIAL_TYPE_MAPPINGS.items(): + if bottle_moudle == moudle_name: + bottle_type = key + break + self.create_sample(plr_resource.name, board_type,bottle_type,site) + return + self.lab_logger().warning(f"无库位的上料,不处理,{plr_resource} 挂载到 {parent_resource}") def create_sample( self, diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index b594125..adb2d72 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -8,8 +8,8 @@ import os # BioyondCellWorkstation 默认配置(包含所有必需参数) API_CONFIG = { # API 连接配置 - # "api_host": os.getenv("BIOYOND_API_HOST", "http://172.21.32.65:44389"),#实机 - "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.10.169:44388"),# 仿真机 + # "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.1.143:44389"),#实机 + "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.7.149:44388"),# 仿真机 "api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"), "timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")), @@ -17,7 +17,7 @@ API_CONFIG = { "report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"), # HTTP 服务配置 - "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.21.32.115"), # HTTP服务监听地址,监听计算机飞连ip地址 + "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.16.2.140"), # HTTP服务监听地址,监听计算机飞连ip地址 "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), "debug_mode": False,# 调试模式 } diff --git a/unilabos/devices/workstation/bioyond_studio/station.py b/unilabos/devices/workstation/bioyond_studio/station.py index d6fe487..44f9cf1 100644 --- a/unilabos/devices/workstation/bioyond_studio/station.py +++ b/unilabos/devices/workstation/bioyond_studio/station.py @@ -177,7 +177,18 @@ class BioyondWorkstation(WorkstationBase): ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ "resources": [self.deck] }) - + def resource_tree_transfer(self, old_parent: ResourcePLR, plr_resource: ResourcePLR, parent_resource: ResourcePLR): + # ROS2DeviceNode.run_async_func(self._ros_node.resource_tree_transfer, True, **{ + # "old_parent": old_parent, + # "plr_resource": plr_resource, + # "parent_resource": parent_resource, + # }) + print("resource_tree_transfer", plr_resource, parent_resource) + if hasattr(plr_resource, "unilabos_data") and plr_resource.unilabos_data: + if "update_resource_site" in plr_resource.unilabos_data: + site = plr_resource.unilabos_data["update_resource_site"] + return + self.lab_logger().warning(f"无库位的上料,不处理,{plr_resource} 挂载到 {parent_resource}") def transfer_resource_to_another(self, resource: List[ResourceSlot], mount_resource: List[ResourceSlot], sites: List[str], mount_device_id: DeviceSlot): ROS2DeviceNode.run_async_func(self._ros_node.transfer_resource_to_another, True, **{ "plr_resources": resource, diff --git a/unilabos/devices/workstation/coin_cell_assembly.zip b/unilabos/devices/workstation/coin_cell_assembly.zip deleted file mode 100644 index b95b7f4a3f731c9d33a00a977343af3c5962ded5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19794 zcmZsiLv$|;?51nmwr#t;wQbwBZQHhO+s3c$EpBZa^Ua+9IkT9_BFQG}H_4MI%7B8Q z0sU{Yh||^iKgs_%)c>N1gO$C3iJ7gffsu=gnVqq%7o+0;6hHx;RO;yHw%|Ae;{XAL zhX4W5{r?M$-CSKA>=_)ruJNs0w#A-&EItTBu^@2qOioGW zh|IYl%{#+!S+L`bX`=?)`PxC8c=Dhn0HMj{LJd!W7}_9Dk}zd*E{g$!1w_qx3SZ!V z36|?ylWWNs=w+AXjc@z;|895O_W9k`khsFSZ?0u7<*nU9*QcD4U#X-%k zVolzl@$}UqK3^=PUT2#sp8HWD-If|^t+Fg5j_yC8Eq>i5BJJIx9vjS4+UtS*o*~9a%NOlcXMX&^E$Ox= z7Fb7RSRng+y-C`qbg=w`&(&{$!;qOxE`=x^$I5vnD3F0NW}G~zSt5QjlB{3nEcBAJ z@-m%(r>OL}%IWac>SZY}9oxCa6j_9>3TwW|s8$CPTwTB`?Qzf%@vEOxZ}468b4?i_ z=fB*0kN@82^`ZH=uk~iwuxmeyCqmfYCo5pq9vLrI#6E-lc;Q!c*{V3 zOM(%UJt)4EkAnJ=HRZX5wX%TGKDlj3BpyYU5Q5txH4x7lB>GH2jS;S5QzK(KnPf?7 zO4kj&y_u)pl=Y`sTWBk^gAS69%~;*q{#UhsN+pC&Ixol9M4!Y9MwY{lP>|(5noi;=F7S9ektRt$W4GJMX+^V!r_4uJvR2xR}#FiWPeXrZh3~p%B|KkP?fcY z$g-&5hv=P*TDXj0HEUA2d3VQ=K)B3wS?iaKU3X^$9oAVY$;BLda-KE0MGbwFhu~^5 z1G0?M!0%87i;wMdB+*WPH$2`N9*Wr76Sj@^s^>jT>t-pBH^nLihH~WAtG$Z7_g3`DHqn z-w(wdGRGc$1^ym3hcz27r@`SQgxMk%j1}%yc1KWt88(Z6_EgCU2}LTCyG1oS!r!W1 z7or;V;NumLflF(^&cKMGvZ&9~>2#Xw_l%}LDYeQjw7Q_*w7_7u^JDMw;QBaET)EBn zMD7|?o#Qdj#^}4HtJ{V+4_evx6UWaAyWQt>Rq@d!GY2ck&QOn3PedxzGWXBp%H}dB zLtzUO^Qw%Ss;MDBr$9IGWNwOaDs#p2!k5zC<#RvP@0tUMQyr0G;B9ry@2R3V1X_Do zRe0z8Sk~uQ)8#&y|5fI(<@uvY5a>3*$=PU9i@oPByIgl(T1%ZJhI741f!H zLQS_5-fUulRBBhObMhB(dSF%-@y<(UTlH`3E#)d(|u$q@-I{ z(U|xcwUtjgt1H%89q!oguGF4$; z-CrAi5=x#x_QQkUM!~vrr`+dv(fj)i<=f9{_s6#W=Mp@TNfL}ffuo`!5bdufZ=cu6 z{`iAhac<=Iyp#_jbRtU`*(O9aab}UcS*x6|%s-dOv9vhBeND%1uj_>I4|bN4xMTE6 zf~}s@{*9cT><7VO>U(i3x>|t2m)H}r?0kN9`>Fh9g!5JB1X zfgUke`|s6azb6SUDimkoIwoylMshwX@-oUYI6(nhcazf6V(*hjXpJ#65iSl#B5>A- zZ@b*QpD`i z4&T}aeSx7XQ;*Mk@hfAY=lXnd8En)rUxwqZ0;RefGF`r-dEl3)4jUE?+;4$I*0p(o ztOAoLB-xQ%?=}xA31UXLOAf}RLL5iWPPS4ddFTIr{Ehy3opA@^nv`g?)*H|cMj){g z$|`0`Ys~~v0@&R_tF4XGHi|k?9|;wh4m^Gl?!pfW9chRV8gK6H6)8fY7athsy9|P$ zoA1Ut%0dv3@{?{#QbHz9XOz;mkn*E=?HL0Q8~t@na3T!-1{pi7YE5LY={MGO9nUN_ zsnB$Vzk1axFP3h&n`iHLT(DGK7n;g|Xh$x`UK*e`iS&Vf4~by^fFW^$G7%=F7!|&) z1|M8>)W_&STu*ykQrd4z43PNtc|C|fu6JS7AxYnkS*zjDA{2R{zOT8TnjbMIN>NBb zqi86pD)IEJWT~Qoix79Bl7k>AL^k8OR*o$tp0mDOz)0dJzJ)f@(}OR975*kAIikTG z-VPnaXE&a!l9SU-{v#cXkhG@x%2$B4k=l-@-7RusFJcg^C!3TPCQqP6UwX#6pv*#JPeAnal#n^1NEb84Bz@IgeYS< z7pg;#b{&SRPQ%M!%52WDB` zgMK=)OGLry7P%ng7OOvTNgCO4vTQL~-S1rFWR7jy{i%|E5O*crTJJebnJ;Fdw{hr! zFH?VDf1`7bL9-)bmZQ+hYyp+841=|U#XP7#DiftkiYm?>)I}AC$(bZ8^;f0DqsNC= zk~bGtwB#0aOSmIa5FPok*iai<;RMp7Ux+v&%nijkkX$?h>dEgN7OHad6mht#7%>_M z#w!m){Qwlc-Io=XTH?7S1qh}bQxi(D?Yr?qk4tfID_irH%osHFBBuF`xl)@x(+Tuu z38GJC!;^y_;u-Knf+1vghg+PVz!tCqMCi+`j;8XOQ$|$BVHs zXQu=XP8>`Y%UVe3WeoCoADn4kB7UNBEh5kNW#@4GqU@tiE%97vKC`}` z@@wVf^dL`EI(Q-Yp75}?;*pE_Rz3_{Jkd@<{P}RoMK7`;vJvgspa`9x=p9eORQHfe zMD%l=a?~u8l4vxEuH{Mx1o1*3Hb)}yc9N7&Y;_D>Nd9oPohjl?`rj$KRwU!W&Tyzj zxhkAWX=AG#8ULF$AmRf9nL_`SDs=aU?1J`5d2N8hq-=l9Nhh+hTW$DdYDm4Vy0B)I z#WXw6eg_~x;)K*%NO=;a9Y_RW3GG<+$pq1d4917*0*YeLgB57JGKbp*bbH{hJWR46 zVhP>afGKX?qkHpu>x1wD)L>UYjdh&ShHM8?EXOr{8=lV5S;Iy^5y2u1*0eQ#iZPf8Bd$S6Rijl&ugX3eu4X|3|L zMMxGyUl>^oi+D*MS!={<)}4P)k@j%tgu20uBT(cSb)kfN?}Q=;Mi7!t0fG(o8*f5s zs1w*J|82d(YRlrO#vg@WoNtOH0pXpQ3@5%kAkt(P1XSwZHyGR-oc(mD&M_nT3;{mv za-tbU94=;=7&ju54z!MX4=@e~pRTd2x-+#OcOKv}r-8yd?*smd(n$Ha6Sy=>+=wUIsIex@F3!^}bzt6ar%Lz3I;uCZXzJJlI{}a=F!5<% z3yPpEkhy$GAWcKGKa#EKq^m96Y;ukD^Payo!&hL7g$409lAFkLn@hlIfyM|LM^{|* zjm8joZ4Kphl^GRWZ_I>&wavmf)J-SLB76&D?5drT8v8IS#5i@LtQ8%QZRSRiZjECl04N z5$ucjzb{G;>I@T!hfS^g#IPsE6O2)t86C(k^)$2vjZo1VLU|mEA=$X4U^)k1Poshf z2O6j#yY@e@5ggMk4Q4D_+~Ot&L<`U2%H;>jEozRrzi+VGuwR^$3PxFHmGYD#2V?qS_i}H|2Ajk@ZT!%oY+u!`d>+_SW#Zr%lMGLef}{fD3kW-n`HC+X#ZLaj4g`%@h396p(c`lWc}) zV7Z`lNS=#0^Y_8QfY`fvC?N0L#S^52UQXQS7U&1wttD#=f<}2+or_bW`MS-(wL{yu z%dwqj>F6F#$37ACESt z@agF=%Pq9FRCbUu$Q*AP24n)YzTB~U?znRX)K{28_G zyYY{D2~^@yA$+?jMTm*r)}ai^U89i}m0td6!$&AWm7gLr--$6rFo3Gm!a3-K7$L@d_>*m@vRU?jXW7s8%J2NShJo) zVh@&!>t-dtlSBfLV&?Ve=7yR9fpge}`j-CtJbF`bbQv#P!Z-?drZc%0m%JRrHgG8? z(}9#E0s~$UeM~_q2RtUV($usZ6T;QGmyi8=Ka^FAMQ&ukuQ%=6u=eo*>0V5WRM>$L(KQVJS?_jDCT|Jo$anC zckRsH0D7a&N=CWg99pQlLMm1AEQ9IhTB>U_ATe!YCuDaE7ijX*Byl1f9A|R^+R1C2}sZoIM}X#=V~t zXpvBY4F=K(O|ZFE)x*=~$D`(}iyX~gn@hYv85%>K6JdTynQr(pdO(1G<+PdT@7B_* zIVg<$f(k2)6u`Y^NJm(246e^5A#t24mZm#;ZC5Kf;ut?En7)+i_PhnfLzeS)#YI|Y3rjfR z1>th&JrPdB=twzVl0x^2SPh0Ylu~7FLJxwBLsNjc@HnTDk+E+|Eh*vfHLxyOcq@$a~8&j91DZ`RU6M^u>)_fO(MP}O4P z1{QX6R};*Y$BYzRLJG*GY2C+h|3Cxf`5Cp9ra#1h$Xs4kYk?|0PHaMLO&aNPN05)Txx&WAq`(uzZj08vEbOVTY) z!C#7>_)ebYxah{ZdZOSIksn(*3z5}Lp>2@uA{uYJ@Y>-Kpg<|rE<&0pV1Vubh?f}| zArgPc`RmBs*gxDF!9YJ@qq}W7^jr65R-NQw1S`ertc!_mV7bi^aI<<5d8Y+um5<~d zCgKYCdZ%e0w7EhPi|||~C5cwXhsyriU*>7(EcHROX6h$IK@ALvFD0ATpjc|(04)AN z5yl|t+QRnMs?+-zL~nvelHM_TX4(3S8%~78n06-Tyo*4tN5SPSm-n%s9gjI-P|s~V4ZlFY@uQe9tgg}6 zhBG@vD-O~7an@A{Ii}EU$L-X!M_KQz8gTFa)5KxbMhxh%$}kZ}*E8WZ5NM$^QFY0G z)98%|S1y$2E+EuLzcNK=+>VG0YdA)K}hux|J3#$v4na`+$ z%(L*sM!2{|-~6hevBk2|ehRJAfjHkyxS>ya*k)y*R5Rui&}X75gf0pRCIcuT5JuhX zqs@C_{`lew2C>J{^%O&ZLMzWLN$6vTmumc>jXR67@90x`7d9l{#`(ca3L@He;LGM` z>i-3Oqo*9hSh}@QGK8G49(iG~8e@09L)>n!6y;+!cj!iBC5wsM0P>4dj>*K;d(VLH zY&qO~M@*;PUy)(b{1Ou{{zjcz7i!}YLWMg6EwQyESDNy?)Tt9Ku#j91_uwWcy_70= za>_6o56Zd7oC+1-;A?gl*~*w=3eX)|oMjo`kt3+!4OyRrODEe*_5s~ZD11d3#14}b z0};+C3`6fgzC_7_LLeYY|2*r!`B_gfzCD+-@FWeMFa8DGP?BbteKv}p7-Tcud)^NU~F<11;w zy!?hJx;@Z?z2Rz?W~0a{rd!mt^tZzhbWr9;5TqdY0;IB%xIH@L1NhF!B=pDW!&AEk zr^`YT*?u&ml%0(9_{LX3{&wvteuB5Bo44Di=f&;G&FvEZv-{)a8W)hU68`EoWBiZC zekbL8$^+i472!_&41bsZvo+rDlZ78&%a4nbFL$NqgNI!!m%;zT_`}4H3VtCNG^2=h zPicmxrgX9>Jw^ZyQ#g4PsQAH98pjduXOiF{o9pv9IZFa@+$^u0Y%{eCQE6*%KM0LI zCw!c4hOVolyc$Da(_S+e!a?gG5Jy($oNR;#WU{1V2LTE$f#FVgsnzvK684HiSZ(cR zf*>S`XHju88J9(Zc3cw~qw6hGorB;LN)Sx!DkeU}=O4s3O-^K&=&r{Fo8{ztqXSQXOU88`rJm0!DdL4cH7^d@!ZHgSO zR7z$G&z)Ojz%8nPR(uI)!v~^8CrfR?1cB#~EpsvXQbh zEjs#ubZex)#aP-o2^$-%&*Mto^30ZMnFMB8OLKKrc4c?g-UfUjjX)Lw}^MGc6v;g zDqY%4^?UTzhnzA~HqA@Y#EsB&Q|dhlj$9_qec~# zX2?^%x{T=C4C1qLn618iRab6p>deKFracdbdYvj1JuP}08$B+&oNd0N2>sCSy9mxc z$+MY>?As_xR8bNaJ{XZL-0;sVt~v6Na^0rm$&jeX*wzV+`}m!=!dUfR*Mk$cTF@9sll z9Y~WkCoh!B%*1h+Fg#UkdNsBBaa3um-r0Zv<5RR(sPU1+28=ajGMtvUnD5YMSQo%r z$%!b(uxU2@Y@JR*z0|Aw^4O-QA=y<-3C0WL@moY%s0eYD|7)%PQL_6!@AU(*-3^$4 z33_ao^;tBT3M%rE*s`GnWswI;fYT(pj8-LPa}bmpK<;s1bbV}^Ta8})aGdH2&8ll9IT;-PqdmE%`nycI2<@C{)s9>(mFi&fYJo~9 z#T9a+E1NcF)q_dp_fgUB-sXE6{QBifu;cVXW?;`TNnF zlupnQM|Ko2$8W5L`dSK0 zBBwiV=@9CFGKttwU$C^OU%k!O-V?0wbBaz!wT0SJMwRy0O{0V<1e->!hH)t+nt;bS zTnT-mDAbsy>Dt)GmEcmwNulo`n5wZyl%wgPDs;AlRHU81$XNZ4J!hm+lj|g9OA-vX zi~k$NHrGSw)wdM7J8~jXIJOUeQuhV&)O?CA8*~n8nsc}X3ve zqLk)cmD<2eYA033Swb4CVWKy*J!2Cgn-1(kLUbSujsd4I|NdE1r_WiyB*{j)gOp1G z;sWQ^Bsus=xf>-Vd*XoS3%Ox&iOw6AiZUWllG`2I-H)=@jQtj*zvnYvs$mp2kz{aT zC)Qv{JtJ$-o2hA5e3#A8uMO>vBLMGxZ<#J-Gb>`g!bKGnD-Z$uDk#t0Qud&leh2aH zP6NyUMvV=125DqFf8@F)%!*>gpw|(k$W(gNQ&1yvM_omQZI38zf9g6TQRx=?2F9$IK>}!02w9mi3Th4zn@kDJ%+EvQvWC;fuFHZ7>#w!a-NOqMtXKnEl-x;8Uiq3L= zeHP9%1_C_a1zXp`z7fU++2CIIv*DXCOF({3U;i}bM1(wYQVHh3`E>@A?jc?KOm)26 zK_}Tw@PS2;?dB?cToEr0s?HN|73g2$`+AkwvTTHDFxLJ0HqoN_G0Bc5wFX54Gmtho9HG@^1lWqv7rpoC04$xyf+g_48*wzE-Ob4})&g-p z6fDKtxq90W9dJ+mrE>vY{T7U&F;A!+tzq1L_m_~PoYC=o%ZT2`b4eP8q-S?jov+g8m`naCL7<{+5r6HMpCyX3o8A5X z6l^0B{&e{ncK)h)Wqd7MYb8*?m-l6d+&Nbz=G0eWDYA`%3$SN?8{~peR~h^W58Q79 zE_MO;v~4DwLdLzvUMnrp`6%@3y&!0LGPBI|^ zQfMMxG3vJ?j+Ys(_`Pf=J>IXjpUbeqUWrNENP(0dB9^?(Cg9CYx|21TOZ<+t<%ZoU7y6`$v=_#eO%v% z3i7rRPGVbNWETwrl3brjQGfSdezwMIxpB#}PL*LfCXzTQRx8q%=|RQYvlFh+Bw{a7 z3TQkjh6GZMMl)B}!e8PvV58o2A}~BfSZCw88{SXKfo}(<6-1yCoJfE$+q}n z2JHUS{RedNDGm4UJf7CHR(7{PP(3_eVvh2wOn1#jD>V_2zWtui%WOSifBC2^O3e{m_Q-Y?mer-~H#*?0!r= zeAyM@7H^5R}${ow&|eb?)Kc>LUrOIpBl%SZkUJ=c=DeSA`z zm`Z=C9Ajdt%k5EaTaE_lvW%!G=!+vJ_+g0H%NGi$0i_?79o(RP6!}jQWfr-en7`$v zxZ^}^6i_Oq6{?*m0V@oLn3p0oa3bd`cNK)y0+OLUI4J{@0YHDfOULq z>AVH&3fXBY*=ABw6P!r8+GNcf6PaARh76g)(rETEleQf#dFEDQE(cA{0%?yZ6nkpp zD+}r|50mjr)}qOOn-({Ss6ev#>3lH@k=i^Ree+iY${^YCeoZ>`{ZvV_Y$edmt0Sg( z{-I01iF1U6$;K|2o|mk&OhS~R$PX$26rPGWv3>w=A$qhx5b}RCY&N^mUx~D34K|xQ z=nI6^0&(~eH4^dM!XIKq4qj}M9d`0{^*AL6!0Nz^NU_-KCKY%eCvY^>itQ(hWCiT^ z`u^u+)-P9Y(mOz8;&E3kl3xEUWP+6>5($^ScM9rRu)6vx z%%Yt}edOE0?vfCj{dI1eR1ZRH(z+hJd+`}hE~XF~88MrulgB!VFCCJ;9QCTFsoi<_ zq0pwK<0&izFy!*Qpe_dq*p9k6JG&zj#ETqtk^gVz->*|X$op%=CrIS+iQ5sRKXM?` z_V08(oe}q(!DR;ik+K&;+Ff#U9g(*al$Mpl)zJV?xR9LmZiH|vnM4OdqNl)0ZnS$- zz`a z@I3;pO7Evy1?T1#-*bA?8Ng(Oge?dOB(`jOO_(mx(b}3LxkTj0S%Sj{c0QG^yk-mD zkxe`8iF&yESH(aZ?C{{`Ab(rAR4+An9u(Qy(nc_HKK-EIPDkA7pIhte8vG)Rw}Uv1 znMOODjUQL`My3#Ce<0Rn&-2=U5@7bS zo^MsS=zVomI_>)u%~SAIKK+h};kn&&xj?(`JxR^RFQ{ZALnMzj8mRvfQb62b2qET` zm9@sD{?i04R$C|9$oeY(o33GiYnP|px+g(+Z*{5p7qaE^ub2ro;dS$;`7b?o04wqE z_D#-RPByc|u&UcK=AYQ>#*owJy@u8$Y!I;E#Sz}1${uoFGlKP3(ICicsN&xar;UWJ zLF&`PzXJ9=hi%7QfN4TUM|GY24TRj#I&^9l79P0%e!sW0ZZ~s8Mpeyt-a0U^QtfN>T>>hF>S>;u_CE z06z!wn<`^C8ORy15(WX6O;^>=uQ|h7ZQWhR!NvPz@yoYG+ls-31cOgQKCaB9XZ;=X z3ClH$oyEUn1c+mXyjpr)$Q7xplS~jyyw;hSJN(2(va_|Iq7G*Gv)#B-)Q6#%@_B4h zO=5)Nqy`}KAIWlq9yrcEq<_kE^6d%Mgd@9ug39qp0g8AhyZe^E`w+TpSN&C@!<8Ii z9v)!iJA{b12Y=7;uLbi6;cE>u}jOgxW}BCcPPYN zP*)uG@}?aGTkgJXpr6kpdyhI~!*R~W=tef{{$zxrM~b4o&T&kW{+zm_CK8l1rjU$#(`eGkTTM~wDe)umeUde!O=Z}IbVd^#XMi~~xFk_LM1~WP1;`AM2!HU1 zO_Mis7Y^Uwk_mTVaR(-6d-v}eaHwF*iiPBO9A`9}H=+<`3go~c1)}l<;H7eDo z+eti&#jv3$y1$G2NJ9HVMkPcckNJzH6U#OtEBw+%G&*5%6)eE`LSOL9o7)|1X>C45Nwfu1?d_gWq5S(>^ZC+J#;;qT`Ihl3LV&uxgKSAC$#*2Qd^-R5 zS}>GAh(IA?lxW^5tQW_>`Ik{1T3s!hjnwbgPsj=~K}+$xWHXPFkRv@^g<q`j*qJ}@_Hdwie-wKi920A^DdG2peSMn=(XK~J(d$yK969r=!0BGHPI2C;2?!G+!VhTpt{=?o5nu06Yyrn5zHdUg0yzIrnPGh0_+T6PU=ooBxG!}B;1j>gRs={-ghduFip!6?k;@{8HL zXPyQ>-2GEnjK|;J>F=?v*5>PeDMJ2w7Zsoc1==3ewY*t>BS3FB{i@+M(5^pEV%g(C zuvd4lI z2a9honwowaTJ<-(%r%VI;+3dEr0R_xEf?ASl(5Fr2la zH2~56xaVy&-8cyt6`iga(CYC@RO8eq1dn_|r5VDKxWoB`%CDC(7ZLC3-M*Jj!Y)+c z{*0ozm39}j^UXCU6qFh-5uu#tWEZOFDN+t8Kw9AO{#l5l&5JAYl#~!jL@*fCwQw1B z;ouCTly43F6tjVm%G7|WuA*@J%tHQ?h9B=LIUJ8gl#RbIQwoxz9de#`2NHpnWbd8$ zH*l7NLU0zNMOpyi7HpVPj4x0llZ&$8jtBK+7kk}$%nEL!e%{*S#p_*fbPMXD=q;we zXvCd6<7+8&~;xnv9YfQiDhiE|hU1-mLlr`&f@t2f*`swcs6dUE zVh2&qRJ=8L%XwEk6-AXWYwgVg3>!*p1QLr>4}B^G$FJCHL& zb`IYuAKlvpaOk@V1Y5-g1>zs_EBGbVhFW}9#kX1eUmh_kUR_I+`fm`KEA)?-1OTGX z5GE2$7F?v6^V);hD2I!~Z4mz5$F3Tb)Z&yVe!+h&0zHWICCPrlw}7U@+yjJu=b5p~ z%ZAVS(J{HtN58(J9P*u`@sx?v_d#lq(3i)ZzX`3+##-M*nUo!rxcA->Dm+S(D;q+m z|LeW7aL0Cu_Pp%_flJ^Zs?D~tdF*Q?0NDjlxhOmvJ(<^Obb`*K0OXg19waFHciut;fGv&z{^?N2`+rCWj>>ctt6<_)Z_j9giEdr*I38>N>s%cSjH_1F&7FcBmvcpb1T0oFN zTN3^9??<=%g=JjEOyXpZMzRW?wcu9nM?fBig^bGn=s5(3Z#?i^=?^H7y}mv*x1CXInb z!|0faBMk)9h7xf5p30dOOyzxJ9rWzM@Hr;y-rjWMk1*JEwI8t$3v<#2qj)x%B)f5P zStDqC_Wh(~urG;G2HoJ4O)zR%2nk5@f6_3Nt8l;9`H=d(9tJ!Vjq^9V$}Efq=LS1O zr6=z^xuN-6qBXx?atl&3oveD#saFdL9l5VyD3}*qC3E2R zg}=;cIkPKnB4WESB-fEion}nKmk|rtghZJY(+AeZ=hj23{QlKoJN4^u)+!ck$&%%D zHqG<7m2eVX@;!{`=f_gf4_@{nj(E>2M<8Jj?_caX2r&oIfR0uQS#H$FtgSCxRsD8$ z^q;nTR58xA`Bj0NT}P;+3x!ebyk z!o|8}mR_7)G5y(z`O_Sa@6Y?H{n8w-74@Fy2AJwJya9IGw-jozNpo`mzTB6+yOovL$&cNf#~z~R&;e@c%PKM6#VNEx%4)<@ z&;f$+%}AhdX;kCs3<(LIi93kO&j+b_>1AVh{i>y8Na-^-GHO%M55Jd=d92O1M}6%Zfnfr!cx(Mo!I$5J=v*VwFwbI+q%q0y+>1 zGS=ZQQzp(!@8vE>z;P9gIp`$%Vo!Y5j7mbiNz$E|G*NY7!8cwz!qD0H&{okDb>pie z;<6E68%Qp>LgpV^-Jlf1W?#SzE8X=mHu1IDNEIWS6FDrDkk6!Aq{mn{j~`nWk&YTL zPUmrf={Fl%M>ELNMe=3}bYCwz^-gbFQvP9Hak3pZbr|#nznmy^ezeH=bsyEq&{R7IxOhpxzPwFLM-k=SS6hi-af$N@OpGnv zx47U66u4?g#O2->L_6Nibr2u?WBfx*A<-lP=Cg;bFx+WAiz7%wyxm>0+-7*8Yv2#P zt8GWfw!8&?H&~EdH>Q$7FWA-(;9?_?NzT_YU35rY)jhQFZ)vlNoP~@oIif_U`yZe1 z#bmWSE{b;k)b|3T>!a=j2ugTbB3Cj+XT5@L*cxXZN34|g0Q6B%(TR#*Abh=nFVarW z)r#eT9zz!bXiVM5e$@Fe?abh^K21+UMof(L8-K{o2ifu=FdDknBKT~~9QYFQC%JF6 z?3k$d`z_WR-smBeYTKcvK`k}Y39ij)p4x)m(gN;NS*`4xHHIHB5@_n&c-wr)9E!Dk zv*rQSDxWllvGM`ZvJ3?qD+_afo2~aci!%p4nF0K)58;+)&g`avq*}jf-Su+~oetjjr6;S!_mkK(pgjo)k>Z)-gzlLle-wuB|rMv}D# z{Z$74dcqLc;DyCMNaTV$6$&0MgREM6F1muAR^M;b)~P}CimmM*?7Gdjf#NmiKJm4? zJcamtF+vFW5+mL~CBnVv<%2_uA?;xl&trdZc$OFTK=R~yi)9xr0s;+D#^kP&)+@?X z&8-Zm*eCqk)!hT901wUWu2R1bu$srh24B9z4H+8yE&eYSo1417wrYZ`GDS*wg~4{t zct7}maTtmI3B|q0mPMmrC67l=cv$=W=9V-+=i0Yp+co$>*x|EZ)(n6&w}OoAazD3? z=`ht1mo{7rE=;^9LD5QBqRrtTj4j~6^0vX*tU8O+V+WGHF%-)S&)n5`+2_q6W5!Vu zsSfxLDWz$ozj>1*DP4^;)w-|4aQlG$3m<}uEirY<0(o2j1_V;8#)@_DMzn-L&bOnJ zVTHRtQ$5^6^fo_X9aHSm00BqfgLf9g{kZ&hQ5f!eu6&)LlFDvRW_0O1ax!(STXZOo z=F?}7D;L^o>8Zu)mXQl5zB?8S$GD_`+@F4L0*3bn@nD}*6bcn8!^^6eNOtMqoiU0m z4gdW>)P=2$1MZ4;Ip7=8rC}T*WtL%dlCrC39&vZxunl^bZSDGZ zl*1965&$UOzQNxiZI)_|vH5y)EZF?Z}uL z^4^+#HSQ8MrVKXiT22h4F%d&LYJ>3yKbnxzgJ$?D`gOu69gOtL%_{p_5Cd0j?z;rE zK~KKxQ%l8bg?|RO9fG$gun4#@c)1G1EYD1qpNS_R1%UwT?QabRJO>hA5*d=%Vw7oq zz`MIp!z|ka8^-o#A~LswrQ;~W_R7V;Y^faDF=60I2Qy5LHb%xa4$p034NTa^!rI2} zS#XSR&yBx{PL9vLX2#`!bJ+JU*zWAu{}+F4j{seto}OY`&N@qj_&vh(*}^!yYl&yEn~l~t;hKIA*Oie?;g)Y%zv5-HYduKgqcJ()3vb&< zwwoUc`#2Cz4gM|th#MAzq!w5#wgEBLTJKocN_DC*UNceb9vsfoY_^<W$2^9>MRpsGFUsG!St@ehEpNcRV6| zfcTkQtu2%)X`g~9yW7OIB=5yo%$Z038E7uT`I}jb1AkToMkyI=!5-z7X?HwQ_ z9HR0AO97JJ`<1eO`OFU<<2OfBb?)l6&(ZTfZZ|-FO2zQh2oWWc-?9BWSJZ`!Du*Ku zduQewT2gzSY@bJk%C@ISTOVbb75XU!_04&H*RX+%-8JY6zw)J!@rWnecCh;qwbZ(Q zkt&rIyQOzS0{)+boVL!NZ@c(p2;usmaZ~biD4|EMD{N0THrwl~5!6!(PaB^H)5uL~ zJnyFVV>_RRpoq(aX!sRq8q`fFL7-4~O!cNmL=4_6J8EFc1@W{D%9?i_Pk z8TB13CBbH}{_HWR3r5W=Lp1jjG&jX)A{KmT4IDQf94s&no0w7$jzZBg*Kid@d) zspgTXChQbTe}gp{lV?~=Yl#8NXEV{1x0{UG z%h{6D=#|Dc4VqZt(sk*j~@T_ z-A1aj^53B#n-_{4vVgQY@b$~UG&1%EDFmhWKuNeX&8DHEa?h3N6K2$=@(Hh0_f;1; zVMJK?P#a(l4{3~iSG{qXo`KCteO^=3*~4gA%?ZV0p$)HDphWskRjILOvRh)KD0Xp+ zHd%CQD7UTWgHB>$-e6E|SHV~cfVIKgJ#uwp664l2!3&d2-2oQjA$-k5-GyfXCb+$b zu~*hEZ|%a-^W{NfT(Y)vSJ;&ZzNPug_0~(i}G%ZaBP7w?%w7?yi}503@k5 zQ+5v-FYNoKIU*Hq%%dt7b$2bzHv$j0LH=Yn3zLnB;Yu8Bw~9Ik{A4MW)>4`cUDqlTt@1lSi2z(Y>QC6$R9X7bQf~IQ84rB(KoGeln)xF ziThy1E9wL;BpaC~rhjrp%8@z~;gdWZrg=PuPOG*?-H8gYhPnxap>vX~1V}ud@#f5u z4I`hkWS5yd{9csT<1%Euff5#Ea93aNUH!hR%NU_7v(prPRsWznRB(VOjX&3ej89k_yIS47YI)=!4)e14x+IczSd!kY(Kh~gn}laafIf-bVII`1b!?~|41%m2GD zT8<3(@0(RRbbPg`Ue!avT!ruF>M1#kcP`Rrqr+(LrcI0t{97Pe2lgr9ruY1U05Zntz3R*v!n+}`}CN3k;X z^iz^lT{xXNS(L4WTStis;AWpZZN}BD8EXrlq~4fnC@^?`By~4xq5U@VoXuQev5OTw znuoNV!>AEwnZ-Zzs1RTK+_gItY^>P%u&SakLT(e>9&BtJ2DQxA4JDKJJyrRCHF6$M zO(kmpjsYox^aX@~^db=i3lM_z-jO1RB3(j>XbeRGqf%C+gh(jDLu%+nKna0GlqyXS zfzYG~p+$-W1O;C3xx0AOw|nN?JN^Ie%$+kc=gdEU$61PeMm|IPY=rt0WSi%2MX)`a z0wXv`Lm4dq;KvF8$oyMHFh?g@m+Qy`tO5LuI#W2U4GdCg#PnV} zlGA1mPOWY&U*c2cPIJo9!AYoHxqTI_tGc>reO&D$}nZ^Nyf zZ7i*iy2Y63E!^^;{Yh(_nYHa{iI)M6oXH zbQYh4ufp%NUJ-1S%|lAK{i0ea zY}`sdUYYx{4KvLEt_ssC_DGYVTpwaAohXu@Jke+873+Cg;95)sR0k7eB*mmHuh3Lv zoy;}qp_kY1E3jf5l#&QXJ`r_#RNOmy(Tj|du@yDpJ$^-9Ao-WC zENR7Q!+??Qet)DS!A3DpeAZZq&*>1&Qi+M6J@QPp!JvKkgx;kx0>ZxN z0DY6*7jPOT;)^rRg;aJ~lx)nXN5KyRGOiru*pWd6o4;A08B96esgtVJFOTK#FUubt zD|WqrORsBT>GG3P9!&@q;z1G(w%obJO*!SGVNsF^b!&9lgf;hOyzsIsg~>)?JzmnI z634{PdUT{d2)~`L=BqH>nMG@E@!mk<5Y&zlvyFlum%ZqKN02Bp-RqyLhEexegt*M# z8$ll$O=LU9nzBx3*MX$Cic8iu2WInH1DUtwVJva&Gvo|2n~_jQHZ_huXT0w;|A_ z{s;&5;q=nF50CWplExywT!q#6kv5`jy`FtOU)YS3P3HDp>Aq_#$kkrsJP^FF)%z^K zBa>9wh#2XDZ8XvMZ3k9yvW}nEgSTx*MpKRpS<2pNm3IF4X0$U0p2k$Z0o>PB`xvJmZ-iT+Z>w=iT#x^WA9q+fJ{EvS>Y z-J4dAH=ZJX8BX(8A|G7ETtimG%*{|fKqp@7-dy$D^zjBsW2xeAl3TWUVr@z9pqD7k z%ZsmGJ)xAOV-m@s>!NPr=RJHV#aW`e2fm;9(sJfK0;m%ov-U4FV13>EzuIGQf&039 zdYp8Tgi-=_{>@h}G!Swt>GG)h4EmpA8M$|+uw8-wnwt?VRv_#YFk#c0g7 zK#xf}N@%m;1zEKQNMq7o$tpIQIOeO;fGj&+>;&R$=fK+L-1DioIht58JNJ%{*cj-YA8|c(slF%$zl;_wmThGe4w#%e9VT#D; z52QbwKLs`p2!;%BVhp-?kUUXV0o0kVB)wGa610Xw?&Vavq%_=Aw-J@-&y%=xE9A!Y zmC1EdVrhM7_4*vSsnqsAp+TQ_+TUy}SBU*5(r$6Zc@5{!dZ^6$BEH;yJvHX-$4;Ul z%sL3{Mja9k8l$zKkxcJwzQG;(<<++COd(en^q>x-*!mInbgHEYLMmqg%B*=}M6kP; zYhBH~>d9ag^~x>HMgqk)MAnLi2c}Xe!16JpChCcnN_q5btL_b(-I6%R#D)7g78(33KYX2y8jDuc6HR``VLw@X4;vtp zAwK4M{S-EEFJO4(_q}x0L6C{-pONBw5^wu^w~aFT*to_x#EVwFvJmM$pDc%_JHyGS zTUCd>9SHUzW5EzmkW@CqfUP@ed;EIN<3-7jxra1%qma5WXsRnfTxFUY-_?O=!LnVlvyKV$ zYC_(JqFEYrA-WJ)ug38XmU$dexGzZ{E6ubr3yw5b>txAjUgVua2nz&P+}-j-GMsjA z(Z?~(%{~(7Gpo?blULD&kaO-{ND>&KMg?4u50H?am_LXO+ox3e%Ciy3rR`qd7dO7nkJh~{Um+0&aNw$J7UmX^Ntp1 z8(wW(e68j)U;Hx7-t2XVxmveOV^z}qII#RXv&5aHK)d0V; zJSFx`4%;{XC$bI!guZ4T0Pq6(3E_H6dLswEmHx~78}c)2RewVEpFn bool: + def func_allpack_cmd(self, elec_num, elec_use_num, elec_vol:int=50, assembly_type:int=7, assembly_pressure:int=4200, file_path: str="/Users/sml/work") -> bool: elec_num, elec_use_num, elec_vol, assembly_type, assembly_pressure = int(elec_num), int(elec_use_num), int(elec_vol), int(assembly_type), int(assembly_pressure) summary_csv_file = os.path.join(file_path, "duandian.csv") # 如果断点文件存在,先读取之前的进度 diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_1105.csv b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_1105.csv new file mode 100644 index 0000000..3f7b357 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly_1105.csv @@ -0,0 +1,64 @@ +Name,DataType,InitValue,Comment,Attribute,DeviceType,Address, +COIL_SYS_START_CMD,BOOL,,,,coil,9010, +COIL_SYS_STOP_CMD,BOOL,,,,coil,9020, +COIL_SYS_RESET_CMD,BOOL,,,,coil,9030, +COIL_SYS_HAND_CMD,BOOL,,,,coil,9040, +COIL_SYS_AUTO_CMD,BOOL,,,,coil,9050, +COIL_SYS_INIT_CMD,BOOL,,,,coil,9060, +COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,,,,coil,9700, +COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,,,,coil,9710,unilab_rec_msg_succ_cmd +COIL_SYS_START_STATUS,BOOL,,,,coil,9210, +COIL_SYS_STOP_STATUS,BOOL,,,,coil,9220, +COIL_SYS_RESET_STATUS,BOOL,,,,coil,9230, +COIL_SYS_HAND_STATUS,BOOL,,,,coil,9240, +COIL_SYS_AUTO_STATUS,BOOL,,,,coil,9250, +COIL_SYS_INIT_STATUS,BOOL,,,,coil,9260, +COIL_REQUEST_REC_MSG_STATUS,BOOL,,,,coil,9500, +COIL_REQUEST_SEND_MSG_STATUS,BOOL,,,,coil,9510,request_send_msg_status +REG_MSG_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,17000, +REG_MSG_ELECTROLYTE_NUM,INT16,,,,hold_register,17002,unilab_send_msg_electrolyte_num +REG_MSG_ELECTROLYTE_VOLUME,INT16,,,,hold_register,17004,unilab_send_msg_electrolyte_vol +REG_MSG_ASSEMBLY_TYPE,INT16,,,,hold_register,17006,unilab_send_msg_assembly_type +REG_MSG_ASSEMBLY_PRESSURE,INT16,,,,hold_register,17008,unilab_send_msg_assembly_pressure +REG_DATA_ASSEMBLY_COIN_CELL_NUM,INT16,,,,hold_register,16000,data_assembly_coin_cell_num +REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,,,,hold_register,16002,data_open_circuit_voltage +REG_DATA_AXIS_X_POS,FLOAT32,,,,hold_register,16004, +REG_DATA_AXIS_Y_POS,FLOAT32,,,,hold_register,16006, +REG_DATA_AXIS_Z_POS,FLOAT32,,,,hold_register,16008, +REG_DATA_POLE_WEIGHT,FLOAT32,,,,hold_register,16010,data_pole_weight +REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,,,,hold_register,16012,data_assembly_time +REG_DATA_ASSEMBLY_PRESSURE,INT16,,,,hold_register,16014,data_assembly_pressure +REG_DATA_ELECTROLYTE_VOLUME,INT16,,,,hold_register,16016,data_electrolyte_volume +REG_DATA_COIN_NUM,INT16,,,,hold_register,16018,data_coin_num +REG_DATA_ELECTROLYTE_CODE,STRING,,,,hold_register,16020,data_electrolyte_code() +REG_DATA_COIN_CELL_CODE,STRING,,,,hold_register,16030,data_coin_cell_code() +REG_DATA_STACK_VISON_CODE,STRING,,,,hold_register,18004,data_stack_vision_code() +REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,,,,hold_register,16050,data_glove_box_pressure +REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,,,,hold_register,16052,data_glove_box_water_content +REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,,,,hold_register,16054,data_glove_box_o2_content +UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,9720, +UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,9520, +REG_MSG_ELECTROLYTE_NUM_USED,INT16,,,,hold_register,17496, +REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,16000, +UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,9730, +UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,9530, +REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,16018,ASSEMBLY_TYPE7or8 +COIL_ALUMINUM_FOIL,BOOL,,使用铝箔垫,,coil,9340, +REG_MSG_NE_PLATE_MATRIX,INT16,,负极片矩阵点位,,hold_register,17440, +REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,隔膜矩阵点位,,hold_register,17450, +REG_MSG_TIP_BOX_MATRIX,INT16,,移液枪头矩阵点位,,hold_register,17480, +REG_MSG_NE_PLATE_NUM,INT16,,负极片盘数,,hold_register,17443, +REG_MSG_SEPARATOR_PLATE_NUM,INT16,,隔膜盘数,,hold_register,17453, +REG_MSG_PRESS_MODE,BOOL,,压制模式(false:压力检测模式,True:距离模式),,coil,9360,电池压制模式 +,,,,,,, +,BOOL,,视觉对位(false:使用,true:忽略),,coil,9300,视觉对位 +,BOOL,,复检(false:使用,true:忽略),,coil,9310,视觉复检 +,BOOL,,手套箱_左仓(false:使用,true:忽略),,coil,9320,手套箱左仓 +,BOOL,,手套箱_右仓(false:使用,true:忽略),,coil,9420,手套箱右仓 +,BOOL,,真空检知(false:使用,true:忽略),,coil,9350,真空检知 +,BOOL,,电解液添加模式(false:单次滴液,true:二次滴液),,coil,9370,滴液模式 +,BOOL,,正极片称重(false:使用,true:忽略),,coil,9380,正极片称重 +,BOOL,,正负极片组装方式(false:正装,true:倒装),,coil,9390,正负极反装 +,BOOL,,压制清洁(false:使用,true:忽略),,coil,9400,压制清洁 +,BOOL,,物料盘摆盘方式(false:水平摆盘,true:堆叠摆盘),,coil,9410,负极片摆盘方式 +REG_MSG_BATTERY_CLEAN_IGNORE,BOOL,,忽略电池清洁(false:使用,true:忽略),,coil,9460, diff --git a/unilabos/resources/bioyond/YB_bottle_carriers.py b/unilabos/resources/bioyond/YB_bottle_carriers.py index 01d64c5..5284f0a 100644 --- a/unilabos/resources/bioyond/YB_bottle_carriers.py +++ b/unilabos/resources/bioyond/YB_bottle_carriers.py @@ -355,7 +355,7 @@ def YB_6x5ml_DispensingVialCarrier(name: str) -> BottleCarrier: size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="6x5ml_DispensingVialCarrier", + model="YB_6x5ml_DispensingVialCarrier", ) carrier.num_items_x = 4 carrier.num_items_y = 2 @@ -554,7 +554,7 @@ def YB_jia_yang_tou_da_1X1_carrier(name: str) -> BottleCarrier: size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="6x_LargeDispenseHeadCarrier", + model="YB_6x_LargeDispenseHeadCarrier", ) carrier.num_items_x = 1 carrier.num_items_y = 1 diff --git a/unilabos/resources/bioyond/YB_bottles.py b/unilabos/resources/bioyond/YB_bottles.py index 8ebdb41..432383e 100644 --- a/unilabos/resources/bioyond/YB_bottles.py +++ b/unilabos/resources/bioyond/YB_bottles.py @@ -87,7 +87,7 @@ def YB_fen_ye_5ml_Bottle( height=height, max_volume=max_volume, barcode=barcode, - model="Separation_Bottle_5ml", + model="YB_fen_ye_5ml_Bottle", ) """20ml分液瓶""" From a2a8ee9088278ba571fa53ede75c6416068e0273 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Fri, 31 Oct 2025 21:43:25 +0800 Subject: [PATCH 071/104] fix run async execution error --- unilabos/ros/nodes/base_device_node.py | 39 ++++++++------------------ unilabos/utils/async_util.py | 22 --------------- 2 files changed, 12 insertions(+), 49 deletions(-) delete mode 100644 unilabos/utils/async_util.py diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index f106312..edf41fb 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -53,7 +53,7 @@ from unilabos.ros.nodes.resource_tracker import ( ) from unilabos.ros.x.rclpyx import get_event_loop from unilabos.ros.utils.driver_creator import WorkstationNodeCreator, PyLabRobotCreator, DeviceClassCreator -from unilabos.utils.async_util import run_async_func +from rclpy.task import Task from unilabos.utils.import_manager import default_manager from unilabos.utils.log import info, debug, warning, error, critical, logger, trace from unilabos.utils.type_check import get_type_class, TypeEncoder, get_result_info_str @@ -1385,18 +1385,19 @@ class ROS2DeviceNode: 它不继承设备类,而是通过代理模式访问设备类的属性和方法。 """ - # 类变量,用于循环管理 - _loop = None - _loop_running = False - _loop_thread = None - @classmethod - def get_loop(cls): - return cls._loop + def run_async_func(cls, func, trace_error=True, **kwargs) -> Task: + def _handle_future_exception(fut): + try: + fut.result() + except Exception as e: + error(f"异步任务 {func.__name__} 报错了") + error(traceback.format_exc()) - @classmethod - def run_async_func(cls, func, trace_error=True, **kwargs): - return run_async_func(func, loop=cls._loop, trace_error=trace_error, **kwargs) + future = rclpy.get_global_executor().create_task(func(**kwargs)) + if trace_error: + future.add_done_callback(_handle_future_exception) + return future @property def driver_instance(self): @@ -1436,11 +1437,6 @@ class ROS2DeviceNode: print_publish: 是否打印发布信息 driver_is_ros: """ - # 在初始化时检查循环状态 - if ROS2DeviceNode._loop_running and ROS2DeviceNode._loop_thread is not None: - pass - elif ROS2DeviceNode._loop_thread is None: - self._start_loop() # 保存设备类是否支持异步上下文 self._has_async_context = hasattr(driver_class, "__aenter__") and hasattr(driver_class, "__aexit__") @@ -1529,17 +1525,6 @@ class ROS2DeviceNode: except Exception as e: self._ros_node.lab_logger().error(f"设备后初始化失败: {e}") - def _start_loop(self): - def run_event_loop(): - loop = asyncio.new_event_loop() - ROS2DeviceNode._loop = loop - asyncio.set_event_loop(loop) - loop.run_forever() - - ROS2DeviceNode._loop_thread = threading.Thread(target=run_event_loop, daemon=True, name="ROS2DeviceNodeLoop") - ROS2DeviceNode._loop_thread.start() - logger.info(f"循环线程已启动") - class DeviceInfoType(TypedDict): id: str diff --git a/unilabos/utils/async_util.py b/unilabos/utils/async_util.py deleted file mode 100644 index 0f50a73..0000000 --- a/unilabos/utils/async_util.py +++ /dev/null @@ -1,22 +0,0 @@ -import asyncio -import traceback -from asyncio import get_event_loop - -from unilabos.utils.log import error - - -def run_async_func(func, *, loop=None, trace_error=True, **kwargs): - if loop is None: - loop = get_event_loop() - - def _handle_future_exception(fut): - try: - fut.result() - except Exception as e: - error(f"异步任务 {func.__name__} 报错了") - error(traceback.format_exc()) - - future = asyncio.run_coroutine_threadsafe(func(**kwargs), loop) - if trace_error: - future.add_done_callback(_handle_future_exception) - return future From 4c3972820bca6c572394fd7d63a4e1b5d8641ac1 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Mon, 3 Nov 2025 15:42:12 +0800 Subject: [PATCH 072/104] support sleep and create_task in node --- .../devices/liquid_handling/prcxi/prcxi.py | 395 +++++++++++------- unilabos/ros/nodes/base_device_node.py | 19 +- unilabos/ros/nodes/presets/host_node.py | 3 +- 3 files changed, 257 insertions(+), 160 deletions(-) diff --git a/unilabos/devices/liquid_handling/prcxi/prcxi.py b/unilabos/devices/liquid_handling/prcxi/prcxi.py index d9c0433..a8677f4 100644 --- a/unilabos/devices/liquid_handling/prcxi/prcxi.py +++ b/unilabos/devices/liquid_handling/prcxi/prcxi.py @@ -30,6 +30,7 @@ from pylabrobot.liquid_handling.standard import ( from pylabrobot.resources import Tip, Deck, Plate, Well, TipRack, Resource, Container, Coordinate, TipSpot, Trash from unilabos.devices.liquid_handling.liquid_handler_abstract import LiquidHandlerAbstract +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode class PRCXIError(RuntimeError): @@ -162,6 +163,10 @@ class PRCXI9300Handler(LiquidHandlerAbstract): ) super().__init__(backend=self._unilabos_backend, deck=deck, simulator=simulator, channel_num=channel_num) + def post_init(self, ros_node: BaseROS2DeviceNode): + super().post_init(ros_node) + self._unilabos_backend.post_init(ros_node) + def set_liquid(self, wells: list[Well], liquid_names: list[str], volumes: list[float]): return super().set_liquid(wells, liquid_names, volumes) @@ -424,6 +429,7 @@ class PRCXI9300Backend(LiquidHandlerBackend): _num_channels = 8 # 默认通道数为 8 _is_reset_ok = False + _ros_node: BaseROS2DeviceNode @property def is_reset_ok(self) -> bool: @@ -456,6 +462,9 @@ class PRCXI9300Backend(LiquidHandlerBackend): self._execute_setup = setup self.debug = debug + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + def create_protocol(self, protocol_name): self.protocol_name = protocol_name self.steps_todo_list = [] @@ -500,7 +509,7 @@ class PRCXI9300Backend(LiquidHandlerBackend): self.api_client.call("IAutomation", "Reset") while not self.is_reset_ok: print("Waiting for PRCXI9300 to reset...") - await asyncio.sleep(1) + await self._ros_node.sleep(1) print("PRCXI9300 reset successfully.") except ConnectionRefusedError as e: raise RuntimeError( @@ -533,7 +542,9 @@ class PRCXI9300Backend(LiquidHandlerBackend): tipspot_index = tipspot.parent.children.index(tipspot) tip_columns.append(tipspot_index // 8) if len(set(tip_columns)) != 1: - raise ValueError("All pickups must be from the same tip column. Found different columns: " + str(tip_columns)) + raise ValueError( + "All pickups must be from the same tip column. Found different columns: " + str(tip_columns) + ) PlateNo = plate_indexes[0] + 1 hole_col = tip_columns[0] + 1 hole_row = 1 @@ -1109,12 +1120,15 @@ class PRCXI9300Api: "LiquidDispensingMethod": liquid_method, } + class DefaultLayout: def __init__(self, product_name: str = "PRCXI9300"): self.labresource = {} if product_name not in ["PRCXI9300", "PRCXI9320"]: - raise ValueError(f"Unsupported product_name: {product_name}. Only 'PRCXI9300' and 'PRCXI9320' are supported.") + raise ValueError( + f"Unsupported product_name: {product_name}. Only 'PRCXI9300' and 'PRCXI9320' are supported." + ) if product_name == "PRCXI9300": self.rows = 2 @@ -1129,25 +1143,93 @@ class DefaultLayout: self.layout = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] self.trash_slot = 16 self.waste_liquid_slot = 12 - self.default_layout = {"MatrixId":f"{time.time()}","MatrixName":f"{time.time()}","MatrixCount":16,"WorkTablets": - [{"Number": 1, "Code": "T1", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 2, "Code": "T2", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 3, "Code": "T3", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 4, "Code": "T4", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 5, "Code": "T5", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 6, "Code": "T6", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 7, "Code": "T7", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 8, "Code": "T8", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 9, "Code": "T9", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 10, "Code": "T10", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 11, "Code": "T11", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 12, "Code": "T12", "Material": {"uuid": "730067cf07ae43849ddf4034299030e9", "materialEnum": 0}}, # 这个设置成废液槽,用储液槽表示 - {"Number": 13, "Code": "T13", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 14, "Code": "T14", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 15, "Code": "T15", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}}, - {"Number": 16, "Code": "T16", "Material": {"uuid": "730067cf07ae43849ddf4034299030e9", "materialEnum": 0}} # 这个设置成垃圾桶,用储液槽表示 -] -} + self.default_layout = { + "MatrixId": f"{time.time()}", + "MatrixName": f"{time.time()}", + "MatrixCount": 16, + "WorkTablets": [ + { + "Number": 1, + "Code": "T1", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 2, + "Code": "T2", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 3, + "Code": "T3", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 4, + "Code": "T4", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 5, + "Code": "T5", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 6, + "Code": "T6", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 7, + "Code": "T7", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 8, + "Code": "T8", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 9, + "Code": "T9", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 10, + "Code": "T10", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 11, + "Code": "T11", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 12, + "Code": "T12", + "Material": {"uuid": "730067cf07ae43849ddf4034299030e9", "materialEnum": 0}, + }, # 这个设置成废液槽,用储液槽表示 + { + "Number": 13, + "Code": "T13", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 14, + "Code": "T14", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 15, + "Code": "T15", + "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}, + }, + { + "Number": 16, + "Code": "T16", + "Material": {"uuid": "730067cf07ae43849ddf4034299030e9", "materialEnum": 0}, + }, # 这个设置成垃圾桶,用储液槽表示 + ], + } def get_layout(self) -> Dict[str, Any]: return { @@ -1155,7 +1237,7 @@ class DefaultLayout: "columns": self.columns, "layout": self.layout, "trash_slot": self.trash_slot, - "waste_liquid_slot": self.waste_liquid_slot + "waste_liquid_slot": self.waste_liquid_slot, } def get_trash_slot(self) -> int: @@ -1178,17 +1260,19 @@ class DefaultLayout: reserved_positions = {12, 16} available_positions = [i for i in range(1, 17) if i not in reserved_positions] - # 计算总需求 + # 计算总需求 total_needed = sum(count for _, _, count in needs) if total_needed > len(available_positions): - raise ValueError(f"需要 {total_needed} 个位置,但只有 {len(available_positions)} 个可用位置(排除位置12和16)") + raise ValueError( + f"需要 {total_needed} 个位置,但只有 {len(available_positions)} 个可用位置(排除位置12和16)" + ) # 依次分配位置 current_pos = 0 for reagent_name, material_name, count in needs: - material_uuid = self.labresource[material_name]['uuid'] - material_enum = self.labresource[material_name]['materialEnum'] + material_uuid = self.labresource[material_name]["uuid"] + material_enum = self.labresource[material_name]["materialEnum"] for _ in range(count): if current_pos >= len(available_positions): @@ -1196,17 +1280,18 @@ class DefaultLayout: position = available_positions[current_pos] # 找到对应的tablet并更新 - for tablet in self.default_layout['WorkTablets']: - if tablet['Number'] == position: - tablet['Material']['uuid'] = material_uuid - tablet['Material']['materialEnum'] = material_enum - layout_list.append(dict(reagent_name=reagent_name, material_name=material_name, positions=position)) + for tablet in self.default_layout["WorkTablets"]: + if tablet["Number"] == position: + tablet["Material"]["uuid"] = material_uuid + tablet["Material"]["materialEnum"] = material_enum + layout_list.append( + dict(reagent_name=reagent_name, material_name=material_name, positions=position) + ) break current_pos += 1 return self.default_layout, layout_list - if __name__ == "__main__": # Example usage # 1. 用导出的json,给每个T1 T2板子设定相应的物料,如果是孔板和枪头盒,要对应区分 @@ -1302,10 +1387,7 @@ if __name__ == "__main__": # # # plate2.set_well_liquids(plate_2_liquids) - - - - # handler = PRCXI9300Handler(deck=deck, host="10.181.214.132", port=9999, + # handler = PRCXI9300Handler(deck=deck, host="10.181.214.132", port=9999, # timeout=10.0, setup=False, debug=False, # simulator=True, # matrix_id="71593", @@ -1391,10 +1473,7 @@ if __name__ == "__main__": # # input("Press Enter to continue...") # Wait for user input before proceeding # # print("PRCXI9300Handler initialized with deck and host settings.") - - -### 9320 ### - + ### 9320 ### deck = PRCXI9300Deck(name="PRCXI_Deck", size_x=100, size_y=100, size_z=100) @@ -1412,12 +1491,15 @@ if __name__ == "__main__": new_plate: PRCXI9300Container = PRCXI9300Container.deserialize(well_containers) return new_plate - def get_tip_rack(name: str, child_prefix: str="tip") -> PRCXI9300Container: + def get_tip_rack(name: str, child_prefix: str = "tip") -> PRCXI9300Container: tip_racks = opentrons_96_tiprack_10ul(name).serialize() tip_rack = PRCXI9300Container( - name=name, size_x=50, size_y=50, size_z=10, category="tip_rack", ordering=collections.OrderedDict({ - k: f"{child_prefix}_{k}" for k, v in tip_racks["ordering"].items() - }) + name=name, + size_x=50, + size_y=50, + size_z=10, + category="tip_rack", + ordering=collections.OrderedDict({k: f"{child_prefix}_{k}" for k, v in tip_racks["ordering"].items()}), ) tip_rack_serialized = tip_rack.serialize() tip_rack_serialized["parent_name"] = deck.name @@ -1629,6 +1711,7 @@ if __name__ == "__main__": ) backend: PRCXI9300Backend = handler.backend from pylabrobot.resources import set_volume_tracking + set_volume_tracking(enabled=True) # res = backend.api_client.get_all_materials() asyncio.run(handler.setup()) # Initialize the handler and setup the connection @@ -1640,10 +1723,10 @@ if __name__ == "__main__": for well in plate13.get_all_items(): # well_pos = well.name.split("_")[1] # 走一行 - # if well_pos.startswith("A"): - if well.name.startswith("PlateT13"): # 走整个Plate + # if well_pos.startswith("A"): + if well.name.startswith("PlateT13"): # 走整个Plate asyncio.run(handler.dispense([well], [0.01], [0])) - + # asyncio.run(handler.dispense([plate10.get_item("H12")], [1], [0])) # asyncio.run(handler.dispense([plate13.get_item("A1")], [1], [0])) # asyncio.run(handler.dispense([plate14.get_item("C5")], [1], [0])) @@ -1652,26 +1735,25 @@ if __name__ == "__main__": asyncio.run(handler.run_protocol()) time.sleep(5) os._exit(0) -# 第一种情景:一个孔往多个孔加液 + # 第一种情景:一个孔往多个孔加液 # plate_2_liquids = handler.set_group("water", [plate2.children[0]], [300]) # plate5_liquids = handler.set_group("master_mix", plate5.children[:23], [100]*23) -# 第二个情景:多个孔往多个孔加液(但是个数得对应) - plate_2_liquids = handler.set_group("water", plate2.children[:23], [300]*23) - plate5_liquids = handler.set_group("master_mix", plate5.children[:23], [100]*23) + # 第二个情景:多个孔往多个孔加液(但是个数得对应) + plate_2_liquids = handler.set_group("water", plate2.children[:23], [300] * 23) + plate5_liquids = handler.set_group("master_mix", plate5.children[:23], [100] * 23) # plate11.set_well_liquids([("Water", 100) if (i % 8 == 0 and i // 8 < 6) else (None, 100) for i in range(96)]) # Set liquids for every 8 wells in plate8 # plate11.set_well_liquids([("Water", 100) if (i % 8 == 0 and i // 8 < 6) else (None, 100) for i in range(96)]) # Set liquids for every 8 wells in plate8 -# A = tree_to_list([resource_plr_to_ulab(deck)]) -# # with open("deck.json", "w", encoding="utf-8") as f: -# # json.dump(A, f, indent=4, ensure_ascii=False) + # A = tree_to_list([resource_plr_to_ulab(deck)]) + # # with open("deck.json", "w", encoding="utf-8") as f: + # # json.dump(A, f, indent=4, ensure_ascii=False) -# print(plate11.get_well(0).tracker.get_used_volume()) - # Initialize the backend and setup the connection + # print(plate11.get_well(0).tracker.get_used_volume()) + # Initialize the backend and setup the connection asyncio.run(handler.transfer_group("water", "master_mix", 10)) # Reset tip tracking - # asyncio.run(handler.pick_up_tips([plate8.children[8]],[0])) # print(plate8.children[8]) # asyncio.run(handler.run_protocol()) @@ -1685,121 +1767,118 @@ if __name__ == "__main__": # print(plate1.children[0]) # asyncio.run(handler.discard_tips([0])) -# asyncio.run(handler.add_liquid( -# asp_vols=[10]*7, -# dis_vols=[10]*7, -# reagent_sources=plate11.children[:7], -# targets=plate1.children[2:9], -# use_channels=[0], -# flow_rates=[None] * 7, -# offsets=[Coordinate(0, 0, 0)] * 7, -# liquid_height=[None] * 7, -# blow_out_air_volume=[None] * 2, -# delays=None, -# mix_time=3, -# mix_vol=5, -# spread="custom", -# )) + # asyncio.run(handler.add_liquid( + # asp_vols=[10]*7, + # dis_vols=[10]*7, + # reagent_sources=plate11.children[:7], + # targets=plate1.children[2:9], + # use_channels=[0], + # flow_rates=[None] * 7, + # offsets=[Coordinate(0, 0, 0)] * 7, + # liquid_height=[None] * 7, + # blow_out_air_volume=[None] * 2, + # delays=None, + # mix_time=3, + # mix_vol=5, + # spread="custom", + # )) # asyncio.run(handler.run_protocol()) # Run the protocol + # # # asyncio.run(handler.transfer_liquid( + # # # asp_vols=[10]*2, + # # # dis_vols=[10]*2, + # # # sources=plate11.children[:2], + # # # targets=plate11.children[-2:], + # # # use_channels=[0], + # # # offsets=[Coordinate(0, 0, 0)] * 4, + # # # liquid_height=[None] * 2, + # # # blow_out_air_volume=[None] * 2, + # # # delays=None, + # # # mix_times=3, + # # # mix_vol=5, + # # # spread="wide", + # # # tip_racks=[plate8] + # # # )) + # # # asyncio.run(handler.remove_liquid( + # # # vols=[10]*2, + # # # sources=plate11.children[:2], + # # # waste_liquid=plate11.children[43], + # # # use_channels=[0], + # # # offsets=[Coordinate(0, 0, 0)] * 4, + # # # liquid_height=[None] * 2, + # # # blow_out_air_volume=[None] * 2, + # # # delays=None, + # # # spread="wide" + # # # )) + # # asyncio.run(handler.run_protocol()) + # # # asyncio.run(handler.discard_tips()) + # # # asyncio.run(handler.mix(well_containers.children[:8 + # # # ], mix_time=3, mix_vol=50, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100)) + # # #print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info -# # # asyncio.run(handler.transfer_liquid( -# # # asp_vols=[10]*2, -# # # dis_vols=[10]*2, -# # # sources=plate11.children[:2], -# # # targets=plate11.children[-2:], -# # # use_channels=[0], -# # # offsets=[Coordinate(0, 0, 0)] * 4, -# # # liquid_height=[None] * 2, -# # # blow_out_air_volume=[None] * 2, -# # # delays=None, -# # # mix_times=3, -# # # mix_vol=5, -# # # spread="wide", -# # # tip_racks=[plate8] -# # # )) - -# # # asyncio.run(handler.remove_liquid( -# # # vols=[10]*2, -# # # sources=plate11.children[:2], -# # # waste_liquid=plate11.children[43], -# # # use_channels=[0], -# # # offsets=[Coordinate(0, 0, 0)] * 4, -# # # liquid_height=[None] * 2, -# # # blow_out_air_volume=[None] * 2, -# # # delays=None, -# # # spread="wide" -# # # )) -# # asyncio.run(handler.run_protocol()) - -# # # asyncio.run(handler.discard_tips()) -# # # asyncio.run(handler.mix(well_containers.children[:8 -# # # ], mix_time=3, mix_vol=50, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100)) -# # #print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info - - -# # # asyncio.run(handler.remove_liquid( -# # # vols=[100]*16, -# # # sources=well_containers.children[-16:], -# # # waste_liquid=well_containers.children[:16], # 这个有些奇怪,但是好像也只能这么写 -# # # use_channels=[0, 1, 2, 3, 4, 5, 6, 7], -# # # flow_rates=[None] * 32, -# # # offsets=[Coordinate(0, 0, 0)] * 32, -# # # liquid_height=[None] * 32, -# # # blow_out_air_volume=[None] * 32, -# # # spread="wide", -# # # )) -# # # asyncio.run(handler.transfer_liquid( -# # # asp_vols=[100]*16, -# # # dis_vols=[100]*16, -# # # tip_racks=[tip_rack], -# # # sources=well_containers.children[-16:], -# # # targets=well_containers.children[:16], -# # # use_channels=[0, 1, 2, 3, 4, 5, 6, 7], -# # # offsets=[Coordinate(0, 0, 0)] * 32, -# # # asp_flow_rates=[None] * 16, -# # # dis_flow_rates=[None] * 16, -# # # liquid_height=[None] * 32, -# # # blow_out_air_volume=[None] * 32, -# # # mix_times=3, -# # # mix_vol=50, -# # # spread="wide", -# # # )) -# # print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info -# # # input("pick_up_tips add step") - #asyncio.run(handler.run_protocol()) # Run the protocol -# # # input("Running protocol...") -# # # input("Press Enter to continue...") # Wait for user input before proceeding -# # # print("PRCXI9300Handler initialized with deck and host settings.") - - -# 一些推荐版位组合的测试样例: - -# 一些推荐版位组合的测试样例: + # # # asyncio.run(handler.remove_liquid( + # # # vols=[100]*16, + # # # sources=well_containers.children[-16:], + # # # waste_liquid=well_containers.children[:16], # 这个有些奇怪,但是好像也只能这么写 + # # # use_channels=[0, 1, 2, 3, 4, 5, 6, 7], + # # # flow_rates=[None] * 32, + # # # offsets=[Coordinate(0, 0, 0)] * 32, + # # # liquid_height=[None] * 32, + # # # blow_out_air_volume=[None] * 32, + # # # spread="wide", + # # # )) + # # # asyncio.run(handler.transfer_liquid( + # # # asp_vols=[100]*16, + # # # dis_vols=[100]*16, + # # # tip_racks=[tip_rack], + # # # sources=well_containers.children[-16:], + # # # targets=well_containers.children[:16], + # # # use_channels=[0, 1, 2, 3, 4, 5, 6, 7], + # # # offsets=[Coordinate(0, 0, 0)] * 32, + # # # asp_flow_rates=[None] * 16, + # # # dis_flow_rates=[None] * 16, + # # # liquid_height=[None] * 32, + # # # blow_out_air_volume=[None] * 32, + # # # mix_times=3, + # # # mix_vol=50, + # # # spread="wide", + # # # )) + # # print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info + # # # input("pick_up_tips add step") + # asyncio.run(handler.run_protocol()) # Run the protocol + # # # input("Running protocol...") + # # # input("Press Enter to continue...") # Wait for user input before proceeding + # # # print("PRCXI9300Handler initialized with deck and host settings.") + # 一些推荐版位组合的测试样例: + # 一些推荐版位组合的测试样例: with open("prcxi_material.json", "r") as f: material_info = json.load(f) layout = DefaultLayout("PRCXI9320") layout.add_lab_resource(material_info) - MatrixLayout_1, dict_1 = layout.recommend_layout([ - ("reagent_1", "96 细胞培养皿", 3), - ("reagent_2", "12道储液槽", 1), - ("reagent_3", "200μL Tip头", 7), - ("reagent_4", "10μL加长 Tip头", 1), - ]) + MatrixLayout_1, dict_1 = layout.recommend_layout( + [ + ("reagent_1", "96 细胞培养皿", 3), + ("reagent_2", "12道储液槽", 1), + ("reagent_3", "200μL Tip头", 7), + ("reagent_4", "10μL加长 Tip头", 1), + ] + ) print(dict_1) - MatrixLayout_2, dict_2 = layout.recommend_layout([ - ("reagent_1", "96深孔板", 4), - ("reagent_2", "12道储液槽", 1), - ("reagent_3", "200μL Tip头", 1), - ("reagent_4", "10μL加长 Tip头", 1), - ]) + MatrixLayout_2, dict_2 = layout.recommend_layout( + [ + ("reagent_1", "96深孔板", 4), + ("reagent_2", "12道储液槽", 1), + ("reagent_3", "200μL Tip头", 1), + ("reagent_4", "10μL加长 Tip头", 1), + ] + ) # with open("prcxi_material.json", "r") as f: # material_info = json.load(f) diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index edf41fb..2fc7ea7 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -53,7 +53,7 @@ from unilabos.ros.nodes.resource_tracker import ( ) from unilabos.ros.x.rclpyx import get_event_loop from unilabos.ros.utils.driver_creator import WorkstationNodeCreator, PyLabRobotCreator, DeviceClassCreator -from rclpy.task import Task +from rclpy.task import Task, Future from unilabos.utils.import_manager import default_manager from unilabos.utils.log import info, debug, warning, error, critical, logger, trace from unilabos.utils.type_check import get_type_class, TypeEncoder, get_result_info_str @@ -555,6 +555,15 @@ class BaseROS2DeviceNode(Node, Generic[T]): rclpy.get_global_executor().add_node(self) self.lab_logger().debug(f"ROS节点初始化完成") + async def sleep(self, rel_time: float, callback_group=None): + if callback_group is None: + callback_group = self.callback_group + await ROS2DeviceNode.async_wait_for(self, rel_time, callback_group) + + @classmethod + async def create_task(cls, func, trace_error=True, **kwargs) -> Task: + return ROS2DeviceNode.run_async_func(func, trace_error, **kwargs) + async def update_resource(self, resources: List["ResourcePLR"]): r = SerialCommand.Request() tree_set = ResourceTreeSet.from_plr_resources(resources) @@ -1399,6 +1408,14 @@ class ROS2DeviceNode: future.add_done_callback(_handle_future_exception) return future + @classmethod + async def async_wait_for(cls, node: Node, wait_time: float, callback_group=None): + future = Future() + timer = node.create_timer(wait_time, lambda : future.set_result(None), callback_group=callback_group, clock=node.get_clock()) + await future + timer.cancel() + node.destroy_timer(timer) + @property def driver_instance(self): return self._driver_instance diff --git a/unilabos/ros/nodes/presets/host_node.py b/unilabos/ros/nodes/presets/host_node.py index 43d16e8..346cf9c 100644 --- a/unilabos/ros/nodes/presets/host_node.py +++ b/unilabos/ros/nodes/presets/host_node.py @@ -18,7 +18,8 @@ from unilabos_msgs.srv import ( ResourceDelete, ResourceUpdate, ResourceList, - SerialCommand, ResourceGet, + SerialCommand, + ResourceGet, ) # type: ignore from unilabos_msgs.srv._serial_command import SerialCommand_Request, SerialCommand_Response from unique_identifier_msgs.msg import UUID From 0f341e9b4dbee0f6e18ef2332578685f0c4d1026 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Mon, 3 Nov 2025 15:49:11 +0800 Subject: [PATCH 073/104] modify devices to use correct executor (sleep, create_task) --- test/registry/example_devices.py | 33 +- unilabos/devices/cnc/grbl_async.py | 11 +- unilabos/devices/cnc/mock.py | 9 +- .../laiyu_liquid/core/laiyu_liquid_main.py | 423 +++++++++--------- .../liquid_handler_abstract.py | 10 +- .../devices/pump_and_valve/runze_async.py | 11 +- .../devices/virtual/virtual_centrifuge.py | 9 +- unilabos/devices/virtual/virtual_column.py | 9 +- unilabos/devices/virtual/virtual_filter.py | 202 ++++----- unilabos/devices/virtual/virtual_heatchill.py | 9 +- unilabos/devices/virtual/virtual_rotavap.py | 273 ++++++----- unilabos/devices/virtual/virtual_separator.py | 11 +- .../devices/virtual/virtual_solenoid_valve.py | 10 +- .../virtual/virtual_solid_dispenser.py | 9 +- unilabos/devices/virtual/virtual_stirrer.py | 11 +- .../devices/virtual/virtual_transferpump.py | 13 +- 16 files changed, 597 insertions(+), 456 deletions(-) diff --git a/test/registry/example_devices.py b/test/registry/example_devices.py index d5b26b2..d41c7b4 100644 --- a/test/registry/example_devices.py +++ b/test/registry/example_devices.py @@ -3,7 +3,8 @@ """ import asyncio -from typing import Dict, Any, Optional, List +from typing import Dict, Any, List +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode class SmartPumpController: @@ -14,6 +15,8 @@ class SmartPumpController: 适用于实验室自动化系统中的液体处理任务。 """ + _ros_node: BaseROS2DeviceNode + def __init__(self, device_id: str = "smart_pump_01", port: str = "/dev/ttyUSB0"): """ 初始化智能泵控制器 @@ -30,6 +33,9 @@ class SmartPumpController: self.calibration_factor = 1.0 self.pump_mode = "continuous" # continuous, volume, rate + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + def connect_device(self, timeout: int = 10) -> bool: """ 连接到泵设备 @@ -90,7 +96,7 @@ class SmartPumpController: pump_time = (volume / flow_rate) * 60 # 转换为秒 self.current_flow_rate = flow_rate - await asyncio.sleep(min(pump_time, 3.0)) # 模拟泵送过程 + await self._ros_node.sleep(min(pump_time, 3.0)) # 模拟泵送过程 self.total_volume_pumped += volume self.current_flow_rate = 0.0 @@ -170,6 +176,8 @@ class AdvancedTemperatureController: 适用于需要精确温度控制的化学反应和材料处理过程。 """ + _ros_node: BaseROS2DeviceNode + def __init__(self, controller_id: str = "temp_controller_01"): """ 初始化温度控制器 @@ -185,6 +193,9 @@ class AdvancedTemperatureController: self.pid_enabled = True self.temperature_history: List[Dict] = [] + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + def set_target_temperature(self, temperature: float, rate: float = 10.0) -> bool: """ 设置目标温度 @@ -238,7 +249,7 @@ class AdvancedTemperatureController: } ) - await asyncio.sleep(step_time) + await self._ros_node.sleep(step_time) # 保持历史记录不超过100条 if len(self.temperature_history) > 100: @@ -330,6 +341,8 @@ class MultiChannelAnalyzer: 常用于光谱分析、电化学测量等应用场景。 """ + _ros_node: BaseROS2DeviceNode + def __init__(self, analyzer_id: str = "analyzer_01", channels: int = 8): """ 初始化多通道分析仪 @@ -344,6 +357,9 @@ class MultiChannelAnalyzer: self.is_measuring = False self.sample_rate = 1000 # Hz + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + def configure_channel(self, channel: int, enabled: bool = True, unit: str = "V") -> bool: """ 配置通道 @@ -376,7 +392,7 @@ class MultiChannelAnalyzer: # 模拟数据采集 measurements = [] - for second in range(duration): + for _ in range(duration): timestamp = asyncio.get_event_loop().time() frame_data = {} @@ -391,7 +407,7 @@ class MultiChannelAnalyzer: measurements.append({"timestamp": timestamp, "data": frame_data}) - await asyncio.sleep(1.0) # 每秒采集一次 + await self._ros_node.sleep(1.0) # 每秒采集一次 self.is_measuring = False @@ -465,6 +481,8 @@ class AutomatedDispenser: 集成称重功能,确保分配精度和重现性。 """ + _ros_node: BaseROS2DeviceNode + def __init__(self, dispenser_id: str = "dispenser_01"): """ 初始化自动分配器 @@ -479,6 +497,9 @@ class AutomatedDispenser: self.container_capacity = 1000.0 # mL self.precision_mode = True + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + def move_to_position(self, x: float, y: float, z: float) -> bool: """ 移动到指定位置 @@ -517,7 +538,7 @@ class AutomatedDispenser: if viscosity == "high": dispense_time *= 2 # 高粘度液体需要更长时间 - await asyncio.sleep(min(dispense_time, 5.0)) # 最多等待5秒 + await self._ros_node.sleep(min(dispense_time, 5.0)) # 最多等待5秒 self.dispensed_total += volume diff --git a/unilabos/devices/cnc/grbl_async.py b/unilabos/devices/cnc/grbl_async.py index 7e5ac7f..3ecd4ba 100644 --- a/unilabos/devices/cnc/grbl_async.py +++ b/unilabos/devices/cnc/grbl_async.py @@ -12,6 +12,7 @@ from serial import Serial from serial.serialutil import SerialException from unilabos.messages import Point3D +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode class GrblCNCConnectionError(Exception): @@ -32,6 +33,7 @@ class GrblCNCInfo: class GrblCNCAsync: _status: str = "Offline" _position: Point3D = Point3D(x=0.0, y=0.0, z=0.0) + _ros_node: BaseROS2DeviceNode def __init__(self, port: str, address: str = "1", limits: tuple[int, int, int, int, int, int] = (-150, 150, -200, 0, 0, 60)): self.port = port @@ -58,6 +60,9 @@ class GrblCNCAsync: self._run_future: Optional[Future[Any]] = None self._run_lock = Lock() + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + def _read_all(self): data = self._serial.read_until(b"\n") data_decoded = data.decode() @@ -148,7 +153,7 @@ class GrblCNCAsync: try: await self._query(command) while True: - await asyncio.sleep(0.2) # Wait for 0.5 seconds before polling again + await self._ros_node.sleep(0.2) # Wait for 0.5 seconds before polling again status = await self.get_status() if "Idle" in status: @@ -214,7 +219,7 @@ class GrblCNCAsync: self._pose_number = i self.pose_number_remaining = len(points) - i await self.set_position(point) - await asyncio.sleep(0.5) + await self._ros_node.sleep(0.5) self._step_number = -1 async def stop_operation(self): @@ -235,7 +240,7 @@ class GrblCNCAsync: async def open(self): if self._read_task: raise GrblCNCConnectionError - self._read_task = asyncio.create_task(self._read_loop()) + self._read_task = self._ros_node.create_task(self._read_loop()) try: await self.get_status() diff --git a/unilabos/devices/cnc/mock.py b/unilabos/devices/cnc/mock.py index b8c52f1..ebe9683 100644 --- a/unilabos/devices/cnc/mock.py +++ b/unilabos/devices/cnc/mock.py @@ -2,6 +2,8 @@ import time import asyncio from pydantic import BaseModel +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class Point3D(BaseModel): x: float @@ -14,9 +16,14 @@ def d(a: Point3D, b: Point3D) -> float: class MockCNCAsync: + _ros_node: BaseROS2DeviceNode["MockCNCAsync"] + def __init__(self): self._position: Point3D = Point3D(x=0.0, y=0.0, z=0.0) self._status = "Idle" + + def post_create(self, ros_node): + self._ros_node = ros_node @property def position(self) -> Point3D: @@ -38,5 +45,5 @@ class MockCNCAsync: self._position.x = current_pos.x + (position.x - current_pos.x) / 20 * (i+1) self._position.y = current_pos.y + (position.y - current_pos.y) / 20 * (i+1) self._position.z = current_pos.z + (position.z - current_pos.z) / 20 * (i+1) - await asyncio.sleep(move_time / 20) + await self._ros_node.sleep(move_time / 20) self._status = "Idle" diff --git a/unilabos/devices/laiyu_liquid/core/laiyu_liquid_main.py b/unilabos/devices/laiyu_liquid/core/laiyu_liquid_main.py index 9609255..f369a20 100644 --- a/unilabos/devices/laiyu_liquid/core/laiyu_liquid_main.py +++ b/unilabos/devices/laiyu_liquid/core/laiyu_liquid_main.py @@ -15,108 +15,113 @@ from typing import List, Optional, Dict, Any, Union, Tuple from dataclasses import dataclass from abc import ABC, abstractmethod +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + # 基础导入 try: from pylabrobot.resources import Deck, Plate, TipRack, Tip, Resource, Well + PYLABROBOT_AVAILABLE = True except ImportError: # 如果 pylabrobot 不可用,创建基础的模拟类 PYLABROBOT_AVAILABLE = False - + class Resource: def __init__(self, name: str): self.name = name - + class Deck(Resource): pass - + class Plate(Resource): pass - + class TipRack(Resource): pass - + class Tip(Resource): pass - + class Well(Resource): pass + # LaiYu_Liquid 控制器导入 try: - from .controllers.pipette_controller import ( - PipetteController, TipStatus, LiquidClass, LiquidParameters - ) - from .controllers.xyz_controller import ( - XYZController, MachineConfig, CoordinateOrigin, MotorAxis - ) + from .controllers.pipette_controller import PipetteController, TipStatus, LiquidClass, LiquidParameters + from .controllers.xyz_controller import XYZController, MachineConfig, CoordinateOrigin, MotorAxis + CONTROLLERS_AVAILABLE = True except ImportError: CONTROLLERS_AVAILABLE = False + # 创建模拟的控制器类 class PipetteController: def __init__(self, *args, **kwargs): pass - + def connect(self): return True - + def initialize(self): return True - + class XYZController: def __init__(self, *args, **kwargs): pass - + def connect_device(self): return True + logger = logging.getLogger(__name__) class LaiYuLiquidError(RuntimeError): """LaiYu_Liquid 设备异常""" + pass @dataclass class LaiYuLiquidConfig: """LaiYu_Liquid 设备配置""" + port: str = "/dev/cu.usbserial-3130" # RS485转USB端口 address: int = 1 # 设备地址 baudrate: int = 9600 # 波特率 timeout: float = 5.0 # 通信超时时间 - + # 工作台尺寸 deck_width: float = 340.0 # 工作台宽度 (mm) deck_height: float = 250.0 # 工作台高度 (mm) deck_depth: float = 160.0 # 工作台深度 (mm) - + # 移液参数 max_volume: float = 1000.0 # 最大体积 (μL) min_volume: float = 0.1 # 最小体积 (μL) - + # 运动参数 max_speed: float = 100.0 # 最大速度 (mm/s) acceleration: float = 50.0 # 加速度 (mm/s²) - + # 安全参数 safe_height: float = 50.0 # 安全高度 (mm) tip_pickup_depth: float = 10.0 # 吸头拾取深度 (mm) liquid_detection: bool = True # 液面检测 - + # 取枪头相关参数 tip_pickup_speed: int = 30 # 取枪头时的移动速度 (rpm) tip_pickup_acceleration: int = 500 # 取枪头时的加速度 (rpm/s) tip_approach_height: float = 5.0 # 接近枪头时的高度 (mm) tip_pickup_force_depth: float = 2.0 # 强制插入深度 (mm) tip_pickup_retract_height: float = 20.0 # 取枪头后的回退高度 (mm) - + # 丢弃枪头相关参数 tip_drop_height: float = 10.0 # 丢弃枪头时的高度 (mm) tip_drop_speed: int = 50 # 丢弃枪头时的移动速度 (rpm) trash_position: Tuple[float, float, float] = (300.0, 200.0, 0.0) # 垃圾桶位置 (mm) - + # 安全范围配置 deck_width: float = 300.0 # 工作台宽度 (mm) deck_height: float = 200.0 # 工作台高度 (mm) @@ -128,25 +133,25 @@ class LaiYuLiquidConfig: class LaiYuLiquidDeck: """LaiYu_Liquid 工作台管理""" - + def __init__(self, config: LaiYuLiquidConfig): self.config = config self.resources: Dict[str, Resource] = {} self.positions: Dict[str, Tuple[float, float, float]] = {} - + def add_resource(self, name: str, resource: Resource, position: Tuple[float, float, float]): """添加资源到工作台""" self.resources[name] = resource self.positions[name] = position - + def get_resource(self, name: str) -> Optional[Resource]: """获取资源""" return self.resources.get(name) - + def get_position(self, name: str) -> Optional[Tuple[float, float, float]]: """获取资源位置""" return self.positions.get(name) - + def list_resources(self) -> List[str]: """列出所有资源""" return list(self.resources.keys()) @@ -154,8 +159,18 @@ class LaiYuLiquidDeck: class LaiYuLiquidContainer: """LaiYu_Liquid 容器类""" - - def __init__(self, name: str, size_x: float = 0, size_y: float = 0, size_z: float = 0, container_type: str = "", volume: float = 0.0, max_volume: float = 1000.0, lid_height: float = 0.0): + + def __init__( + self, + name: str, + size_x: float = 0, + size_y: float = 0, + size_z: float = 0, + container_type: str = "", + volume: float = 0.0, + max_volume: float = 1000.0, + lid_height: float = 0.0, + ): self.name = name self.size_x = size_x self.size_y = size_y @@ -166,19 +181,19 @@ class LaiYuLiquidContainer: self.max_volume = max_volume self.last_updated = time.time() self.child_resources = {} # 存储子资源 - + @property def is_empty(self) -> bool: return self.volume <= 0.0 - + @property def is_full(self) -> bool: return self.volume >= self.max_volume - + @property def available_volume(self) -> float: return max(0.0, self.max_volume - self.volume) - + def add_volume(self, volume: float) -> bool: """添加体积""" if self.volume + volume <= self.max_volume: @@ -186,7 +201,7 @@ class LaiYuLiquidContainer: self.last_updated = time.time() return True return False - + def remove_volume(self, volume: float) -> bool: """移除体积""" if self.volume >= volume: @@ -194,20 +209,25 @@ class LaiYuLiquidContainer: self.last_updated = time.time() return True return False - + def assign_child_resource(self, resource, location=None): """分配子资源 - 与 PyLabRobot 资源管理系统兼容""" - if hasattr(resource, 'name'): - self.child_resources[resource.name] = { - 'resource': resource, - 'location': location - } + if hasattr(resource, "name"): + self.child_resources[resource.name] = {"resource": resource, "location": location} class LaiYuLiquidTipRack: """LaiYu_Liquid 吸头架类""" - - def __init__(self, name: str, size_x: float = 0, size_y: float = 0, size_z: float = 0, tip_count: int = 96, tip_volume: float = 1000.0): + + def __init__( + self, + name: str, + size_x: float = 0, + size_y: float = 0, + size_z: float = 0, + tip_count: int = 96, + tip_volume: float = 1000.0, + ): self.name = name self.size_x = size_x self.size_y = size_y @@ -216,34 +236,31 @@ class LaiYuLiquidTipRack: self.tip_volume = tip_volume self.tips_available = [True] * tip_count self.child_resources = {} # 存储子资源 - + @property def available_tips(self) -> int: return sum(self.tips_available) - + @property def is_empty(self) -> bool: return self.available_tips == 0 - + def pick_tip(self, position: int) -> bool: """拾取吸头""" if 0 <= position < self.tip_count and self.tips_available[position]: self.tips_available[position] = False return True return False - + def has_tip(self, position: int) -> bool: """检查位置是否有吸头""" if 0 <= position < self.tip_count: return self.tips_available[position] return False - + def assign_child_resource(self, resource, location=None): """分配子资源到指定位置""" - self.child_resources[resource.name] = { - 'resource': resource, - 'location': location - } + self.child_resources[resource.name] = {"resource": resource, "location": location} def get_module_info(): @@ -253,36 +270,32 @@ def get_module_info(): "version": "1.0.0", "description": "LaiYu液体处理工作站模块,提供移液器控制、XYZ轴控制和资源管理功能", "author": "UniLabOS Team", - "capabilities": [ - "移液器控制", - "XYZ轴运动控制", - "吸头架管理", - "板和容器管理", - "资源位置管理" - ], - "dependencies": { - "required": ["serial"], - "optional": ["pylabrobot"] - } + "capabilities": ["移液器控制", "XYZ轴运动控制", "吸头架管理", "板和容器管理", "资源位置管理"], + "dependencies": {"required": ["serial"], "optional": ["pylabrobot"]}, } class LaiYuLiquidBackend: """LaiYu_Liquid 硬件通信后端""" - - def __init__(self, config: LaiYuLiquidConfig, deck: Optional['LaiYuLiquidDeck'] = None): + + _ros_node: BaseROS2DeviceNode + + def __init__(self, config: LaiYuLiquidConfig, deck: Optional["LaiYuLiquidDeck"] = None): self.config = config self.deck = deck # 工作台引用,用于获取资源位置信息 self.pipette_controller = None self.xyz_controller = None self.is_connected = False self.is_initialized = False - + # 状态跟踪 self.current_position = (0.0, 0.0, 0.0) self.tip_attached = False self.current_volume = 0.0 - + + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + def _validate_position(self, x: float, y: float, z: float) -> bool: """验证位置是否在安全范围内""" try: @@ -290,71 +303,71 @@ class LaiYuLiquidBackend: if not (0 <= x <= self.config.deck_width): logger.error(f"X轴位置 {x:.2f}mm 超出范围 [0, {self.config.deck_width}]") return False - + # 检查Y轴范围 if not (0 <= y <= self.config.deck_height): logger.error(f"Y轴位置 {y:.2f}mm 超出范围 [0, {self.config.deck_height}]") return False - + # 检查Z轴范围(负值表示向下,0为工作台表面) if not (-self.config.deck_depth <= z <= self.config.safe_height): logger.error(f"Z轴位置 {z:.2f}mm 超出安全范围 [{-self.config.deck_depth}, {self.config.safe_height}]") return False - + return True except Exception as e: logger.error(f"位置验证失败: {e}") return False - + def _check_hardware_ready(self) -> bool: """检查硬件是否准备就绪""" if not self.is_connected: logger.error("设备未连接") return False - + if CONTROLLERS_AVAILABLE: if self.xyz_controller is None: logger.error("XYZ控制器未初始化") return False - + return True - + async def emergency_stop(self) -> bool: """紧急停止所有运动""" try: logger.warning("执行紧急停止") - + if CONTROLLERS_AVAILABLE and self.xyz_controller: # 停止XYZ控制器 await self.xyz_controller.stop_all_motion() logger.info("XYZ控制器已停止") - + if self.pipette_controller: # 停止移液器控制器 await self.pipette_controller.stop() logger.info("移液器控制器已停止") - + return True except Exception as e: logger.error(f"紧急停止失败: {e}") return False - + async def move_to_safe_position(self) -> bool: """移动到安全位置""" try: if not self._check_hardware_ready(): return False - + safe_position = ( self.config.deck_width / 2, # 工作台中心X self.config.deck_height / 2, # 工作台中心Y - self.config.safe_height # 安全高度Z + self.config.safe_height, # 安全高度Z ) - + if not self._validate_position(*safe_position): logger.error("安全位置无效") return False - + if CONTROLLERS_AVAILABLE and self.xyz_controller: await self.xyz_controller.move_to_work_coord(*safe_position) self.current_position = safe_position @@ -365,33 +378,28 @@ class LaiYuLiquidBackend: self.current_position = safe_position logger.info("模拟移动到安全位置") return True - + except Exception as e: logger.error(f"移动到安全位置失败: {e}") return False - + async def setup(self) -> bool: """设置硬件连接""" try: if CONTROLLERS_AVAILABLE: # 初始化移液器控制器 - self.pipette_controller = PipetteController( - port=self.config.port, - address=self.config.address - ) - + self.pipette_controller = PipetteController(port=self.config.port, address=self.config.address) + # 初始化XYZ控制器 machine_config = MachineConfig() self.xyz_controller = XYZController( - port=self.config.port, - baudrate=self.config.baudrate, - machine_config=machine_config + port=self.config.port, baudrate=self.config.baudrate, machine_config=machine_config ) - + # 连接设备 pipette_connected = await asyncio.to_thread(self.pipette_controller.connect) xyz_connected = await asyncio.to_thread(self.xyz_controller.connect_device) - + if pipette_connected and xyz_connected: self.is_connected = True logger.info("LaiYu_Liquid 硬件连接成功") @@ -404,124 +412,123 @@ class LaiYuLiquidBackend: logger.info("LaiYu_Liquid 运行在模拟模式") self.is_connected = True return True - + except Exception as e: logger.error(f"LaiYu_Liquid 设置失败: {e}") return False - + async def stop(self): """停止设备""" try: - if self.pipette_controller and hasattr(self.pipette_controller, 'disconnect'): + if self.pipette_controller and hasattr(self.pipette_controller, "disconnect"): await asyncio.to_thread(self.pipette_controller.disconnect) - - if self.xyz_controller and hasattr(self.xyz_controller, 'disconnect'): + + if self.xyz_controller and hasattr(self.xyz_controller, "disconnect"): await asyncio.to_thread(self.xyz_controller.disconnect) - + self.is_connected = False self.is_initialized = False logger.info("LaiYu_Liquid 已停止") - + except Exception as e: logger.error(f"LaiYu_Liquid 停止失败: {e}") - + async def move_to(self, x: float, y: float, z: float) -> bool: """移动到指定位置""" try: if not self.is_connected: raise LaiYuLiquidError("设备未连接") - + # 模拟移动 - await asyncio.sleep(0.1) # 模拟移动时间 + await self._ros_node.sleep(0.1) # 模拟移动时间 self.current_position = (x, y, z) logger.debug(f"移动到位置: ({x}, {y}, {z})") return True - + except Exception as e: logger.error(f"移动失败: {e}") return False - + async def pick_up_tip(self, tip_rack: str, position: int) -> bool: """拾取吸头 - 包含真正的Z轴下降控制""" try: # 硬件准备检查 if not self._check_hardware_ready(): return False - + if self.tip_attached: logger.warning("已有吸头附着,无法拾取新吸头") return False - + logger.info(f"开始从 {tip_rack} 位置 {position} 拾取吸头") - + # 获取枪头架位置信息 if self.deck is None: logger.error("工作台未初始化") return False - + tip_position = self.deck.get_position(tip_rack) if tip_position is None: logger.error(f"未找到枪头架 {tip_rack} 的位置信息") return False - + # 计算具体枪头位置(这里简化处理,实际应根据position计算偏移) tip_x, tip_y, tip_z = tip_position - + # 验证所有关键位置的安全性 safe_z = tip_z + self.config.tip_approach_height pickup_z = tip_z - self.config.tip_pickup_force_depth retract_z = tip_z + self.config.tip_pickup_retract_height - - if not (self._validate_position(tip_x, tip_y, safe_z) and - self._validate_position(tip_x, tip_y, pickup_z) and - self._validate_position(tip_x, tip_y, retract_z)): + + if not ( + self._validate_position(tip_x, tip_y, safe_z) + and self._validate_position(tip_x, tip_y, pickup_z) + and self._validate_position(tip_x, tip_y, retract_z) + ): logger.error("枪头拾取位置超出安全范围") return False - + if CONTROLLERS_AVAILABLE and self.xyz_controller: # 真实硬件控制流程 logger.info("使用真实XYZ控制器进行枪头拾取") - + try: # 1. 移动到枪头上方的安全位置 safe_z = tip_z + self.config.tip_approach_height logger.info(f"移动到枪头上方安全位置: ({tip_x:.2f}, {tip_y:.2f}, {safe_z:.2f})") move_success = await asyncio.to_thread( - self.xyz_controller.move_to_work_coord, - tip_x, tip_y, safe_z + self.xyz_controller.move_to_work_coord, tip_x, tip_y, safe_z ) if not move_success: logger.error("移动到枪头上方失败") return False - + # 2. Z轴下降到枪头位置 pickup_z = tip_z - self.config.tip_pickup_force_depth logger.info(f"Z轴下降到枪头拾取位置: {pickup_z:.2f}mm") z_down_success = await asyncio.to_thread( - self.xyz_controller.move_to_work_coord, - tip_x, tip_y, pickup_z + self.xyz_controller.move_to_work_coord, tip_x, tip_y, pickup_z ) if not z_down_success: logger.error("Z轴下降到枪头位置失败") return False - + # 3. 等待一小段时间确保枪头牢固附着 - await asyncio.sleep(0.2) - + await self._ros_node.sleep(0.2) + # 4. Z轴上升到回退高度 retract_z = tip_z + self.config.tip_pickup_retract_height logger.info(f"Z轴上升到回退高度: {retract_z:.2f}mm") z_up_success = await asyncio.to_thread( - self.xyz_controller.move_to_work_coord, - tip_x, tip_y, retract_z + self.xyz_controller.move_to_work_coord, tip_x, tip_y, retract_z ) if not z_up_success: logger.error("Z轴上升失败") return False - + # 5. 更新当前位置 self.current_position = (tip_x, tip_y, retract_z) - + except Exception as move_error: logger.error(f"枪头拾取过程中发生错误: {move_error}") # 尝试移动到安全位置 @@ -529,35 +536,35 @@ class LaiYuLiquidBackend: await self.emergency_stop() await self.move_to_safe_position() return False - + else: # 模拟模式 logger.info("模拟模式:执行枪头拾取动作") - await asyncio.sleep(1.0) # 模拟整个拾取过程的时间 + await self._ros_node.sleep(1.0) # 模拟整个拾取过程的时间 self.current_position = (tip_x, tip_y, tip_z + self.config.tip_pickup_retract_height) - + # 6. 标记枪头已附着 self.tip_attached = True logger.info("吸头拾取成功") return True - + except Exception as e: logger.error(f"拾取吸头失败: {e}") return False - + async def drop_tip(self, location: str = "trash") -> bool: """丢弃吸头 - 包含真正的Z轴控制""" try: # 硬件准备检查 if not self._check_hardware_ready(): return False - + if not self.tip_attached: logger.warning("没有吸头附着,无需丢弃") return True - + logger.info(f"开始丢弃吸头到 {location}") - + # 确定丢弃位置 if location == "trash": # 使用配置中的垃圾桶位置 @@ -567,48 +574,48 @@ class LaiYuLiquidBackend: if self.deck is None: logger.error("工作台未初始化") return False - + drop_position = self.deck.get_position(location) if drop_position is None: logger.error(f"未找到丢弃位置 {location} 的信息") return False drop_x, drop_y, drop_z = drop_position - + # 验证丢弃位置的安全性 safe_z = drop_z + self.config.safe_height drop_height_z = drop_z + self.config.tip_drop_height - - if not (self._validate_position(drop_x, drop_y, safe_z) and - self._validate_position(drop_x, drop_y, drop_height_z)): + + if not ( + self._validate_position(drop_x, drop_y, safe_z) + and self._validate_position(drop_x, drop_y, drop_height_z) + ): logger.error("枪头丢弃位置超出安全范围") return False - + if CONTROLLERS_AVAILABLE and self.xyz_controller: # 真实硬件控制流程 logger.info("使用真实XYZ控制器进行枪头丢弃") - + try: # 1. 移动到丢弃位置上方的安全高度 safe_z = drop_z + self.config.tip_drop_height logger.info(f"移动到丢弃位置上方: ({drop_x:.2f}, {drop_y:.2f}, {safe_z:.2f})") move_success = await asyncio.to_thread( - self.xyz_controller.move_to_work_coord, - drop_x, drop_y, safe_z + self.xyz_controller.move_to_work_coord, drop_x, drop_y, safe_z ) if not move_success: logger.error("移动到丢弃位置上方失败") return False - + # 2. Z轴下降到丢弃高度 logger.info(f"Z轴下降到丢弃高度: {drop_z:.2f}mm") z_down_success = await asyncio.to_thread( - self.xyz_controller.move_to_work_coord, - drop_x, drop_y, drop_z + self.xyz_controller.move_to_work_coord, drop_x, drop_y, drop_z ) if not z_down_success: logger.error("Z轴下降到丢弃位置失败") return False - + # 3. 执行枪头弹出动作(如果有移液器控制器) if self.pipette_controller: try: @@ -617,23 +624,22 @@ class LaiYuLiquidBackend: logger.info("执行枪头弹出命令") except Exception as e: logger.warning(f"枪头弹出命令失败: {e}") - + # 4. 等待一小段时间确保枪头完全脱离 - await asyncio.sleep(0.3) - + await self._ros_node.sleep(0.3) + # 5. Z轴上升到安全高度 logger.info(f"Z轴上升到安全高度: {safe_z:.2f}mm") z_up_success = await asyncio.to_thread( - self.xyz_controller.move_to_work_coord, - drop_x, drop_y, safe_z + self.xyz_controller.move_to_work_coord, drop_x, drop_y, safe_z ) if not z_up_success: logger.error("Z轴上升失败") return False - + # 6. 更新当前位置 self.current_position = (drop_x, drop_y, safe_z) - + except Exception as drop_error: logger.error(f"枪头丢弃过程中发生错误: {drop_error}") # 尝试移动到安全位置 @@ -641,63 +647,63 @@ class LaiYuLiquidBackend: await self.emergency_stop() await self.move_to_safe_position() return False - + else: # 模拟模式 logger.info("模拟模式:执行枪头丢弃动作") - await asyncio.sleep(0.8) # 模拟整个丢弃过程的时间 + await self._ros_node.sleep(0.8) # 模拟整个丢弃过程的时间 self.current_position = (drop_x, drop_y, drop_z + self.config.tip_drop_height) - + # 7. 标记枪头已脱离,清空体积 self.tip_attached = False self.current_volume = 0.0 logger.info("吸头丢弃成功") return True - + except Exception as e: logger.error(f"丢弃吸头失败: {e}") return False - + async def aspirate(self, volume: float, location: str) -> bool: """吸取液体""" try: if not self.is_connected: raise LaiYuLiquidError("设备未连接") - + if not self.tip_attached: raise LaiYuLiquidError("没有吸头附着") - + if volume <= 0 or volume > self.config.max_volume: raise LaiYuLiquidError(f"体积超出范围: {volume}") - + # 模拟吸取 - await asyncio.sleep(0.3) + await self._ros_node.sleep(0.3) self.current_volume += volume logger.debug(f"从 {location} 吸取 {volume} μL") return True - + except Exception as e: logger.error(f"吸取失败: {e}") return False - + async def dispense(self, volume: float, location: str) -> bool: """分配液体""" try: if not self.is_connected: raise LaiYuLiquidError("设备未连接") - + if not self.tip_attached: raise LaiYuLiquidError("没有吸头附着") - + if volume <= 0 or volume > self.current_volume: raise LaiYuLiquidError(f"分配体积无效: {volume}") - + # 模拟分配 - await asyncio.sleep(0.3) + await self._ros_node.sleep(0.3) self.current_volume -= volume logger.debug(f"向 {location} 分配 {volume} μL") return True - + except Exception as e: logger.error(f"分配失败: {e}") return False @@ -705,7 +711,7 @@ class LaiYuLiquidBackend: class LaiYuLiquid: """LaiYu_Liquid 主要接口类""" - + def __init__(self, config: Optional[LaiYuLiquidConfig] = None, **kwargs): # 如果传入了关键字参数,创建配置对象 if kwargs and config is None: @@ -717,37 +723,37 @@ class LaiYuLiquid: self.config = LaiYuLiquidConfig(**config_params) else: self.config = config or LaiYuLiquidConfig() - + # 先创建deck,然后传递给backend self.deck = LaiYuLiquidDeck(self.config) self.backend = LaiYuLiquidBackend(self.config, self.deck) self.is_setup = False - + @property def current_position(self) -> Tuple[float, float, float]: """获取当前位置""" return self.backend.current_position - + @property def current_volume(self) -> float: """获取当前体积""" return self.backend.current_volume - + @property def is_connected(self) -> bool: """获取连接状态""" return self.backend.is_connected - + @property def is_initialized(self) -> bool: """获取初始化状态""" return self.backend.is_initialized - + @property def tip_attached(self) -> bool: """获取吸头附着状态""" return self.backend.tip_attached - + async def setup(self) -> bool: """设置液体处理器""" try: @@ -759,27 +765,28 @@ class LaiYuLiquid: except Exception as e: logger.error(f"LaiYu_Liquid 设置失败: {e}") return False - + async def stop(self): """停止液体处理器""" await self.backend.stop() self.is_setup = False - - async def transfer(self, source: str, target: str, volume: float, - tip_rack: str = "tip_rack_1", tip_position: int = 0) -> bool: + + async def transfer( + self, source: str, target: str, volume: float, tip_rack: str = "tip_rack_1", tip_position: int = 0 + ) -> bool: """液体转移""" try: if not self.is_setup: raise LaiYuLiquidError("设备未设置") - + # 获取源和目标位置 source_pos = self.deck.get_position(source) target_pos = self.deck.get_position(target) tip_pos = self.deck.get_position(tip_rack) - + if not all([source_pos, target_pos, tip_pos]): raise LaiYuLiquidError("位置信息不完整") - + # 执行转移步骤 steps = [ ("移动到吸头架", self.backend.move_to(*tip_pos)), @@ -788,22 +795,22 @@ class LaiYuLiquid: ("吸取液体", self.backend.aspirate(volume, source)), ("移动到目标位置", self.backend.move_to(*target_pos)), ("分配液体", self.backend.dispense(volume, target)), - ("丢弃吸头", self.backend.drop_tip()) + ("丢弃吸头", self.backend.drop_tip()), ] - + for step_name, step_coro in steps: logger.debug(f"执行步骤: {step_name}") success = await step_coro if not success: raise LaiYuLiquidError(f"步骤失败: {step_name}") - + logger.info(f"液体转移完成: {source} -> {target}, {volume} μL") return True - + except Exception as e: logger.error(f"液体转移失败: {e}") return False - + def add_resource(self, name: str, resource_type: str, position: Tuple[float, float, float]): """添加资源到工作台""" if resource_type == "plate": @@ -812,9 +819,9 @@ class LaiYuLiquid: resource = TipRack(name) else: resource = Resource(name) - + self.deck.add_resource(name, resource, position) - + def get_status(self) -> Dict[str, Any]: """获取设备状态""" return { @@ -823,59 +830,59 @@ class LaiYuLiquid: "current_position": self.backend.current_position, "tip_attached": self.backend.tip_attached, "current_volume": self.backend.current_volume, - "resources": self.deck.list_resources() + "resources": self.deck.list_resources(), } def create_quick_setup() -> LaiYuLiquidDeck: """ 创建快速设置的LaiYu液体处理工作站 - + Returns: LaiYuLiquidDeck: 配置好的工作台实例 """ # 创建默认配置 config = LaiYuLiquidConfig() - + # 创建工作台 deck = LaiYuLiquidDeck(config) - + # 导入资源创建函数 try: from .laiyu_liquid_res import ( create_tip_rack_1000ul, create_tip_rack_200ul, create_96_well_plate, - create_waste_container + create_waste_container, ) - + # 添加基本资源 tip_rack_1000 = create_tip_rack_1000ul("tip_rack_1000") tip_rack_200 = create_tip_rack_200ul("tip_rack_200") plate_96 = create_96_well_plate("plate_96") waste = create_waste_container("waste") - + # 添加到工作台 deck.add_resource("tip_rack_1000", tip_rack_1000, (50, 50, 0)) deck.add_resource("tip_rack_200", tip_rack_200, (150, 50, 0)) deck.add_resource("plate_96", plate_96, (250, 50, 0)) deck.add_resource("waste", waste, (50, 150, 0)) - + except ImportError: # 如果资源模块不可用,创建空的工作台 logger.warning("资源模块不可用,创建空的工作台") - + return deck __all__ = [ "LaiYuLiquid", - "LaiYuLiquidBackend", + "LaiYuLiquidBackend", "LaiYuLiquidConfig", "LaiYuLiquidDeck", "LaiYuLiquidContainer", "LaiYuLiquidTipRack", "LaiYuLiquidError", "create_quick_setup", - "get_module_info" -] \ No newline at end of file + "get_module_info", +] diff --git a/unilabos/devices/liquid_handling/liquid_handler_abstract.py b/unilabos/devices/liquid_handling/liquid_handler_abstract.py index 69b757b..32e370f 100644 --- a/unilabos/devices/liquid_handling/liquid_handler_abstract.py +++ b/unilabos/devices/liquid_handling/liquid_handler_abstract.py @@ -25,6 +25,8 @@ from pylabrobot.resources import ( Tip, ) +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class LiquidHandlerMiddleware(LiquidHandler): def __init__(self, backend: LiquidHandlerBackend, deck: Deck, simulator: bool = False, channel_num: int = 8): @@ -536,6 +538,7 @@ class LiquidHandlerMiddleware(LiquidHandler): class LiquidHandlerAbstract(LiquidHandlerMiddleware): """Extended LiquidHandler with additional operations.""" support_touch_tip = True + _ros_node: BaseROS2DeviceNode def __init__(self, backend: LiquidHandlerBackend, deck: Deck, simulator: bool=False, channel_num:int = 8): """Initialize a LiquidHandler. @@ -548,8 +551,11 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware): self.group_info = dict() super().__init__(backend, deck, simulator, channel_num) + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + @classmethod - def set_liquid(self, wells: list[Well], liquid_names: list[str], volumes: list[float]): + def set_liquid(cls, wells: list[Well], liquid_names: list[str], volumes: list[float]): """Set the liquid in a well.""" for well, liquid_name, volume in zip(wells, liquid_names, volumes): well.set_liquids([(liquid_name, volume)]) # type: ignore @@ -1081,7 +1087,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware): print(f"Waiting time: {msg}") print(f"Current time: {time.strftime('%H:%M:%S')}") print(f"Time to finish: {time.strftime('%H:%M:%S', time.localtime(time.time() + seconds))}") - await asyncio.sleep(seconds) + await self._ros_node.sleep(seconds) if msg: print(f"Done: {msg}") print(f"Current time: {time.strftime('%H:%M:%S')}") diff --git a/unilabos/devices/pump_and_valve/runze_async.py b/unilabos/devices/pump_and_valve/runze_async.py index 9b8d649..7bc1115 100644 --- a/unilabos/devices/pump_and_valve/runze_async.py +++ b/unilabos/devices/pump_and_valve/runze_async.py @@ -8,6 +8,8 @@ import serial.tools.list_ports from serial import Serial from serial.serialutil import SerialException +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class RunzeSyringePumpMode(Enum): Normal = 0 @@ -77,6 +79,8 @@ class RunzeSyringePumpInfo: class RunzeSyringePumpAsync: + _ros_node: BaseROS2DeviceNode + def __init__(self, port: str, address: str = "1", volume: float = 25000, mode: RunzeSyringePumpMode = None): self.port = port self.address = address @@ -102,6 +106,9 @@ class RunzeSyringePumpAsync: self._run_future: Optional[Future[Any]] = None self._run_lock = Lock() + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + def _adjust_total_steps(self): self.total_steps = 6000 if self.mode == RunzeSyringePumpMode.Normal else 48000 self.total_steps_vel = 48000 if self.mode == RunzeSyringePumpMode.AccuratePosVel else 6000 @@ -182,7 +189,7 @@ class RunzeSyringePumpAsync: try: await self._query(command) while True: - await asyncio.sleep(0.5) # Wait for 0.5 seconds before polling again + await self._ros_node.sleep(0.5) # Wait for 0.5 seconds before polling again status = await self.query_device_status() if status == '`': @@ -364,7 +371,7 @@ class RunzeSyringePumpAsync: if self._read_task: raise RunzeSyringePumpConnectionError - self._read_task = asyncio.create_task(self._read_loop()) + self._read_task = self._ros_node.create_task(self._read_loop()) try: await self.query_device_status() diff --git a/unilabos/devices/virtual/virtual_centrifuge.py b/unilabos/devices/virtual/virtual_centrifuge.py index 79f9dce..afce45a 100644 --- a/unilabos/devices/virtual/virtual_centrifuge.py +++ b/unilabos/devices/virtual/virtual_centrifuge.py @@ -3,9 +3,13 @@ import logging import time as time_module from typing import Dict, Any, Optional +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class VirtualCentrifuge: """Virtual centrifuge device - 简化版,只保留核心功能""" + + _ros_node: BaseROS2DeviceNode def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs): # 处理可能的不同调用方式 @@ -32,6 +36,9 @@ class VirtualCentrifuge: for key, value in kwargs.items(): if key not in skip_keys and not hasattr(self, key): setattr(self, key, value) + + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node async def initialize(self) -> bool: """Initialize virtual centrifuge""" @@ -132,7 +139,7 @@ class VirtualCentrifuge: break # 每秒更新一次 - await asyncio.sleep(1.0) + await self._ros_node.sleep(1.0) # 离心完成 self.data.update({ diff --git a/unilabos/devices/virtual/virtual_column.py b/unilabos/devices/virtual/virtual_column.py index 892a320..539f302 100644 --- a/unilabos/devices/virtual/virtual_column.py +++ b/unilabos/devices/virtual/virtual_column.py @@ -2,9 +2,13 @@ import asyncio import logging from typing import Dict, Any, Optional +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class VirtualColumn: """Virtual column device for RunColumn protocol 🏛️""" + _ros_node: BaseROS2DeviceNode + def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs): # 处理可能的不同调用方式 if device_id is None and 'id' in kwargs: @@ -28,6 +32,9 @@ class VirtualColumn: print(f"🏛️ === 虚拟色谱柱 {self.device_id} 已创建 === ✨") print(f"📏 柱参数: 流速={self._max_flow_rate}mL/min | 长度={self._column_length}cm | 直径={self._column_diameter}cm 🔬") + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + async def initialize(self) -> bool: """Initialize virtual column 🚀""" self.logger.info(f"🔧 初始化虚拟色谱柱 {self.device_id} ✨") @@ -101,7 +108,7 @@ class VirtualColumn: step_time = separation_time / steps for i in range(steps): - await asyncio.sleep(step_time) + await self._ros_node.sleep(step_time) progress = (i + 1) / steps * 100 volume_processed = (i + 1) * 5.0 # 假设每步处理5mL diff --git a/unilabos/devices/virtual/virtual_filter.py b/unilabos/devices/virtual/virtual_filter.py index ffd8f54..98effc9 100644 --- a/unilabos/devices/virtual/virtual_filter.py +++ b/unilabos/devices/virtual/virtual_filter.py @@ -4,70 +4,76 @@ import time as time_module from typing import Dict, Any, Optional from unilabos.compile.utils.vessel_parser import get_vessel +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode class VirtualFilter: """Virtual filter device - 完全按照 Filter.action 规范 🌊""" - + + _ros_node: BaseROS2DeviceNode + def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs): - if device_id is None and 'id' in kwargs: - device_id = kwargs.pop('id') - if config is None and 'config' in kwargs: - config = kwargs.pop('config') - + if device_id is None and "id" in kwargs: + device_id = kwargs.pop("id") + if config is None and "config" in kwargs: + config = kwargs.pop("config") + self.device_id = device_id or "unknown_filter" self.config = config or {} self.logger = logging.getLogger(f"VirtualFilter.{self.device_id}") self.data = {} - + # 从config或kwargs中获取配置参数 - self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL') - self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 100.0) - self._max_stir_speed = self.config.get('max_stir_speed') or kwargs.get('max_stir_speed', 1000.0) - self._max_volume = self.config.get('max_volume') or kwargs.get('max_volume', 500.0) - + self.port = self.config.get("port") or kwargs.get("port", "VIRTUAL") + self._max_temp = self.config.get("max_temp") or kwargs.get("max_temp", 100.0) + self._max_stir_speed = self.config.get("max_stir_speed") or kwargs.get("max_stir_speed", 1000.0) + self._max_volume = self.config.get("max_volume") or kwargs.get("max_volume", 500.0) + # 处理其他kwargs参数 - skip_keys = {'port', 'max_temp', 'max_stir_speed', 'max_volume'} + skip_keys = {"port", "max_temp", "max_stir_speed", "max_volume"} for key, value in kwargs.items(): if key not in skip_keys and not hasattr(self, key): setattr(self, key, value) - + + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + async def initialize(self) -> bool: """Initialize virtual filter 🚀""" self.logger.info(f"🔧 初始化虚拟过滤器 {self.device_id} ✨") - + # 按照 Filter.action 的 feedback 字段初始化 - self.data.update({ - "status": "Idle", - "progress": 0.0, # Filter.action feedback - "current_temp": 25.0, # Filter.action feedback - "filtered_volume": 0.0, # Filter.action feedback - "message": "Ready for filtration" - }) - + self.data.update( + { + "status": "Idle", + "progress": 0.0, # Filter.action feedback + "current_temp": 25.0, # Filter.action feedback + "filtered_volume": 0.0, # Filter.action feedback + "message": "Ready for filtration", + } + ) + self.logger.info(f"✅ 过滤器 {self.device_id} 初始化完成 🌊") return True - + async def cleanup(self) -> bool: """Cleanup virtual filter 🧹""" self.logger.info(f"🧹 清理虚拟过滤器 {self.device_id} 🔚") - - self.data.update({ - "status": "Offline" - }) - + + self.data.update({"status": "Offline"}) + self.logger.info(f"✅ 过滤器 {self.device_id} 清理完成 💤") return True - + async def filter( - self, + self, vessel: dict, filtrate_vessel: dict = {}, - stir: bool = False, - stir_speed: float = 300.0, - temp: float = 25.0, - continue_heatchill: bool = False, - volume: float = 0.0 + stir: bool = False, + stir_speed: float = 300.0, + temp: float = 25.0, + continue_heatchill: bool = False, + volume: float = 0.0, ) -> bool: """Execute filter action - 完全按照 Filter.action 参数 🌊""" vessel_id, _ = get_vessel(vessel) @@ -79,59 +85,52 @@ class VirtualFilter: temp = 25.0 # 0度自动设置为室温 self.logger.info(f"🌡️ 温度自动调整: {original_temp}°C → {temp}°C (室温) 🏠") elif temp < 4.0: - temp = 4.0 # 小于4度自动设置为4度 + temp = 4.0 # 小于4度自动设置为4度 self.logger.info(f"🌡️ 温度自动调整: {original_temp}°C → {temp}°C (最低温度) ❄️") - + self.logger.info(f"🌊 开始过滤操作: {vessel_id} → {filtrate_vessel_id} 🚰") self.logger.info(f" 🌪️ 搅拌: {stir} ({stir_speed} RPM)") self.logger.info(f" 🌡️ 温度: {temp}°C") self.logger.info(f" 💧 体积: {volume}mL") self.logger.info(f" 🔥 保持加热: {continue_heatchill}") - + # 验证参数 if temp > self._max_temp or temp < 4.0: error_msg = f"🌡️ 温度 {temp}°C 超出范围 (4-{self._max_temp}°C) ⚠️" self.logger.error(f"❌ {error_msg}") - self.data.update({ - "status": f"Error: 温度超出范围 ⚠️", - "message": error_msg - }) + self.data.update({"status": f"Error: 温度超出范围 ⚠️", "message": error_msg}) return False - + if stir and stir_speed > self._max_stir_speed: error_msg = f"🌪️ 搅拌速度 {stir_speed} RPM 超出范围 (0-{self._max_stir_speed} RPM) ⚠️" self.logger.error(f"❌ {error_msg}") - self.data.update({ - "status": f"Error: 搅拌速度超出范围 ⚠️", - "message": error_msg - }) + self.data.update({"status": f"Error: 搅拌速度超出范围 ⚠️", "message": error_msg}) return False - + if volume > self._max_volume: error_msg = f"💧 过滤体积 {volume} mL 超出范围 (0-{self._max_volume} mL) ⚠️" self.logger.error(f"❌ {error_msg}") - self.data.update({ - "status": f"Error", - "message": error_msg - }) + self.data.update({"status": f"Error", "message": error_msg}) return False - + # 开始过滤 filter_volume = volume if volume > 0 else 50.0 self.logger.info(f"🚀 开始过滤 {filter_volume}mL 液体 💧") - - self.data.update({ - "status": f"Running", - "current_temp": temp, - "filtered_volume": 0.0, - "progress": 0.0, - "message": f"🚀 Starting filtration: {vessel_id} → {filtrate_vessel_id}" - }) - + + self.data.update( + { + "status": f"Running", + "current_temp": temp, + "filtered_volume": 0.0, + "progress": 0.0, + "message": f"🚀 Starting filtration: {vessel_id} → {filtrate_vessel_id}", + } + ) + try: # 过滤过程 - 实时更新进度 start_time = time_module.time() - + # 根据体积和搅拌估算过滤时间 base_time = filter_volume / 5.0 # 5mL/s 基础速度 if stir: @@ -140,78 +139,79 @@ class VirtualFilter: if temp > 50.0: base_time *= 0.7 # 高温加速过滤 self.logger.info(f"🔥 高温加速过滤,预计时间减少30% ⚡") - + filter_time = max(base_time, 10.0) # 最少10秒 self.logger.info(f"⏱️ 预计过滤时间: {filter_time:.1f}秒 ⌛") - + while True: current_time = time_module.time() elapsed = current_time - start_time remaining = max(0, filter_time - elapsed) progress = min(100.0, (elapsed / filter_time) * 100) current_filtered = (progress / 100.0) * filter_volume - + # 更新状态 - 按照 Filter.action feedback 字段 status_msg = f"🌊 过滤中: {vessel}" if stir: status_msg += f" | 🌪️ 搅拌: {stir_speed} RPM" status_msg += f" | 🌡️ {temp}°C | 📊 {progress:.1f}% | 💧 已过滤: {current_filtered:.1f}mL" - - self.data.update({ - "progress": progress, # Filter.action feedback - "current_temp": temp, # Filter.action feedback - "filtered_volume": current_filtered, # Filter.action feedback - "status": "Running", - "message": f"🌊 Filtering: {progress:.1f}% complete, {current_filtered:.1f}mL filtered" - }) - + + self.data.update( + { + "progress": progress, # Filter.action feedback + "current_temp": temp, # Filter.action feedback + "filtered_volume": current_filtered, # Filter.action feedback + "status": "Running", + "message": f"🌊 Filtering: {progress:.1f}% complete, {current_filtered:.1f}mL filtered", + } + ) + # 进度日志(每25%打印一次) if progress >= 25 and progress % 25 < 1: self.logger.info(f"📊 过滤进度: {progress:.0f}% | 💧 {current_filtered:.1f}mL 完成 ✨") - + if remaining <= 0: break - - await asyncio.sleep(1.0) - + + await self._ros_node.sleep(1.0) + # 过滤完成 final_temp = temp if continue_heatchill else 25.0 final_status = f"✅ 过滤完成: {vessel} | 💧 {filter_volume}mL → {filtrate_vessel}" if continue_heatchill: final_status += " | 🔥 继续加热搅拌" self.logger.info(f"🔥 继续保持加热搅拌状态 🌪️") - - self.data.update({ - "status": final_status, - "progress": 100.0, # Filter.action feedback - "current_temp": final_temp, # Filter.action feedback - "filtered_volume": filter_volume, # Filter.action feedback - "message": f"✅ Filtration completed: {filter_volume}mL filtered from {vessel_id}" - }) - + + self.data.update( + { + "status": final_status, + "progress": 100.0, # Filter.action feedback + "current_temp": final_temp, # Filter.action feedback + "filtered_volume": filter_volume, # Filter.action feedback + "message": f"✅ Filtration completed: {filter_volume}mL filtered from {vessel_id}", + } + ) + self.logger.info(f"🎉 过滤完成! 💧 {filter_volume}mL 从 {vessel_id} 过滤到 {filtrate_vessel_id} ✨") self.logger.info(f"📊 最终状态: 温度 {final_temp}°C | 进度 100% | 体积 {filter_volume}mL 🏁") return True - + except Exception as e: error_msg = f"过滤过程中发生错误: {str(e)} 💥" self.logger.error(f"❌ {error_msg}") - self.data.update({ - "status": f"Error", - "message": f"❌ Filtration failed: {str(e)}" - }) + self.data.update({"status": f"Error", "message": f"❌ Filtration failed: {str(e)}"}) return False - + # === 核心状态属性 - 按照 Filter.action feedback 字段 === @property def status(self) -> str: return self.data.get("status", "❓ Unknown") - + @property def progress(self) -> float: """Filter.action feedback 字段 📊""" return self.data.get("progress", 0.0) - + @property def current_temp(self) -> float: """Filter.action feedback 字段 🌡️""" @@ -230,15 +230,15 @@ class VirtualFilter: @property def message(self) -> str: return self.data.get("message", "") - + @property def max_temp(self) -> float: return self._max_temp - + @property def max_stir_speed(self) -> float: return self._max_stir_speed - + @property def max_volume(self) -> float: - return self._max_volume \ No newline at end of file + return self._max_volume diff --git a/unilabos/devices/virtual/virtual_heatchill.py b/unilabos/devices/virtual/virtual_heatchill.py index 2f7e555..29a9fd2 100644 --- a/unilabos/devices/virtual/virtual_heatchill.py +++ b/unilabos/devices/virtual/virtual_heatchill.py @@ -3,9 +3,13 @@ import logging import time as time_module # 重命名time模块,避免与参数冲突 from typing import Dict, Any +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class VirtualHeatChill: """Virtual heat chill device for HeatChillProtocol testing 🌡️""" + _ros_node: BaseROS2DeviceNode + def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs): # 处理可能的不同调用方式 if device_id is None and 'id' in kwargs: @@ -35,6 +39,9 @@ class VirtualHeatChill: print(f"🌡️ === 虚拟温控设备 {self.device_id} 已创建 === ✨") print(f"🔥 温度范围: {self._min_temp}°C ~ {self._max_temp}°C | 🌪️ 最大搅拌: {self._max_stir_speed} RPM") + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + async def initialize(self) -> bool: """Initialize virtual heat chill 🚀""" self.logger.info(f"🔧 初始化虚拟温控设备 {self.device_id} ✨") @@ -177,7 +184,7 @@ class VirtualHeatChill: break # 等待1秒后再次检查 - await asyncio.sleep(1.0) + await self._ros_node.sleep(1.0) # 操作完成 final_stir_info = f" | 🌪️ 搅拌: {stir_speed} RPM" if stir else "" diff --git a/unilabos/devices/virtual/virtual_rotavap.py b/unilabos/devices/virtual/virtual_rotavap.py index 23e24b7..5e85d35 100644 --- a/unilabos/devices/virtual/virtual_rotavap.py +++ b/unilabos/devices/virtual/virtual_rotavap.py @@ -3,13 +3,19 @@ import logging import time as time_module from typing import Dict, Any, Optional +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + + def debug_print(message): """调试输出 🔍""" print(f"🌪️ [ROTAVAP] {message}", flush=True) + class VirtualRotavap: """Virtual rotary evaporator device - 简化版,只保留核心功能 🌪️""" + _ros_node: BaseROS2DeviceNode + def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs): # 处理可能的不同调用方式 if device_id is None and "id" in kwargs: @@ -38,56 +44,65 @@ class VirtualRotavap: print(f"🌪️ === 虚拟旋转蒸发仪 {self.device_id} 已创建 === ✨") print(f"🔥 温度范围: 10°C ~ {self._max_temp}°C | 🌀 转速范围: 10 ~ {self._max_rotation_speed} RPM") + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + async def initialize(self) -> bool: """Initialize virtual rotary evaporator 🚀""" self.logger.info(f"🔧 初始化虚拟旋转蒸发仪 {self.device_id} ✨") - + # 只保留核心状态 - self.data.update({ - "status": "🏠 待机中", - "rotavap_state": "Ready", # Ready, Evaporating, Completed, Error - "current_temp": 25.0, - "target_temp": 25.0, - "rotation_speed": 0.0, - "vacuum_pressure": 1.0, # 大气压 - "evaporated_volume": 0.0, - "progress": 0.0, - "remaining_time": 0.0, - "message": "🌪️ Ready for evaporation" - }) - + self.data.update( + { + "status": "🏠 待机中", + "rotavap_state": "Ready", # Ready, Evaporating, Completed, Error + "current_temp": 25.0, + "target_temp": 25.0, + "rotation_speed": 0.0, + "vacuum_pressure": 1.0, # 大气压 + "evaporated_volume": 0.0, + "progress": 0.0, + "remaining_time": 0.0, + "message": "🌪️ Ready for evaporation", + } + ) + self.logger.info(f"✅ 旋转蒸发仪 {self.device_id} 初始化完成 🌪️") - self.logger.info(f"📊 设备规格: 温度范围 10°C ~ {self._max_temp}°C | 转速范围 10 ~ {self._max_rotation_speed} RPM") + self.logger.info( + f"📊 设备规格: 温度范围 10°C ~ {self._max_temp}°C | 转速范围 10 ~ {self._max_rotation_speed} RPM" + ) return True async def cleanup(self) -> bool: """Cleanup virtual rotary evaporator 🧹""" self.logger.info(f"🧹 清理虚拟旋转蒸发仪 {self.device_id} 🔚") - - self.data.update({ - "status": "💤 离线", - "rotavap_state": "Offline", - "current_temp": 25.0, - "rotation_speed": 0.0, - "vacuum_pressure": 1.0, - "message": "💤 System offline" - }) - + + self.data.update( + { + "status": "💤 离线", + "rotavap_state": "Offline", + "current_temp": 25.0, + "rotation_speed": 0.0, + "vacuum_pressure": 1.0, + "message": "💤 System offline", + } + ) + self.logger.info(f"✅ 旋转蒸发仪 {self.device_id} 清理完成 💤") return True async def evaporate( - self, - vessel: str, - pressure: float = 0.1, - temp: float = 60.0, + self, + vessel: str, + pressure: float = 0.1, + temp: float = 60.0, time: float = 180.0, stir_speed: float = 100.0, solvent: str = "", - **kwargs + **kwargs, ) -> bool: """Execute evaporate action - 简化版 🌪️""" - + # 🔧 新增:确保time参数是数值类型 if isinstance(time, str): try: @@ -98,31 +113,31 @@ class VirtualRotavap: elif not isinstance(time, (int, float)): self.logger.error(f"❌ 时间参数类型无效: {type(time)},使用默认值180.0秒") time = 180.0 - + # 确保time是float类型; 并加速 time = float(time) / 10.0 - + # 🔧 简化处理:如果vessel就是设备自己,直接操作 if vessel == self.device_id: debug_print(f"🎯 在设备 {self.device_id} 上直接执行蒸发操作") actual_vessel = self.device_id else: actual_vessel = vessel - + # 参数预处理 if solvent: self.logger.info(f"🧪 识别到溶剂: {solvent}") # 根据溶剂调整参数 solvent_lower = solvent.lower() - if any(s in solvent_lower for s in ['water', 'aqueous']): + if any(s in solvent_lower for s in ["water", "aqueous"]): temp = max(temp, 80.0) pressure = max(pressure, 0.2) self.logger.info(f"💧 水系溶剂:调整参数 → 温度 {temp}°C, 压力 {pressure} bar") - elif any(s in solvent_lower for s in ['ethanol', 'methanol', 'acetone']): + elif any(s in solvent_lower for s in ["ethanol", "methanol", "acetone"]): temp = min(temp, 50.0) pressure = min(pressure, 0.05) self.logger.info(f"⚡ 易挥发溶剂:调整参数 → 温度 {temp}°C, 压力 {pressure} bar") - + self.logger.info(f"🌪️ 开始蒸发操作: {actual_vessel}") self.logger.info(f" 🥽 容器: {actual_vessel}") self.logger.info(f" 🌡️ 温度: {temp}°C") @@ -131,126 +146,140 @@ class VirtualRotavap: self.logger.info(f" 🌀 转速: {stir_speed} RPM") if solvent: self.logger.info(f" 🧪 溶剂: {solvent}") - + # 验证参数 if temp > self._max_temp or temp < 10.0: error_msg = f"🌡️ 温度 {temp}°C 超出范围 (10-{self._max_temp}°C) ⚠️" self.logger.error(f"❌ {error_msg}") - self.data.update({ - "status": f"❌ 错误: 温度超出范围", - "rotavap_state": "Error", - "current_temp": 25.0, - "progress": 0.0, - "evaporated_volume": 0.0, - "message": error_msg - }) + self.data.update( + { + "status": f"❌ 错误: 温度超出范围", + "rotavap_state": "Error", + "current_temp": 25.0, + "progress": 0.0, + "evaporated_volume": 0.0, + "message": error_msg, + } + ) return False if stir_speed > self._max_rotation_speed or stir_speed < 10.0: error_msg = f"🌀 旋转速度 {stir_speed} RPM 超出范围 (10-{self._max_rotation_speed} RPM) ⚠️" self.logger.error(f"❌ {error_msg}") - self.data.update({ - "status": f"❌ 错误: 转速超出范围", - "rotavap_state": "Error", - "current_temp": 25.0, - "progress": 0.0, - "evaporated_volume": 0.0, - "message": error_msg - }) + self.data.update( + { + "status": f"❌ 错误: 转速超出范围", + "rotavap_state": "Error", + "current_temp": 25.0, + "progress": 0.0, + "evaporated_volume": 0.0, + "message": error_msg, + } + ) return False if pressure < 0.01 or pressure > 1.0: error_msg = f"💨 真空度 {pressure} bar 超出范围 (0.01-1.0 bar) ⚠️" self.logger.error(f"❌ {error_msg}") - self.data.update({ - "status": f"❌ 错误: 压力超出范围", - "rotavap_state": "Error", - "current_temp": 25.0, - "progress": 0.0, - "evaporated_volume": 0.0, - "message": error_msg - }) + self.data.update( + { + "status": f"❌ 错误: 压力超出范围", + "rotavap_state": "Error", + "current_temp": 25.0, + "progress": 0.0, + "evaporated_volume": 0.0, + "message": error_msg, + } + ) return False # 开始蒸发 - 🔧 现在time已经确保是float类型 self.logger.info(f"🚀 启动蒸发程序! 预计用时 {time/60:.1f}分钟 ⏱️") - - self.data.update({ - "status": f"🌪️ 蒸发中: {actual_vessel}", - "rotavap_state": "Evaporating", - "current_temp": temp, - "target_temp": temp, - "rotation_speed": stir_speed, - "vacuum_pressure": pressure, - "remaining_time": time, - "progress": 0.0, - "evaporated_volume": 0.0, - "message": f"🌪️ Evaporating {actual_vessel} at {temp}°C, {pressure} bar, {stir_speed} RPM" - }) + + self.data.update( + { + "status": f"🌪️ 蒸发中: {actual_vessel}", + "rotavap_state": "Evaporating", + "current_temp": temp, + "target_temp": temp, + "rotation_speed": stir_speed, + "vacuum_pressure": pressure, + "remaining_time": time, + "progress": 0.0, + "evaporated_volume": 0.0, + "message": f"🌪️ Evaporating {actual_vessel} at {temp}°C, {pressure} bar, {stir_speed} RPM", + } + ) try: # 蒸发过程 - 实时更新进度 start_time = time_module.time() total_time = time last_logged_progress = 0 - + while True: current_time = time_module.time() elapsed = current_time - start_time remaining = max(0, total_time - elapsed) progress = min(100.0, (elapsed / total_time) * 100) - + # 模拟蒸发体积 - 根据溶剂类型调整 - if solvent and any(s in solvent.lower() for s in ['water', 'aqueous']): + if solvent and any(s in solvent.lower() for s in ["water", "aqueous"]): evaporated_vol = progress * 0.6 # 水系溶剂蒸发慢 - elif solvent and any(s in solvent.lower() for s in ['ethanol', 'methanol', 'acetone']): + elif solvent and any(s in solvent.lower() for s in ["ethanol", "methanol", "acetone"]): evaporated_vol = progress * 1.0 # 易挥发溶剂蒸发快 else: evaporated_vol = progress * 0.8 # 默认蒸发量 - + # 🔧 更新状态 - 确保包含所有必需字段 status_msg = f"🌪️ 蒸发中: {actual_vessel} | 🌡️ {temp}°C | 💨 {pressure} bar | 🌀 {stir_speed} RPM | 📊 {progress:.1f}% | ⏰ 剩余: {remaining:.0f}s" - - self.data.update({ - "remaining_time": remaining, - "progress": progress, - "evaporated_volume": evaporated_vol, - "current_temp": temp, - "status": status_msg, - "message": f"🌪️ Evaporating: {progress:.1f}% complete, 💧 {evaporated_vol:.1f}mL evaporated, ⏰ {remaining:.0f}s remaining" - }) - + + self.data.update( + { + "remaining_time": remaining, + "progress": progress, + "evaporated_volume": evaporated_vol, + "current_temp": temp, + "status": status_msg, + "message": f"🌪️ Evaporating: {progress:.1f}% complete, 💧 {evaporated_vol:.1f}mL evaporated, ⏰ {remaining:.0f}s remaining", + } + ) + # 进度日志(每25%打印一次) if progress >= 25 and int(progress) % 25 == 0 and int(progress) != last_logged_progress: - self.logger.info(f"📊 蒸发进度: {progress:.0f}% | 💧 已蒸发: {evaporated_vol:.1f}mL | ⏰ 剩余: {remaining:.0f}s ✨") + self.logger.info( + f"📊 蒸发进度: {progress:.0f}% | 💧 已蒸发: {evaporated_vol:.1f}mL | ⏰ 剩余: {remaining:.0f}s ✨" + ) last_logged_progress = int(progress) - + # 时间到了,退出循环 if remaining <= 0: break - + # 每秒更新一次 - await asyncio.sleep(1.0) - + await self._ros_node.sleep(1.0) + # 蒸发完成 - if solvent and any(s in solvent.lower() for s in ['water', 'aqueous']): + if solvent and any(s in solvent.lower() for s in ["water", "aqueous"]): final_evaporated = 60.0 # 水系溶剂 - elif solvent and any(s in solvent.lower() for s in ['ethanol', 'methanol', 'acetone']): + elif solvent and any(s in solvent.lower() for s in ["ethanol", "methanol", "acetone"]): final_evaporated = 100.0 # 易挥发溶剂 else: final_evaporated = 80.0 # 默认 - - self.data.update({ - "status": f"✅ 蒸发完成: {actual_vessel} | 💧 蒸发量: {final_evaporated:.1f}mL", - "rotavap_state": "Completed", - "evaporated_volume": final_evaporated, - "progress": 100.0, - "current_temp": temp, - "remaining_time": 0.0, - "rotation_speed": 0.0, - "vacuum_pressure": 1.0, - "message": f"✅ Evaporation completed: {final_evaporated}mL evaporated from {actual_vessel}" - }) + + self.data.update( + { + "status": f"✅ 蒸发完成: {actual_vessel} | 💧 蒸发量: {final_evaporated:.1f}mL", + "rotavap_state": "Completed", + "evaporated_volume": final_evaporated, + "progress": 100.0, + "current_temp": temp, + "remaining_time": 0.0, + "rotation_speed": 0.0, + "vacuum_pressure": 1.0, + "message": f"✅ Evaporation completed: {final_evaporated}mL evaporated from {actual_vessel}", + } + ) self.logger.info(f"🎉 蒸发操作完成! ✨") self.logger.info(f"📊 蒸发结果:") @@ -262,24 +291,26 @@ class VirtualRotavap: self.logger.info(f" ⏱️ 总用时: {total_time:.0f}s") if solvent: self.logger.info(f" 🧪 处理溶剂: {solvent} 🏁") - + return True except Exception as e: # 出错处理 error_msg = f"蒸发过程中发生错误: {str(e)} 💥" self.logger.error(f"❌ {error_msg}") - - self.data.update({ - "status": f"❌ 蒸发错误: {str(e)}", - "rotavap_state": "Error", - "current_temp": 25.0, - "progress": 0.0, - "evaporated_volume": 0.0, - "rotation_speed": 0.0, - "vacuum_pressure": 1.0, - "message": f"❌ Evaporation failed: {str(e)}" - }) + + self.data.update( + { + "status": f"❌ 蒸发错误: {str(e)}", + "rotavap_state": "Error", + "current_temp": 25.0, + "progress": 0.0, + "evaporated_volume": 0.0, + "rotation_speed": 0.0, + "vacuum_pressure": 1.0, + "message": f"❌ Evaporation failed: {str(e)}", + } + ) return False # === 核心状态属性 === diff --git a/unilabos/devices/virtual/virtual_separator.py b/unilabos/devices/virtual/virtual_separator.py index e1c4612..0f266ce 100644 --- a/unilabos/devices/virtual/virtual_separator.py +++ b/unilabos/devices/virtual/virtual_separator.py @@ -2,9 +2,13 @@ import asyncio import logging from typing import Dict, Any, Optional +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class VirtualSeparator: """Virtual separator device for SeparateProtocol testing""" + + _ros_node: BaseROS2DeviceNode def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs): # 处理可能的不同调用方式 @@ -35,6 +39,9 @@ class VirtualSeparator: for key, value in kwargs.items(): if key not in skip_keys and not hasattr(self, key): setattr(self, key, value) + + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node async def initialize(self) -> bool: """Initialize virtual separator""" @@ -119,14 +126,14 @@ class VirtualSeparator: for repeat in range(repeats): # 搅拌阶段 for progress in range(0, 51, 10): - await asyncio.sleep(simulation_time / (repeats * 10)) + await self._ros_node.sleep(simulation_time / (repeats * 10)) overall_progress = ((repeat * 100) + (progress * 0.5)) / repeats self.data["progress"] = overall_progress self.data["message"] = f"第{repeat+1}次分离 - 搅拌中 ({progress}%)" # 静置分相阶段 for progress in range(50, 101, 10): - await asyncio.sleep(simulation_time / (repeats * 10)) + await self._ros_node.sleep(simulation_time / (repeats * 10)) overall_progress = ((repeat * 100) + (progress * 0.5)) / repeats self.data["progress"] = overall_progress self.data["message"] = f"第{repeat+1}次分离 - 静置分相中 ({progress}%)" diff --git a/unilabos/devices/virtual/virtual_solenoid_valve.py b/unilabos/devices/virtual/virtual_solenoid_valve.py index e019424..26970cb 100644 --- a/unilabos/devices/virtual/virtual_solenoid_valve.py +++ b/unilabos/devices/virtual/virtual_solenoid_valve.py @@ -2,11 +2,16 @@ import time import asyncio from typing import Union +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class VirtualSolenoidValve: """ 虚拟电磁阀门 - 简单的开关型阀门,只有开启和关闭两个状态 """ + + _ros_node: BaseROS2DeviceNode + def __init__(self, device_id: str = None, config: dict = None, **kwargs): # 从配置中获取参数,提供默认值 if config is None: @@ -21,6 +26,9 @@ class VirtualSolenoidValve: self._status = "Idle" self._valve_state = "Closed" # "Open" or "Closed" self._is_open = False + + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node async def initialize(self) -> bool: """初始化设备""" @@ -63,7 +71,7 @@ class VirtualSolenoidValve: self._status = "Busy" # 模拟阀门响应时间 - await asyncio.sleep(self.response_time) + await self._ros_node.sleep(self.response_time) # 处理不同的命令格式 if isinstance(command, str): diff --git a/unilabos/devices/virtual/virtual_solid_dispenser.py b/unilabos/devices/virtual/virtual_solid_dispenser.py index f8c14a7..6318261 100644 --- a/unilabos/devices/virtual/virtual_solid_dispenser.py +++ b/unilabos/devices/virtual/virtual_solid_dispenser.py @@ -3,6 +3,8 @@ import logging import re from typing import Dict, Any, Optional +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class VirtualSolidDispenser: """ 虚拟固体粉末加样器 - 用于处理 Add Protocol 中的固体试剂添加 ⚗️ @@ -13,6 +15,8 @@ class VirtualSolidDispenser: - 简单反馈:成功/失败 + 消息 📊 """ + _ros_node: BaseROS2DeviceNode + def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs): self.device_id = device_id or "virtual_solid_dispenser" self.config = config or {} @@ -32,6 +36,9 @@ class VirtualSolidDispenser: print(f"⚗️ === 虚拟固体分配器 {self.device_id} 创建成功! === ✨") print(f"📊 设备规格: 最大容量 {self.max_capacity}g | 精度 {self.precision}g 🎯") + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + async def initialize(self) -> bool: """初始化固体加样器 🚀""" self.logger.info(f"🔧 初始化固体分配器 {self.device_id} ✨") @@ -263,7 +270,7 @@ class VirtualSolidDispenser: for i in range(steps): progress = (i + 1) / steps * 100 - await asyncio.sleep(step_time) + await self._ros_node.sleep(step_time) if i % 2 == 0: # 每隔一步显示进度 self.logger.debug(f"📊 加样进度: {progress:.0f}% | {amount_emoji} 正在分配 {reagent}...") diff --git a/unilabos/devices/virtual/virtual_stirrer.py b/unilabos/devices/virtual/virtual_stirrer.py index cccf61e..8e95617 100644 --- a/unilabos/devices/virtual/virtual_stirrer.py +++ b/unilabos/devices/virtual/virtual_stirrer.py @@ -3,9 +3,13 @@ import logging import time as time_module from typing import Dict, Any +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class VirtualStirrer: """Virtual stirrer device for StirProtocol testing - 功能完整版 🌪️""" + _ros_node: BaseROS2DeviceNode + def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs): # 处理可能的不同调用方式 if device_id is None and 'id' in kwargs: @@ -34,6 +38,9 @@ class VirtualStirrer: print(f"🌪️ === 虚拟搅拌器 {self.device_id} 已创建 === ✨") print(f"🔧 速度范围: {self._min_speed} ~ {self._max_speed} RPM | 📱 端口: {self.port}") + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + async def initialize(self) -> bool: """Initialize virtual stirrer 🚀""" self.logger.info(f"🔧 初始化虚拟搅拌器 {self.device_id} ✨") @@ -134,7 +141,7 @@ class VirtualStirrer: if remaining <= 0: break - await asyncio.sleep(1.0) + await self._ros_node.sleep(1.0) self.logger.info(f"✅ 搅拌阶段完成! 🌪️ {stir_speed} RPM × {stir_time}s") @@ -176,7 +183,7 @@ class VirtualStirrer: if remaining <= 0: break - await asyncio.sleep(1.0) + await self._ros_node.sleep(1.0) self.logger.info(f"✅ 沉降阶段完成! 🛑 静置 {settling_time}s") diff --git a/unilabos/devices/virtual/virtual_transferpump.py b/unilabos/devices/virtual/virtual_transferpump.py index 1187db5..7b8eea8 100644 --- a/unilabos/devices/virtual/virtual_transferpump.py +++ b/unilabos/devices/virtual/virtual_transferpump.py @@ -4,6 +4,8 @@ from enum import Enum from typing import Union, Optional import logging +from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode + class VirtualPumpMode(Enum): Normal = 0 @@ -14,6 +16,8 @@ class VirtualPumpMode(Enum): class VirtualTransferPump: """虚拟转移泵类 - 模拟泵的基本功能,无需实际硬件 🚰""" + _ros_node: BaseROS2DeviceNode + def __init__(self, device_id: str = None, config: dict = None, **kwargs): """ 初始化虚拟转移泵 @@ -53,6 +57,9 @@ class VirtualTransferPump: print(f"💨 快速模式: {'启用' if self._fast_mode else '禁用'} | 移动时间: {self._fast_move_time}s | 喷射时间: {self._fast_dispense_time}s") print(f"📊 最大容量: {self.max_volume}mL | 端口: {self.port}") + def post_init(self, ros_node: BaseROS2DeviceNode): + self._ros_node = ros_node + async def initialize(self) -> bool: """初始化虚拟泵 🚀""" self.logger.info(f"🔧 初始化虚拟转移泵 {self.device_id} ✨") @@ -104,7 +111,7 @@ class VirtualTransferPump: async def _simulate_operation(self, duration: float): """模拟操作延时 ⏱️""" self._status = "Busy" - await asyncio.sleep(duration) + await self._ros_node.sleep(duration) self._status = "Idle" def _calculate_duration(self, volume: float, velocity: float = None) -> float: @@ -223,7 +230,7 @@ class VirtualTransferPump: # 等待一小步时间 if i < steps and step_duration > 0: - await asyncio.sleep(step_duration) + await self._ros_node.sleep(step_duration) else: # 移动距离很小,直接完成 self._position = target_position @@ -341,7 +348,7 @@ class VirtualTransferPump: # 短暂停顿 self.logger.debug("⏸️ 短暂停顿...") - await asyncio.sleep(0.1) + await self._ros_node.sleep(0.1) # 排液 await self.dispense(volume, dispense_velocity) From 1b2c0dbcd7ef673ab8ae4f06dbaa1668539663b7 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:31:37 +0800 Subject: [PATCH 074/104] adjust with_children param --- unilabos/ros/nodes/base_device_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index 2fc7ea7..19503fd 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -656,7 +656,7 @@ class BaseROS2DeviceNode(Node, Generic[T]): ].call_async( SerialCommand.Request( command=json.dumps( - {"data": {"data": resources_uuid, "with_children": False}, "action": "get"} + {"data": {"data": resources_uuid, "with_children": True if action == "add" else "update"}, "action": "get"} ) ) ) # type: ignore From 30b202bea09821d049e27fc53cd799ab45acc8ff Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Fri, 7 Nov 2025 14:46:27 +0800 Subject: [PATCH 075/104] disable slave connect websocket --- unilabos/app/main.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/unilabos/app/main.py b/unilabos/app/main.py index c646518..db15e2c 100644 --- a/unilabos/app/main.py +++ b/unilabos/app/main.py @@ -375,22 +375,23 @@ def main(): args_dict["bridges"] = [] - # 获取通信客户端(仅支持WebSocket) - comm_client = get_communication_client() - - if "websocket" in args_dict["app_bridges"]: - args_dict["bridges"].append(comm_client) if "fastapi" in args_dict["app_bridges"]: args_dict["bridges"].append(http_client) - if "websocket" in args_dict["app_bridges"]: + # 获取通信客户端(仅支持WebSocket) + if BasicConfig.is_host_mode: + comm_client = get_communication_client() + if "websocket" in args_dict["app_bridges"]: + args_dict["bridges"].append(comm_client) + def _exit(signum, frame): + comm_client.stop() + sys.exit(0) - def _exit(signum, frame): - comm_client.stop() - sys.exit(0) + signal.signal(signal.SIGINT, _exit) + signal.signal(signal.SIGTERM, _exit) + comm_client.start() + else: + print_status("SlaveMode跳过Websocket连接") - signal.signal(signal.SIGINT, _exit) - signal.signal(signal.SIGTERM, _exit) - comm_client.start() args_dict["resources_mesh_config"] = {} args_dict["resources_edge_config"] = resource_edge_info # web visiualize 2D From d9dffc6bf88af790992bb3b68d7b33a385032547 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Fri, 7 Nov 2025 14:52:09 +0800 Subject: [PATCH 076/104] correct remove_resource stats --- unilabos/ros/nodes/resource_tracker.py | 41 ++++++++++++++++++++------ 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/unilabos/ros/nodes/resource_tracker.py b/unilabos/ros/nodes/resource_tracker.py index c958fe7..fc3ca7a 100644 --- a/unilabos/ros/nodes/resource_tracker.py +++ b/unilabos/ros/nodes/resource_tracker.py @@ -2,7 +2,7 @@ import traceback import uuid from pydantic import BaseModel, field_serializer, field_validator from pydantic import Field -from typing import List, Tuple, Any, Dict, Literal, Optional, cast, TYPE_CHECKING +from typing import List, Tuple, Any, Dict, Literal, Optional, cast, TYPE_CHECKING, Union from unilabos.utils.log import logger @@ -927,7 +927,7 @@ class DeviceNodeResourceTracker(object): """ 递归遍历资源树,更新所有节点的uuid - Args:0 + Args: resource: 资源对象(可以是dict或实例) uuid_map: uuid映射字典,{old_uuid: new_uuid} @@ -952,6 +952,27 @@ class DeviceNodeResourceTracker(object): return self._traverse_and_process(resource, process) + def loop_gather_uuid(self, resource) -> List[str]: + """ + 递归遍历资源树,收集所有节点的uuid + + Args: + resource: 资源对象(可以是dict或实例) + + Returns: + 收集到的uuid列表 + """ + uuid_list = [] + + def process(res): + current_uuid = self._get_resource_attr(res, "uuid", "unilabos_uuid") + if current_uuid: + uuid_list.append(current_uuid) + return 0 + + self._traverse_and_process(resource, process) + return uuid_list + def _collect_uuid_mapping(self, resource): """ 递归收集资源的 uuid 映射到 uuid_to_resources @@ -972,7 +993,7 @@ class DeviceNodeResourceTracker(object): self._traverse_and_process(resource, process) - def _remove_uuid_mapping(self, resource): + def _remove_uuid_mapping(self, resource) -> int: """ 递归清除资源的 uuid 映射 @@ -985,9 +1006,10 @@ class DeviceNodeResourceTracker(object): if current_uuid and current_uuid in self.uuid_to_resources: self.uuid_to_resources.pop(current_uuid) logger.debug(f"移除资源UUID映射: {current_uuid} -> {res}") + return 1 return 0 - self._traverse_and_process(resource, process) + return self._traverse_and_process(resource, process) def parent_resource(self, resource): if id(resource) in self.resource2parent_resource: @@ -1042,13 +1064,12 @@ class DeviceNodeResourceTracker(object): removed = True break - if not removed: + # 递归清除uuid映射 + count = self._remove_uuid_mapping(resource) + if not count: logger.warning(f"尝试移除不存在的资源: {resource}") return False - # 递归清除uuid映射 - self._remove_uuid_mapping(resource) - # 清除 resource2parent_resource 中与该资源相关的映射 # 需要清除:1) 该资源作为 key 的映射 2) 该资源作为 value 的映射 keys_to_remove = [] @@ -1071,7 +1092,9 @@ class DeviceNodeResourceTracker(object): self.uuid_to_resources.clear() self.resource2parent_resource.clear() - def figure_resource(self, query_resource, try_mode=False): + def figure_resource( + self, query_resource: Union[List[Union[dict, "PLRResource"]], dict, "PLRResource"], try_mode=False + ) -> Union[List[Union[dict, "PLRResource", List[Union[dict, "PLRResource"]]]], dict, "PLRResource"]: if isinstance(query_resource, list): return [self.figure_resource(r, try_mode) for r in query_resource] elif ( From d897d70c3e7fd172710d7b4d38f0fe11ad9d20df Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Fri, 7 Nov 2025 15:04:53 +0800 Subject: [PATCH 077/104] change uuid logger to trace level --- .../devices/liquid_handling/liquid_handler_abstract.py | 10 +++++----- unilabos/ros/nodes/resource_tracker.py | 8 +++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/unilabos/devices/liquid_handling/liquid_handler_abstract.py b/unilabos/devices/liquid_handling/liquid_handler_abstract.py index 32e370f..6aefaf3 100644 --- a/unilabos/devices/liquid_handling/liquid_handler_abstract.py +++ b/unilabos/devices/liquid_handling/liquid_handler_abstract.py @@ -1,11 +1,11 @@ from __future__ import annotations -import re -import traceback -from typing import List, Sequence, Optional, Literal, Union, Iterator, Dict, Any, Callable, Set, cast -from collections import Counter + import asyncio import time -import pprint as pp +import traceback +from collections import Counter +from typing import List, Sequence, Optional, Literal, Union, Iterator, Dict, Any, Callable, Set, cast + from pylabrobot.liquid_handling import LiquidHandler, LiquidHandlerBackend, LiquidHandlerChatterboxBackend, Strictness from pylabrobot.liquid_handling.liquid_handler import TipPresenceProbingMethod from pylabrobot.liquid_handling.standard import GripDirection diff --git a/unilabos/ros/nodes/resource_tracker.py b/unilabos/ros/nodes/resource_tracker.py index fc3ca7a..085ce02 100644 --- a/unilabos/ros/nodes/resource_tracker.py +++ b/unilabos/ros/nodes/resource_tracker.py @@ -894,7 +894,7 @@ class DeviceNodeResourceTracker(object): new_uuid = name_to_uuid_map[resource_name] self.set_resource_uuid(res, new_uuid) self.uuid_to_resources[new_uuid] = res - logger.debug(f"设置资源UUID: {resource_name} -> {new_uuid}") + logger.trace(f"设置资源UUID: {resource_name} -> {new_uuid}") return 1 return 0 @@ -917,7 +917,8 @@ class DeviceNodeResourceTracker(object): if resource_name and resource_name in name_to_extra_map: extra = name_to_extra_map[resource_name] self.set_resource_extra(res, extra) - logger.debug(f"设置资源Extra: {resource_name} -> {extra}") + if len(extra): + logger.debug(f"设置资源Extra: {resource_name} -> {extra}") return 1 return 0 @@ -986,9 +987,10 @@ class DeviceNodeResourceTracker(object): if current_uuid: old = self.uuid_to_resources.get(current_uuid) self.uuid_to_resources[current_uuid] = res - logger.debug( + logger.trace( f"收集资源UUID映射: {current_uuid} -> {res} {'' if old is None else f'(覆盖旧值: {old})'}" ) + return 1 return 0 self._traverse_and_process(resource, process) From 5a0c2f98509b7b67f1f71e0a9d47463e14e064e3 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Fri, 7 Nov 2025 16:11:27 +0800 Subject: [PATCH 078/104] enable slave mode --- unilabos/app/backend.py | 4 +- unilabos/app/register.py | 5 +- unilabos/ros/main_slave_run.py | 130 +++++++++++++----------- unilabos/ros/nodes/presets/host_node.py | 21 ++-- unilabos/ros/nodes/resource_tracker.py | 2 +- unilabos/utils/log.py | 3 +- 6 files changed, 90 insertions(+), 75 deletions(-) diff --git a/unilabos/app/backend.py b/unilabos/app/backend.py index d43b954..b2bc0af 100644 --- a/unilabos/app/backend.py +++ b/unilabos/app/backend.py @@ -13,7 +13,7 @@ def start_backend( graph=None, controllers_config: dict = {}, bridges=[], - without_host: bool = False, + is_slave: bool = False, visual: str = "None", resources_mesh_config: dict = {}, **kwargs, @@ -32,7 +32,7 @@ def start_backend( raise ValueError(f"Unsupported backend: {backend}") backend_thread = threading.Thread( - target=main if not without_host else slave, + target=main if not is_slave else slave, args=( devices_config, resources_config, diff --git a/unilabos/app/register.py b/unilabos/app/register.py index f456183..633df98 100644 --- a/unilabos/app/register.py +++ b/unilabos/app/register.py @@ -1,11 +1,12 @@ import json import time +from typing import Optional, Tuple, Dict, Any from unilabos.utils.log import logger from unilabos.utils.type_check import TypeEncoder -def register_devices_and_resources(lab_registry): +def register_devices_and_resources(lab_registry, gather_only=False) -> Optional[Tuple[Dict[str, Any], Dict[str, Any]]]: """ 注册设备和资源到服务器(仅支持HTTP) """ @@ -28,6 +29,8 @@ def register_devices_and_resources(lab_registry): resources_to_register[resource_info["id"]] = resource_info logger.debug(f"[UniLab Register] 收集资源: {resource_info['id']}") + if gather_only: + return devices_to_register, resources_to_register # 注册设备 if devices_to_register: try: diff --git a/unilabos/ros/main_slave_run.py b/unilabos/ros/main_slave_run.py index d9ad368..1ded6da 100644 --- a/unilabos/ros/main_slave_run.py +++ b/unilabos/ros/main_slave_run.py @@ -6,11 +6,12 @@ from typing import Optional, Dict, Any, List import rclpy from unilabos_msgs.srv._serial_command import SerialCommand_Response +from unilabos.app.register import register_devices_and_resources from unilabos.ros.nodes.presets.resource_mesh_manager import ResourceMeshManager from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker, ResourceTreeSet from unilabos.devices.ros_dev.liquid_handler_joint_publisher import LiquidHandlerJointPublisher from unilabos_msgs.srv import SerialCommand # type: ignore -from rclpy.executors import MultiThreadedExecutor, SingleThreadedExecutor +from rclpy.executors import MultiThreadedExecutor from rclpy.node import Node from rclpy.timer import Timer @@ -108,66 +109,51 @@ def slave( rclpy_init_args: List[str] = ["--log-level", "debug"], ) -> None: """从节点函数""" + # 1. 初始化 ROS2 if not rclpy.ok(): rclpy.init(args=rclpy_init_args) executor = rclpy.__executor if not executor: executor = rclpy.__executor = MultiThreadedExecutor() - devices_instances = {} - for device_config in devices_config.root_nodes: - device_id = device_config.res_content.id - if device_config.res_content.type != "device": - d = initialize_device_from_dict(device_id, device_config.get_nested_dict()) - devices_instances[device_id] = d - # 默认初始化 - # if d is not None and isinstance(d, Node): - # executor.add_node(d) - # else: - # print(f"Warning: Device {device_id} could not be initialized or is not a valid Node") - n = Node(f"slaveMachine_{BasicConfig.machine_name}", parameter_overrides=[]) - executor.add_node(n) - - if visual != "disable": - from unilabos.ros.nodes.presets.joint_republisher import JointRepublisher - - resource_mesh_manager = ResourceMeshManager( - resources_mesh_config, - resources_config, # type: ignore FIXME - resource_tracker=DeviceNodeResourceTracker(), - device_id="resource_mesh_manager", - ) - joint_republisher = JointRepublisher("joint_republisher", DeviceNodeResourceTracker()) - - executor.add_node(resource_mesh_manager) - executor.add_node(joint_republisher) + # 1.5 启动 executor 线程 thread = threading.Thread(target=executor.spin, daemon=True, name="slave_executor_thread") thread.start() + # 2. 创建 Slave Machine Node + n = Node(f"slaveMachine_{BasicConfig.machine_name}", parameter_overrides=[]) + executor.add_node(n) + + # 3. 向 Host 报送节点信息和物料,获取 UUID 映射 + uuid_mapping = {} if not BasicConfig.slave_no_host: + # 3.1 报送节点信息 sclient = n.create_client(SerialCommand, "/node_info_update") sclient.wait_for_service() + registry_config = {} + devices_to_register, resources_to_register = register_devices_and_resources(lab_registry, True) + registry_config.update(devices_to_register) + registry_config.update(resources_to_register) request = SerialCommand.Request() request.command = json.dumps( { "machine_name": BasicConfig.machine_name, "type": "slave", "devices_config": devices_config.dump(), - "registry_config": lab_registry.obtain_registry_device_info(), + "registry_config": registry_config, }, ensure_ascii=False, cls=TypeEncoder, ) - response = sclient.call_async(request).result() + sclient.call_async(request).result() logger.info(f"Slave node info updated.") - # 使用新的 c2s_update_resource_tree 服务 - rclient = n.create_client(SerialCommand, "/c2s_update_resource_tree") - rclient.wait_for_service() - - # 序列化 ResourceTreeSet 为 JSON + # 3.2 报送物料树,获取 UUID 映射 if resources_config: + rclient = n.create_client(SerialCommand, "/c2s_update_resource_tree") + rclient.wait_for_service() + request = SerialCommand.Request() request.command = json.dumps( { @@ -180,35 +166,61 @@ def slave( }, ensure_ascii=False, ) - tree_response: SerialCommand_Response = rclient.call_async(request).result() + tree_response: SerialCommand_Response = rclient.call(request) uuid_mapping = json.loads(tree_response.response) - # 创建反向映射:new_uuid -> old_uuid - reverse_uuid_mapping = {new_uuid: old_uuid for old_uuid, new_uuid in uuid_mapping.items()} - for node in resources_config.root_nodes: - if node.res_content.type == "device": - for sub_node in node.children: - # 只有二级子设备 - if sub_node.res_content.type != "device": - device_tracker = devices_instances[node.res_content.id].resource_tracker - # sub_node.res_content.uuid 已经是新UUID,需要用旧UUID去查找 - old_uuid = reverse_uuid_mapping.get(sub_node.res_content.uuid) - if old_uuid: - # 找到旧UUID,使用UUID查找 - resource_instance = device_tracker.figure_resource({"uuid": old_uuid}) - else: - # 未找到旧UUID,使用name查找 - resource_instance = device_tracker.figure_resource({"name": sub_node.res_content.name}) - device_tracker.loop_update_uuid(resource_instance, uuid_mapping) + logger.info(f"Slave resource tree added. UUID mapping: {len(uuid_mapping)} nodes") + + # 3.3 使用 UUID 映射更新 resources_config 的 UUID(参考 client.py 逻辑) + old_uuids = {node.res_content.uuid: node for node in resources_config.all_nodes} + for old_uuid, node in old_uuids.items(): + if old_uuid in uuid_mapping: + new_uuid = uuid_mapping[old_uuid] + node.res_content.uuid = new_uuid + # 更新所有子节点的 parent_uuid + for child in node.children: + child.res_content.parent_uuid = new_uuid else: - logger.error("Slave模式不允许新增非设备节点下的物料") - continue - if tree_response: - logger.info(f"Slave resource tree added. Response: {tree_response.response}") - else: - logger.warning("Slave resource tree add response is None") + logger.warning(f"资源UUID未更新: {old_uuid}") else: logger.info("No resources to add.") + # 4. 初始化所有设备实例(此时 resources_config 的 UUID 已更新) + devices_instances = {} + for device_config in devices_config.root_nodes: + device_id = device_config.res_content.id + if device_config.res_content.type == "device": + d = initialize_device_from_dict(device_id, device_config.get_nested_dict()) + if d is not None: + devices_instances[device_id] = d + logger.info(f"Device {device_id} initialized.") + else: + logger.warning(f"Device {device_id} initialization failed.") + + # 5. 如果启用可视化,创建可视化相关节点 + if visual != "disable": + from unilabos.ros.nodes.presets.joint_republisher import JointRepublisher + + # 将 ResourceTreeSet 转换为 list 用于 visual 组件 + resources_list = ( + [node.res_content.model_dump(by_alias=True) for node in resources_config.all_nodes] + if resources_config + else [] + ) + resource_mesh_manager = ResourceMeshManager( + resources_mesh_config, + resources_list, + resource_tracker=DeviceNodeResourceTracker(), + device_id="resource_mesh_manager", + ) + joint_republisher = JointRepublisher("joint_republisher", DeviceNodeResourceTracker()) + lh_joint_pub = LiquidHandlerJointPublisher( + resources_config=resources_list, resource_tracker=DeviceNodeResourceTracker() + ) + executor.add_node(resource_mesh_manager) + executor.add_node(joint_republisher) + executor.add_node(lh_joint_pub) + + # 7. 保持运行 while True: time.sleep(1) diff --git a/unilabos/ros/nodes/presets/host_node.py b/unilabos/ros/nodes/presets/host_node.py index 346cf9c..5c43a5d 100644 --- a/unilabos/ros/nodes/presets/host_node.py +++ b/unilabos/ros/nodes/presets/host_node.py @@ -878,11 +878,10 @@ class HostNode(BaseROS2DeviceNode): success = False uuid_mapping = {} if len(self.bridges) > 0: - from unilabos.app.web.client import HTTPClient + from unilabos.app.web.client import HTTPClient, http_client - client: HTTPClient = self.bridges[-1] resource_start_time = time.time() - uuid_mapping = client.resource_tree_add(resource_tree_set, mount_uuid, first_add) + uuid_mapping = http_client.resource_tree_add(resource_tree_set, mount_uuid, first_add) success = True resource_end_time = time.time() self.lab_logger().info( @@ -990,9 +989,10 @@ class HostNode(BaseROS2DeviceNode): """ 更新节点信息回调 """ - self.lab_logger().info(f"[Host Node] Node info update request received: {request}") + # self.lab_logger().info(f"[Host Node] Node info update request received: {request}") try: from unilabos.app.communication import get_communication_client + from unilabos.app.web.client import HTTPClient, http_client info = json.loads(request.command) if "SYNC_SLAVE_NODE_INFO" in info: @@ -1001,10 +1001,10 @@ class HostNode(BaseROS2DeviceNode): edge_device_id = info["edge_device_id"] self.device_machine_names[edge_device_id] = machine_name else: - comm_client = get_communication_client() - registry_config = info["registry_config"] - for device_config in registry_config: - comm_client.publish_registry(device_config["id"], device_config) + devices_config = info.pop("devices_config") + registry_config = info.pop("registry_config") + if registry_config: + http_client.resource_registry({"resources": registry_config}) self.lab_logger().debug(f"[Host Node] Node info update: {info}") response.response = "OK" except Exception as e: @@ -1030,10 +1030,9 @@ class HostNode(BaseROS2DeviceNode): success = False if len(self.bridges) > 0: # 边的提交待定 - from unilabos.app.web.client import HTTPClient + from unilabos.app.web.client import HTTPClient, http_client - client: HTTPClient = self.bridges[-1] - r = client.resource_add(add_schema(resources)) + r = http_client.resource_add(add_schema(resources)) success = bool(r) response.success = success diff --git a/unilabos/ros/nodes/resource_tracker.py b/unilabos/ros/nodes/resource_tracker.py index 085ce02..ce23a5b 100644 --- a/unilabos/ros/nodes/resource_tracker.py +++ b/unilabos/ros/nodes/resource_tracker.py @@ -1007,7 +1007,7 @@ class DeviceNodeResourceTracker(object): current_uuid = self._get_resource_attr(res, "uuid", "unilabos_uuid") if current_uuid and current_uuid in self.uuid_to_resources: self.uuid_to_resources.pop(current_uuid) - logger.debug(f"移除资源UUID映射: {current_uuid} -> {res}") + logger.trace(f"移除资源UUID映射: {current_uuid} -> {res}") return 1 return 0 diff --git a/unilabos/utils/log.py b/unilabos/utils/log.py index 74442a6..3894233 100644 --- a/unilabos/utils/log.py +++ b/unilabos/utils/log.py @@ -191,7 +191,8 @@ def configure_logger(loglevel=None): # 添加处理器到根日志记录器 root_logger.addHandler(console_handler) - + logging.getLogger("asyncio").setLevel(logging.INFO) + logging.getLogger("urllib3").setLevel(logging.INFO) # 配置日志系统 configure_logger() From 85dc46cd3833bed48c4fd149a7d32f94f0bfa1a5 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Fri, 31 Oct 2025 19:57:16 +0800 Subject: [PATCH 079/104] support name change during materials change --- unilabos/ros/nodes/base_device_node.py | 221 ++++++++++++++++++------- 1 file changed, 160 insertions(+), 61 deletions(-) diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index 19503fd..febc95f 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -638,6 +638,147 @@ class BaseROS2DeviceNode(Node, Generic[T]): - remove: 从资源树中移除资源 """ from pylabrobot.resources.resource import Resource as ResourcePLR + + def _handle_add( + plr_resources: List[ResourcePLR], tree_set: ResourceTreeSet, additional_add_params: Dict[str, Any] + ) -> Dict[str, Any]: + """ + 处理资源添加操作的内部函数 + + Args: + plr_resources: PLR资源列表 + tree_set: 资源树集合 + additional_add_params: 额外的添加参数 + + Returns: + 操作结果字典 + """ + for plr_resource, tree in zip(plr_resources, tree_set.trees): + self.resource_tracker.add_resource(plr_resource) + self.transfer_to_new_resource(plr_resource, tree, additional_add_params) + + func = getattr(self.driver_instance, "resource_tree_add", None) + if callable(func): + func(plr_resources) + + return {"success": True, "action": "add"} + + def _handle_remove(resources_uuid: List[str]) -> Dict[str, Any]: + """ + 处理资源移除操作的内部函数 + + Args: + resources_uuid: 要移除的资源UUID列表 + + Returns: + 操作结果字典,包含移除的资源列表 + """ + found_resources: List[List[Union[ResourcePLR, dict]]] = self.resource_tracker.figure_resource( + [{"uuid": uid} for uid in resources_uuid], try_mode=True + ) + found_plr_resources = [] + other_plr_resources = [] + + for found_resource in found_resources: + for resource in found_resource: + if issubclass(resource.__class__, ResourcePLR): + found_plr_resources.append(resource) + else: + other_plr_resources.append(resource) + + # 调用driver的remove回调 + func = getattr(self.driver_instance, "resource_tree_remove", None) + if callable(func): + func(found_plr_resources) + + # 从parent卸载并从tracker移除 + for plr_resource in found_plr_resources: + if plr_resource.parent is not None: + plr_resource.parent.unassign_child_resource(plr_resource) + self.resource_tracker.remove_resource(plr_resource) + self.lab_logger().info(f"移除物料 {plr_resource} 及其子节点") + + for other_plr_resource in other_plr_resources: + self.resource_tracker.remove_resource(other_plr_resource) + self.lab_logger().info(f"移除物料 {other_plr_resource} 及其子节点") + + return { + "success": True, + "action": "remove", + "removed_plr": found_plr_resources, + "removed_other": other_plr_resources, + } + + def _handle_update( + plr_resources: List[ResourcePLR], tree_set: ResourceTreeSet, additional_add_params: Dict[str, Any] + ) -> Dict[str, Any]: + """ + 处理资源更新操作的内部函数 + + Args: + plr_resources: PLR资源列表(包含新状态) + tree_set: 资源树集合 + additional_add_params: 额外的参数 + + Returns: + 操作结果字典 + """ + for plr_resource, tree in zip(plr_resources, tree_set.trees): + states = plr_resource.serialize_all_state() + original_instance: ResourcePLR = self.resource_tracker.figure_resource( + {"uuid": tree.root_node.res_content.uuid}, try_mode=False + ) + + # Update操作中包含改名:需要先remove再add + if original_instance.name != plr_resource.name: + old_name = original_instance.name + new_name = plr_resource.name + self.lab_logger().info(f"物料改名操作:{old_name} -> {new_name}") + + # 收集所有相关的uuid(包括子节点) + all_uuids = self.resource_tracker.loop_gather_uuid(original_instance) + _handle_remove(all_uuids) + original_instance.name = new_name + _handle_add([original_instance], tree_set, additional_add_params) + + self.lab_logger().info(f"物料改名完成:{old_name} -> {new_name}") + continue + + # 常规更新:不涉及改名 + original_parent_resource = original_instance.parent + original_parent_resource_uuid = getattr(original_parent_resource, "unilabos_uuid", None) + target_parent_resource_uuid = tree.root_node.res_content.uuid_parent + + self.lab_logger().info( + f"物料{original_instance} 原始父节点{original_parent_resource_uuid} " + f"目标父节点{target_parent_resource_uuid} 更新" + ) + + # 更新extra + if getattr(plr_resource, "unilabos_extra", None) is not None: + original_instance.unilabos_extra = getattr(plr_resource, "unilabos_extra") # type: ignore # noqa: E501 + + # 如果父节点变化,需要重新挂载 + if ( + original_parent_resource_uuid != target_parent_resource_uuid + and original_parent_resource is not None + ): + self.transfer_to_new_resource(original_instance, tree, additional_add_params) + + # 加载状态 + original_instance.load_all_state(states) + child_count = len(original_instance.get_all_children()) + self.lab_logger().info( + f"更新了资源属性 {plr_resource}[{tree.root_node.res_content.uuid}] " f"及其子节点 {child_count} 个" + ) + + # 调用driver的update回调 + func = getattr(self.driver_instance, "resource_tree_update", None) + if callable(func): + func(plr_resources) + + return {"success": True, "action": "update"} + try: data = json.loads(req.command) results = [] @@ -664,68 +805,20 @@ class BaseROS2DeviceNode(Node, Generic[T]): tree_set = ResourceTreeSet.from_raw_list(raw_nodes) try: if action == "add": - # 添加资源到资源跟踪器 + if tree_set is None: + raise ValueError("tree_set不能为None") plr_resources = tree_set.to_plr_resources() - for plr_resource, tree in zip(plr_resources, tree_set.trees): - self.resource_tracker.add_resource(plr_resource) - self.transfer_to_new_resource(plr_resource, tree, additional_add_params) - func = getattr(self.driver_instance, "resource_tree_add", None) - if callable(func): - func(plr_resources) - results.append({"success": True, "action": "add"}) + result = _handle_add(plr_resources, tree_set, additional_add_params) + results.append(result) elif action == "update": - # 更新资源 + if tree_set is None: + raise ValueError("tree_set不能为None") plr_resources = tree_set.to_plr_resources() - for plr_resource, tree in zip(plr_resources, tree_set.trees): - states = plr_resource.serialize_all_state() - original_instance: ResourcePLR = self.resource_tracker.figure_resource( - {"uuid": tree.root_node.res_content.uuid}, try_mode=False - ) - original_parent_resource = original_instance.parent - original_parent_resource_uuid = getattr(original_parent_resource, "unilabos_uuid", None) - target_parent_resource_uuid = tree.root_node.res_content.uuid_parent - self.lab_logger().info( - f"物料{original_instance} 原始父节点{original_parent_resource_uuid} 目标父节点{target_parent_resource_uuid} 更新" - ) - # todo: 对extra进行update - if getattr(plr_resource, "unilabos_extra", None) is not None: - original_instance.unilabos_extra = getattr(plr_resource, "unilabos_extra") - if original_parent_resource_uuid != target_parent_resource_uuid and original_parent_resource is not None: - self.transfer_to_new_resource(original_instance, tree, additional_add_params) - original_instance.load_all_state(states) - self.lab_logger().info( - f"更新了资源属性 {plr_resource}[{tree.root_node.res_content.uuid}] 及其子节点 {len(original_instance.get_all_children())} 个" - ) - - func = getattr(self.driver_instance, "resource_tree_update", None) - if callable(func): - func(plr_resources) - results.append({"success": True, "action": "update"}) + result = _handle_update(plr_resources, tree_set, additional_add_params) + results.append(result) elif action == "remove": - # 移除资源 - found_resources: List[List[Union[ResourcePLR, dict]]] = self.resource_tracker.figure_resource( - [{"uuid": uid} for uid in resources_uuid], try_mode=True - ) - found_plr_resources = [] - other_plr_resources = [] - for found_resource in found_resources: - for resource in found_resource: - if issubclass(resource.__class__, ResourcePLR): - found_plr_resources.append(resource) - else: - other_plr_resources.append(resource) - func = getattr(self.driver_instance, "resource_tree_remove", None) - if callable(func): - func(found_plr_resources) - for plr_resource in found_plr_resources: - if plr_resource.parent is not None: - plr_resource.parent.unassign_child_resource(plr_resource) - self.resource_tracker.remove_resource(plr_resource) - self.lab_logger().info(f"移除物料 {plr_resource} 及其子节点") - for other_plr_resource in other_plr_resources: - self.resource_tracker.remove_resource(other_plr_resource) - self.lab_logger().info(f"移除物料 {other_plr_resource} 及其子节点") - results.append({"success": True, "action": "remove"}) + result = _handle_remove(resources_uuid) + results.append(result) except Exception as e: error_msg = f"Error processing {action} operation: {str(e)}" self.lab_logger().error(f"[Resource Tree Update] {error_msg}") @@ -1004,9 +1097,14 @@ class BaseROS2DeviceNode(Node, Generic[T]): # 通过资源跟踪器获取本地实例 final_resources = queried_resources if is_sequence else queried_resources[0] - final_resources = self.resource_tracker.figure_resource({"name": final_resources.name}, try_mode=False) if not is_sequence else [ - self.resource_tracker.figure_resource({"name": res.name}, try_mode=False) for res in queried_resources - ] + final_resources = ( + self.resource_tracker.figure_resource({"name": final_resources.name}, try_mode=False) + if not is_sequence + else [ + self.resource_tracker.figure_resource({"name": res.name}, try_mode=False) + for res in queried_resources + ] + ) action_kwargs[k] = final_resources except Exception as e: @@ -1227,6 +1325,7 @@ class BaseROS2DeviceNode(Node, Generic[T]): raise JsonCommandInitError( f"执行动作时JSON缺少function_name或function_args: {ex}\n原JSON: {string}\n{traceback.format_exc()}" ) + def _convert_resource_sync(self, resource_data: Dict[str, Any]): """同步转换资源数据为实例""" # 创建资源查询请求 From d7494ca45819bfda65db9098ef57ecf972c75499 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:12:54 +0800 Subject: [PATCH 080/104] fix json dumps --- unilabos/ros/nodes/base_device_node.py | 10 +++----- unilabos/ros/nodes/presets/host_node.py | 33 ++++++++----------------- 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index febc95f..e52ea80 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -705,8 +705,8 @@ class BaseROS2DeviceNode(Node, Generic[T]): return { "success": True, "action": "remove", - "removed_plr": found_plr_resources, - "removed_other": other_plr_resources, + # "removed_plr": found_plr_resources, + # "removed_other": other_plr_resources, } def _handle_update( @@ -736,13 +736,11 @@ class BaseROS2DeviceNode(Node, Generic[T]): self.lab_logger().info(f"物料改名操作:{old_name} -> {new_name}") # 收集所有相关的uuid(包括子节点) - all_uuids = self.resource_tracker.loop_gather_uuid(original_instance) - _handle_remove(all_uuids) + _handle_remove([original_instance.unilabos_uuid]) original_instance.name = new_name _handle_add([original_instance], tree_set, additional_add_params) self.lab_logger().info(f"物料改名完成:{old_name} -> {new_name}") - continue # 常规更新:不涉及改名 original_parent_resource = original_instance.parent @@ -827,7 +825,7 @@ class BaseROS2DeviceNode(Node, Generic[T]): # 返回处理结果 result_json = {"results": results, "total": len(data)} - res.response = json.dumps(result_json, ensure_ascii=False) + res.response = json.dumps(result_json, ensure_ascii=False, cls=TypeEncoder) self.lab_logger().info(f"[Resource Tree Update] Completed processing {len(data)} operations") except json.JSONDecodeError as e: diff --git a/unilabos/ros/nodes/presets/host_node.py b/unilabos/ros/nodes/presets/host_node.py index 5c43a5d..7a8806d 100644 --- a/unilabos/ros/nodes/presets/host_node.py +++ b/unilabos/ros/nodes/presets/host_node.py @@ -165,29 +165,16 @@ class HostNode(BaseROS2DeviceNode): # resources_config 的 root node 是 # # 创建反向映射:new_uuid -> old_uuid # reverse_uuid_mapping = {new_uuid: old_uuid for old_uuid, new_uuid in uuid_mapping.items()} - # for tree in resources_config.trees: - # node = tree.root_node - # if node.res_content.type == "device": - # if node.res_content.id == "host_node": - # continue - # # slave节点走c2s更新接口,拿到add自行update uuid - # device_tracker = self.devices_instances[node.res_content.id].resource_tracker - # old_uuid = reverse_uuid_mapping.get(node.res_content.uuid) - # if old_uuid: - # # 找到旧UUID,使用UUID查找 - # resource_instance = device_tracker.uuid_to_resources.get(old_uuid) - # else: - # # 未找到旧UUID,使用name查找 - # resource_instance = device_tracker.figure_resource( - # {"name": node.res_content.name} - # ) - # device_tracker.loop_update_uuid(resource_instance, uuid_mapping) - # else: - # try: - # for plr_resource in ResourceTreeSet([tree]).to_plr_resources(): - # self.resource_tracker.add_resource(plr_resource) - # except Exception as ex: - # self.lab_logger().warning("[Host Node-Resource] 根节点物料序列化失败!") + for tree in resources_config.trees: + node = tree.root_node + if node.res_content.type == "device": + continue + else: + try: + for plr_resource in ResourceTreeSet([tree]).to_plr_resources(): + self._resource_tracker.add_resource(plr_resource) + except Exception as ex: + self.lab_logger().warning(f"[Host Node-Resource] 根节点物料{tree}序列化失败!") except Exception as ex: logger.error(f"[Host Node-Resource] 添加物料出错!\n{traceback.format_exc()}") # 初始化Node基类,传递空参数覆盖列表 From 9bba4620b74b10dd499d42cc9b4663dddbecccf9 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:14:43 +0800 Subject: [PATCH 081/104] fix resource_get param --- unilabos/ros/nodes/base_device_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index e52ea80..7025e77 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -795,7 +795,7 @@ class BaseROS2DeviceNode(Node, Generic[T]): ].call_async( SerialCommand.Request( command=json.dumps( - {"data": {"data": resources_uuid, "with_children": True if action == "add" else "update"}, "action": "get"} + {"data": {"data": resources_uuid, "with_children": True if action == "add" else False}, "action": "get"} ) ) ) # type: ignore From 48d429ae0038a11495333addcb1c4101efcb3272 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:14:43 +0800 Subject: [PATCH 082/104] fix resource_get param --- unilabos/ros/nodes/base_device_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index e52ea80..7025e77 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -795,7 +795,7 @@ class BaseROS2DeviceNode(Node, Generic[T]): ].call_async( SerialCommand.Request( command=json.dumps( - {"data": {"data": resources_uuid, "with_children": True if action == "add" else "update"}, "action": "get"} + {"data": {"data": resources_uuid, "with_children": True if action == "add" else False}, "action": "get"} ) ) ) # type: ignore From 7af32b379bb90aa40e84a64ade4910d772c15073 Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Sat, 8 Nov 2025 14:53:25 +0800 Subject: [PATCH 083/104] Add YB_ prefix to bottle carrier model names --- unilabos/resources/bioyond/YB_bottle_carriers.py | 16 ++++++++-------- unilabos/resources/bioyond/YB_bottles.py | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/unilabos/resources/bioyond/YB_bottle_carriers.py b/unilabos/resources/bioyond/YB_bottle_carriers.py index 5284f0a..d61a327 100644 --- a/unilabos/resources/bioyond/YB_bottle_carriers.py +++ b/unilabos/resources/bioyond/YB_bottle_carriers.py @@ -233,7 +233,7 @@ def YB_1BottleCarrier(name: str) -> BottleCarrier: resource_size_y=beaker_diameter, name_prefix=name, ), - model="1BottleCarrier", + model="YB_1BottleCarrier", ) carrier.num_items_x = 1 carrier.num_items_y = 1 @@ -270,7 +270,7 @@ def YB_1GaoNianYeBottleCarrier(name: str) -> BottleCarrier: resource_size_y=beaker_diameter, name_prefix=name, ), - model="1GaoNianYeBottleCarrier", + model="YB_1GaoNianYeBottleCarrier", ) carrier.num_items_x = 1 carrier.num_items_y = 1 @@ -307,7 +307,7 @@ def YB_1Bottle100mlCarrier(name: str) -> BottleCarrier: resource_size_y=beaker_diameter, name_prefix=name, ), - model="1Bottle100mlCarrier", + model="YB_1Bottle100mlCarrier", ) carrier.num_items_x = 1 carrier.num_items_y = 1 @@ -405,7 +405,7 @@ def YB_6x20ml_DispensingVialCarrier(name: str) -> BottleCarrier: size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="6x20ml_DispensingVialCarrier", + model="YB_6x20ml_DispensingVialCarrier", ) carrier.num_items_x = 4 carrier.num_items_y = 2 @@ -455,7 +455,7 @@ def YB_6x_SmallSolutionBottleCarrier(name: str) -> BottleCarrier: size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="6x_SmallSolutionBottleCarrier", + model="YB_6x_SmallSolutionBottleCarrier", ) carrier.num_items_x = 4 carrier.num_items_y = 2 @@ -505,7 +505,7 @@ def YB_4x_LargeSolutionBottleCarrier(name: str) -> BottleCarrier: size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="4x_LargeSolutionBottleCarrier", + model="YB_4x_LargeSolutionBottleCarrier", ) carrier.num_items_x = 2 carrier.num_items_y = 2 @@ -591,7 +591,7 @@ def YB_AdapterBlock(name: str) -> BottleCarrier: resource_size_y=adapter_diameter, name_prefix=name, ), - model="AdapterBlock", + model="YB_AdapterBlock", ) carrier.num_items_x = 1 carrier.num_items_y = 1 @@ -639,7 +639,7 @@ def YB_TipBox(name: str) -> BottleCarrier: size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="TipBox", + model="YB_TipBox", ) carrier.num_items_x = 12 carrier.num_items_y = 8 diff --git a/unilabos/resources/bioyond/YB_bottles.py b/unilabos/resources/bioyond/YB_bottles.py index 432383e..83b73e4 100644 --- a/unilabos/resources/bioyond/YB_bottles.py +++ b/unilabos/resources/bioyond/YB_bottles.py @@ -15,7 +15,7 @@ def YB_jia_yang_tou_da( height=height, max_volume=max_volume, barcode=barcode, - model="Solid_Stock", + model="YB_jia_yang_tou_da_1X1_carrier", ) """液1x1""" @@ -51,7 +51,7 @@ def YB_ye_100ml_Bottle( height=height, max_volume=max_volume, barcode=barcode, - model="Liquid_Bottle_100ml", + model="YB_1Bottle100mlCarrier", ) """高粘液""" @@ -105,7 +105,7 @@ def YB_fen_ye_20ml_Bottle( height=height, max_volume=max_volume, barcode=barcode, - model="Separation_Bottle_20ml", + model="YB_fen_ye_20ml_Bottle", ) """配液瓶(小)""" @@ -123,7 +123,7 @@ def YB_pei_ye_xiao_Bottle( height=height, max_volume=max_volume, barcode=barcode, - model="Mixing_Bottle_Small", + model="YB_pei_ye_xiao_Bottle", ) """配液瓶(大)""" @@ -141,7 +141,7 @@ def YB_pei_ye_da_Bottle( height=height, max_volume=max_volume, barcode=barcode, - model="Mixing_Bottle_Large", + model="YB_pei_ye_da_Bottle", ) """枪头""" @@ -159,5 +159,5 @@ def YB_Pipette_Tip( height=height, max_volume=max_volume, barcode=barcode, - model="Pipette_Tip", + model="YB_Pipette_Tip", ) From 45a778b928f8c29d0af7d1be371c4a317cc21bee Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Sat, 8 Nov 2025 15:18:52 +0800 Subject: [PATCH 084/104] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=87=8F=E5=B0=91mod?= =?UTF-8?q?bus=E6=8A=A5=E8=AD=A6=E4=BF=A1=E6=81=AF=EF=BC=8C=E4=BB=A5?= =?UTF-8?q?=E5=8F=8Awebsocket=E6=8A=A5=E8=AD=A6=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- unilabos/app/ws_client.py | 3 ++- unilabos/utils/log.py | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/unilabos/app/ws_client.py b/unilabos/app/ws_client.py index c91ca8e..1cf0767 100644 --- a/unilabos/app/ws_client.py +++ b/unilabos/app/ws_client.py @@ -421,7 +421,8 @@ class MessageProcessor: ssl_context = ssl_module.create_default_context() ws_logger = logging.getLogger("websockets.client") - ws_logger.setLevel(logging.INFO) + # ws_logger.setLevel(logging.INFO) + ws_logger.setLevel(logging.WARNING) # 只显示警告和错误 async with websockets.connect( self.websocket_url, diff --git a/unilabos/utils/log.py b/unilabos/utils/log.py index 74442a6..b0b1dfb 100644 --- a/unilabos/utils/log.py +++ b/unilabos/utils/log.py @@ -191,6 +191,13 @@ def configure_logger(loglevel=None): # 添加处理器到根日志记录器 root_logger.addHandler(console_handler) + + # 降低第三方库的日志级别,避免过多输出 + # pymodbus 库的日志太详细,设置为 WARNING + logging.getLogger('pymodbus').setLevel(TRACE_LEVEL) + logging.getLogger('pymodbus.logging').setLevel(TRACE_LEVEL) + logging.getLogger('pymodbus.logging.base').setLevel(TRACE_LEVEL) + logging.getLogger('pymodbus.logging.decoders').setLevel(TRACE_LEVEL) # 配置日志系统 From be429147c0fd1074ca88c2019619cb36499e19d4 Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Sat, 8 Nov 2025 15:42:18 +0800 Subject: [PATCH 085/104] Fix infinite recursion in YB_jia_yang_tou_da by renaming carrier function to YB_jia_yang_tou_da_Carrier --- test/resources/test_resourcetreeset.py | 4 +- .../workstation/bioyond_studio/config.py | 28 +++++----- .../registry/resources/bioyond/YB_bottle.yaml | 18 +++---- .../resources/bioyond/YB_bottle_carriers.yaml | 54 +++++++++---------- .../resources/bioyond/YB_bottle_carriers.py | 48 ++++++++--------- unilabos/resources/bioyond/YB_bottles.py | 16 +++--- 6 files changed, 84 insertions(+), 84 deletions(-) diff --git a/test/resources/test_resourcetreeset.py b/test/resources/test_resourcetreeset.py index 99c6a98..3d3bd4e 100644 --- a/test/resources/test_resourcetreeset.py +++ b/test/resources/test_resourcetreeset.py @@ -15,9 +15,9 @@ lab_registry.setup() type_mapping = { - "加样头(大)": ("YB_jia_yang_tou_da_1X1_carrier", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + "加样头(大)": ("YB_jia_yang_tou_da", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), "液": ("YB_1BottleCarrier", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), - "配液瓶(小)板": ("YB_6x_SmallSolutionBottleCarrier", "3a190c8b-3284-af78-d29f-9a69463ad047"), + "配液瓶(小)板": ("YB_peiyepingxiaoban", "3a190c8b-3284-af78-d29f-9a69463ad047"), "配液瓶(小)": ("YB_pei_ye_xiao_Bottler", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"), } diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index adb2d72..c416ab8 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -234,22 +234,22 @@ WAREHOUSE_MAPPING = { # 物料类型配置 MATERIAL_TYPE_MAPPINGS = { - "100ml液体": ("YB_1Bottle100mlCarrier", "d37166b3-ecaa-481e-bd84-3032b795ba07"), - "液": ("YB_1BottleCarrier", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), - "高粘液": ("YB_1GaoNianYeBottleCarrier", "abe8df30-563d-43d2-85e0-cabec59ddc16"), - "加样头(大)": ("YB_jia_yang_tou_da_1X1_carrier", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), - # "加样头(大)板": ("YB_jia_yang_tou_da_1X1_carrier", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"), - "5ml分液瓶板": ("YB_6x5ml_DispensingVialCarrier", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"), - "5ml分液瓶": ("YB_fen_ye_5ml_Bottle", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"), - "20ml分液瓶板": ("YB_6x20ml_DispensingVialCarrier", "3a192fa4-47db-3449-162a-eaf8aba57e27"), - "20ml分液瓶": ("YB_fen_ye_20ml_Bottle", "3a192c2b-19e8-f0a3-035e-041ca8ca1035"), - "配液瓶(小)板": ("YB_6x_SmallSolutionBottleCarrier", "3a190c8b-3284-af78-d29f-9a69463ad047"), + "100ml液体": ("YB_100ml_yeti", "d37166b3-ecaa-481e-bd84-3032b795ba07"), + "液": ("YB_ye", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), + "高粘液": ("YB_gaonianye", "abe8df30-563d-43d2-85e0-cabec59ddc16"), + "加样头(大)": ("YB_jia_yang_tou_da", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + # "加样头(大)板": ("YB_jia_yang_tou_da", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"), + "5ml分液瓶板": ("YB_5ml_fenyepingban", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"), + "5ml分液瓶": ("YB_5ml_fenyeping", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"), + "20ml分液瓶板": ("YB_20ml_fenyepingban", "3a192fa4-47db-3449-162a-eaf8aba57e27"), + "20ml分液瓶": ("YB_20ml_fenyeping", "3a192c2b-19e8-f0a3-035e-041ca8ca1035"), + "配液瓶(小)板": ("YB_peiyepingxiaoban", "3a190c8b-3284-af78-d29f-9a69463ad047"), "配液瓶(小)": ("YB_pei_ye_xiao_Bottle", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"), - "配液瓶(大)板": ("YB_4x_LargeSolutionBottleCarrier", "53e50377-32dc-4781-b3c0-5ce45bc7dc27"), + "配液瓶(大)板": ("YB_peiyepingdaban", "53e50377-32dc-4781-b3c0-5ce45bc7dc27"), "配液瓶(大)": ("YB_pei_ye_da_Bottle", "19c52ad1-51c5-494f-8854-576f4ca9c6ca"), - "适配器块": ("YB_AdapterBlock", "efc3bb32-d504-4890-91c0-b64ed3ac80cf"), - "枪头盒": ("YB_TipBox", "3a192c2e-20f3-a44a-0334-c8301839d0b3"), - "枪头": ("YB_Pipette_Tip", "b6196971-1050-46da-9927-333e8dea062d"), + "适配器块": ("YB_shi_pei_qi_kuai", "efc3bb32-d504-4890-91c0-b64ed3ac80cf"), + "枪头盒": ("YB_qiang_tou_he", "3a192c2e-20f3-a44a-0334-c8301839d0b3"), + "枪头": ("YB_qiang_tou", "b6196971-1050-46da-9927-333e8dea062d"), } SOLID_LIQUID_MAPPINGS = { diff --git a/unilabos/registry/resources/bioyond/YB_bottle.yaml b/unilabos/registry/resources/bioyond/YB_bottle.yaml index 6d8a380..7cafab4 100644 --- a/unilabos/registry/resources/bioyond/YB_bottle.yaml +++ b/unilabos/registry/resources/bioyond/YB_bottle.yaml @@ -1,37 +1,37 @@ -YB_Pipette_Tip: +YB_qiang_tou: category: - yb3 - YB_bottle class: - module: unilabos.resources.bioyond.YB_bottles:YB_Pipette_Tip + module: unilabos.resources.bioyond.YB_bottles:YB_qiang_tou type: pylabrobot - description: YB_Pipette_Tip + description: YB_qiang_tou handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 -YB_fen_ye_20ml_Bottle: +YB_20ml_fenyeping: category: - yb3 - YB_bottle class: - module: unilabos.resources.bioyond.YB_bottles:YB_fen_ye_20ml_Bottle + module: unilabos.resources.bioyond.YB_bottles:YB_20ml_fenyeping type: pylabrobot - description: YB_fen_ye_20ml_Bottle + description: YB_20ml_fenyeping handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 -YB_fen_ye_5ml_Bottle: +YB_5ml_fenyeping: category: - yb3 - YB_bottle class: - module: unilabos.resources.bioyond.YB_bottles:YB_fen_ye_5ml_Bottle + module: unilabos.resources.bioyond.YB_bottles:YB_5ml_fenyeping type: pylabrobot - description: YB_fen_ye_5ml_Bottle + description: YB_5ml_fenyeping handles: [] icon: '' init_param_schema: {} diff --git a/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml b/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml index eb2ff6f..a387ca6 100644 --- a/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml +++ b/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml @@ -1,11 +1,11 @@ -YB_1Bottle100mlCarrier: +YB_100ml_yeti: category: - yb3 - YB_bottle_carriers class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_1Bottle100mlCarrier + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_100ml_yeti type: pylabrobot - description: YB_1Bottle100mlCarrier + description: YB_100ml_yeti handles: [] icon: '' init_param_schema: {} @@ -24,27 +24,27 @@ YB_1BottleCarrier: init_param_schema: {} registry_type: resource version: 1.0.0 -YB_1GaoNianYeBottleCarrier: +YB_gaonianye: category: - yb3 - YB_bottle_carriers class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_1GaoNianYeBottleCarrier + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_gaonianye type: pylabrobot - description: YB_1GaoNianYeBottleCarrier + description: YB_gaonianye handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 -YB_4x_LargeSolutionBottleCarrier: +YB_peiyepingdaban: category: - yb3 - YB_bottle_carriers class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_4x_LargeSolutionBottleCarrier + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_peiyepingdaban type: pylabrobot - description: YB_4x_LargeSolutionBottleCarrier + description: YB_peiyepingdaban handles: [] icon: '' init_param_schema: {} @@ -76,66 +76,66 @@ YB_6VialCarrier: init_param_schema: {} registry_type: resource version: 1.0.0 -YB_6x20ml_DispensingVialCarrier: +YB_20ml_fenyepingban: category: - yb3 - YB_bottle_carriers class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6x20ml_DispensingVialCarrier + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_20ml_fenyepingban type: pylabrobot - description: YB_6x20ml_DispensingVialCarrier + description: YB_20ml_fenyepingban handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 -YB_6x5ml_DispensingVialCarrier: +YB_5ml_fenyepingban: category: - yb3 - YB_bottle_carriers class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6x5ml_DispensingVialCarrier + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_5ml_fenyepingban type: pylabrobot - description: YB_6x5ml_DispensingVialCarrier + description: YB_5ml_fenyepingban handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 -YB_6x_SmallSolutionBottleCarrier: +YB_peiyepingxiaoban: category: - yb3 - YB_bottle_carriers class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6x_SmallSolutionBottleCarrier + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_peiyepingxiaoban type: pylabrobot - description: YB_6x_SmallSolutionBottleCarrier + description: YB_peiyepingxiaoban handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 -YB_AdapterBlock: +YB_shi_pei_qi_kuai: category: - yb3 - YB_bottle_carriers class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_AdapterBlock + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_shi_pei_qi_kuai type: pylabrobot - description: YB_AdapterBlock + description: YB_shi_pei_qi_kuai handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 -YB_TipBox: +YB_qiang_tou_he: category: - yb3 - YB_bottle_carriers class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_TipBox + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_qiang_tou_he type: pylabrobot - description: YB_TipBox + description: YB_qiang_tou_he handles: [] icon: '' init_param_schema: {} @@ -167,14 +167,14 @@ YB_jia_yang_tou_da: init_param_schema: {} registry_type: resource version: 1.0.0 -YB_jia_yang_tou_da_1X1_carrier: +YB_jia_yang_tou_da_Carrier: category: - yb3 - YB_bottle_carriers class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_jia_yang_tou_da_1X1_carrier + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_jia_yang_tou_da_Carrier type: pylabrobot - description: YB_jia_yang_tou_da_1X1_carrier + description: YB_jia_yang_tou_da_Carrier handles: [] icon: '' init_param_schema: {} diff --git a/unilabos/resources/bioyond/YB_bottle_carriers.py b/unilabos/resources/bioyond/YB_bottle_carriers.py index d61a327..28c8fcc 100644 --- a/unilabos/resources/bioyond/YB_bottle_carriers.py +++ b/unilabos/resources/bioyond/YB_bottle_carriers.py @@ -6,11 +6,11 @@ from unilabos.resources.bioyond.YB_bottles import ( YB_ye_Bottle, YB_ye_100ml_Bottle, YB_gao_nian_ye_Bottle, - YB_fen_ye_5ml_Bottle, - YB_fen_ye_20ml_Bottle, + YB_5ml_fenyeping, + YB_20ml_fenyeping, YB_pei_ye_xiao_Bottle, YB_pei_ye_da_Bottle, - YB_Pipette_Tip, + YB_qiang_tou, ) # 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial @@ -243,7 +243,7 @@ def YB_1BottleCarrier(name: str) -> BottleCarrier: # 高粘液瓶载架 - 单个中央位置 -def YB_1GaoNianYeBottleCarrier(name: str) -> BottleCarrier: +def YB_gaonianye(name: str) -> BottleCarrier: # 载架尺寸 (mm) carrier_size_x = 127.8 @@ -270,7 +270,7 @@ def YB_1GaoNianYeBottleCarrier(name: str) -> BottleCarrier: resource_size_y=beaker_diameter, name_prefix=name, ), - model="YB_1GaoNianYeBottleCarrier", + model="YB_gaonianye", ) carrier.num_items_x = 1 carrier.num_items_y = 1 @@ -280,7 +280,7 @@ def YB_1GaoNianYeBottleCarrier(name: str) -> BottleCarrier: # 100ml液体瓶载架 - 单个中央位置 -def YB_1Bottle100mlCarrier(name: str) -> BottleCarrier: +def YB_100ml_yeti(name: str) -> BottleCarrier: # 载架尺寸 (mm) carrier_size_x = 127.8 @@ -307,7 +307,7 @@ def YB_1Bottle100mlCarrier(name: str) -> BottleCarrier: resource_size_y=beaker_diameter, name_prefix=name, ), - model="YB_1Bottle100mlCarrier", + model="YB_100ml_yeti", ) carrier.num_items_x = 1 carrier.num_items_y = 1 @@ -316,7 +316,7 @@ def YB_1Bottle100mlCarrier(name: str) -> BottleCarrier: return carrier # 5ml分液瓶板 - 4x2布局,8个位置 -def YB_6x5ml_DispensingVialCarrier(name: str) -> BottleCarrier: +def YB_5ml_fenyepingban(name: str) -> BottleCarrier: # 载架尺寸 (mm) @@ -355,18 +355,18 @@ def YB_6x5ml_DispensingVialCarrier(name: str) -> BottleCarrier: size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="YB_6x5ml_DispensingVialCarrier", + model="YB_5ml_fenyepingban", ) carrier.num_items_x = 4 carrier.num_items_y = 2 carrier.num_items_z = 1 ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"] for i in range(8): - carrier[i] = YB_fen_ye_5ml_Bottle(f"{name}_vial_{ordering[i]}") + carrier[i] = YB_5ml_fenyeping(f"{name}_vial_{ordering[i]}") return carrier # 20ml分液瓶板 - 4x2布局,8个位置 -def YB_6x20ml_DispensingVialCarrier(name: str) -> BottleCarrier: +def YB_20ml_fenyepingban(name: str) -> BottleCarrier: # 载架尺寸 (mm) @@ -405,18 +405,18 @@ def YB_6x20ml_DispensingVialCarrier(name: str) -> BottleCarrier: size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="YB_6x20ml_DispensingVialCarrier", + model="YB_20ml_fenyepingban", ) carrier.num_items_x = 4 carrier.num_items_y = 2 carrier.num_items_z = 1 ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"] for i in range(8): - carrier[i] = YB_fen_ye_20ml_Bottle(f"{name}_vial_{ordering[i]}") + carrier[i] = YB_20ml_fenyeping(f"{name}_vial_{ordering[i]}") return carrier # 配液瓶(小)板 - 4x2布局,8个位置 -def YB_6x_SmallSolutionBottleCarrier(name: str) -> BottleCarrier: +def YB_peiyepingxiaoban(name: str) -> BottleCarrier: # 载架尺寸 (mm) @@ -455,7 +455,7 @@ def YB_6x_SmallSolutionBottleCarrier(name: str) -> BottleCarrier: size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="YB_6x_SmallSolutionBottleCarrier", + model="YB_peiyepingxiaoban", ) carrier.num_items_x = 4 carrier.num_items_y = 2 @@ -467,7 +467,7 @@ def YB_6x_SmallSolutionBottleCarrier(name: str) -> BottleCarrier: # 配液瓶(大)板 - 2x2布局,4个位置 -def YB_4x_LargeSolutionBottleCarrier(name: str) -> BottleCarrier: +def YB_peiyepingdaban(name: str) -> BottleCarrier: # 载架尺寸 (mm) carrier_size_x = 127.8 @@ -505,7 +505,7 @@ def YB_4x_LargeSolutionBottleCarrier(name: str) -> BottleCarrier: size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="YB_4x_LargeSolutionBottleCarrier", + model="YB_peiyepingdaban", ) carrier.num_items_x = 2 carrier.num_items_y = 2 @@ -516,7 +516,7 @@ def YB_4x_LargeSolutionBottleCarrier(name: str) -> BottleCarrier: return carrier # 加样头(大)板 - 1x1布局,1个位置 -def YB_jia_yang_tou_da_1X1_carrier(name: str) -> BottleCarrier: +def YB_jia_yang_tou_da_Carrier(name: str) -> BottleCarrier: # 载架尺寸 (mm) carrier_size_x = 127.8 @@ -554,7 +554,7 @@ def YB_jia_yang_tou_da_1X1_carrier(name: str) -> BottleCarrier: size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="YB_6x_LargeDispenseHeadCarrier", + model="YB_jia_yang_tou_da_Carrier", ) carrier.num_items_x = 1 carrier.num_items_y = 1 @@ -563,7 +563,7 @@ def YB_jia_yang_tou_da_1X1_carrier(name: str) -> BottleCarrier: return carrier -def YB_AdapterBlock(name: str) -> BottleCarrier: +def YB_shi_pei_qi_kuai(name: str) -> BottleCarrier: """适配器块 - 单个中央位置""" # 载架尺寸 (mm) @@ -591,7 +591,7 @@ def YB_AdapterBlock(name: str) -> BottleCarrier: resource_size_y=adapter_diameter, name_prefix=name, ), - model="YB_AdapterBlock", + model="YB_shi_pei_qi_kuai", ) carrier.num_items_x = 1 carrier.num_items_y = 1 @@ -600,7 +600,7 @@ def YB_AdapterBlock(name: str) -> BottleCarrier: return carrier -def YB_TipBox(name: str) -> BottleCarrier: +def YB_qiang_tou_he(name: str) -> BottleCarrier: """枪头盒 - 8x12布局,96个位置""" # 载架尺寸 (mm) @@ -639,7 +639,7 @@ def YB_TipBox(name: str) -> BottleCarrier: size_y=carrier_size_y, size_z=carrier_size_z, sites=sites, - model="YB_TipBox", + model="YB_qiang_tou_he", ) carrier.num_items_x = 12 carrier.num_items_y = 8 @@ -648,6 +648,6 @@ def YB_TipBox(name: str) -> BottleCarrier: for i in range(96): row = chr(65 + i // 12) # A-H col = (i % 12) + 1 # 1-12 - carrier[i] = YB_Pipette_Tip(f"{name}_tip_{row}{col}") + carrier[i] = YB_qiang_tou(f"{name}_tip_{row}{col}") return carrier diff --git a/unilabos/resources/bioyond/YB_bottles.py b/unilabos/resources/bioyond/YB_bottles.py index 83b73e4..c8f1c95 100644 --- a/unilabos/resources/bioyond/YB_bottles.py +++ b/unilabos/resources/bioyond/YB_bottles.py @@ -15,7 +15,7 @@ def YB_jia_yang_tou_da( height=height, max_volume=max_volume, barcode=barcode, - model="YB_jia_yang_tou_da_1X1_carrier", + model="YB_jia_yang_tou_da", ) """液1x1""" @@ -51,7 +51,7 @@ def YB_ye_100ml_Bottle( height=height, max_volume=max_volume, barcode=barcode, - model="YB_1Bottle100mlCarrier", + model="YB_100ml_yeti", ) """高粘液""" @@ -73,7 +73,7 @@ def YB_gao_nian_ye_Bottle( ) """5ml分液瓶""" -def YB_fen_ye_5ml_Bottle( +def YB_5ml_fenyeping( name: str, diameter: float = 20.0, height: float = 50.0, @@ -87,11 +87,11 @@ def YB_fen_ye_5ml_Bottle( height=height, max_volume=max_volume, barcode=barcode, - model="YB_fen_ye_5ml_Bottle", + model="YB_5ml_fenyeping", ) """20ml分液瓶""" -def YB_fen_ye_20ml_Bottle( +def YB_20ml_fenyeping( name: str, diameter: float = 30.0, height: float = 65.0, @@ -105,7 +105,7 @@ def YB_fen_ye_20ml_Bottle( height=height, max_volume=max_volume, barcode=barcode, - model="YB_fen_ye_20ml_Bottle", + model="YB_20ml_fenyeping", ) """配液瓶(小)""" @@ -145,7 +145,7 @@ def YB_pei_ye_da_Bottle( ) """枪头""" -def YB_Pipette_Tip( +def YB_qiang_tou( name: str, diameter: float = 10.0, height: float = 50.0, @@ -159,5 +159,5 @@ def YB_Pipette_Tip( height=height, max_volume=max_volume, barcode=barcode, - model="YB_Pipette_Tip", + model="YB_qiang_tou", ) From 442b759397c802dee5431cb99097249cc43bab8d Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Sat, 8 Nov 2025 15:56:39 +0800 Subject: [PATCH 086/104] =?UTF-8?q?=E4=BF=AE=E6=94=B9pymodbus=E5=92=8Cwebs?= =?UTF-8?q?ocket=E7=9A=84=E6=8A=A5=E9=80=81=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- unilabos/app/ws_client.py | 5 ++--- unilabos/ros/nodes/presets/host_node.py | 2 +- unilabos/utils/log.py | 13 +++++++++---- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/unilabos/app/ws_client.py b/unilabos/app/ws_client.py index 1cf0767..d9f1a58 100644 --- a/unilabos/app/ws_client.py +++ b/unilabos/app/ws_client.py @@ -421,8 +421,7 @@ class MessageProcessor: ssl_context = ssl_module.create_default_context() ws_logger = logging.getLogger("websockets.client") - # ws_logger.setLevel(logging.INFO) - ws_logger.setLevel(logging.WARNING) # 只显示警告和错误 + # 日志级别已在 unilabos.utils.log 中统一配置为 WARNING async with websockets.connect( self.websocket_url, @@ -1198,7 +1197,7 @@ class WebSocketClient(BaseCommunicationClient): }, } self.message_processor.send_message(message) - logger.debug(f"[WebSocketClient] Device status published: {device_id}.{property_name}") + logger.trace(f"[WebSocketClient] Device status published: {device_id}.{property_name}") def publish_job_status( self, feedback_data: dict, item: QueueItem, status: str, return_info: Optional[dict] = None diff --git a/unilabos/ros/nodes/presets/host_node.py b/unilabos/ros/nodes/presets/host_node.py index 7a8806d..53b44c2 100644 --- a/unilabos/ros/nodes/presets/host_node.py +++ b/unilabos/ros/nodes/presets/host_node.py @@ -652,7 +652,7 @@ class HostNode(BaseROS2DeviceNode): if bCreate: self.lab_logger().trace(f"Status created: {device_id}.{property_name} = {msg.data}") else: - self.lab_logger().debug(f"Status updated: {device_id}.{property_name} = {msg.data}") + self.lab_logger().trace(f"Status updated: {device_id}.{property_name} = {msg.data}") def send_goal( self, diff --git a/unilabos/utils/log.py b/unilabos/utils/log.py index b0b1dfb..9bf5ceb 100644 --- a/unilabos/utils/log.py +++ b/unilabos/utils/log.py @@ -194,10 +194,15 @@ def configure_logger(loglevel=None): # 降低第三方库的日志级别,避免过多输出 # pymodbus 库的日志太详细,设置为 WARNING - logging.getLogger('pymodbus').setLevel(TRACE_LEVEL) - logging.getLogger('pymodbus.logging').setLevel(TRACE_LEVEL) - logging.getLogger('pymodbus.logging.base').setLevel(TRACE_LEVEL) - logging.getLogger('pymodbus.logging.decoders').setLevel(TRACE_LEVEL) + logging.getLogger('pymodbus').setLevel(logging.WARNING) + logging.getLogger('pymodbus.logging').setLevel(logging.WARNING) + logging.getLogger('pymodbus.logging.base').setLevel(logging.WARNING) + logging.getLogger('pymodbus.logging.decoders').setLevel(logging.WARNING) + + # websockets 库的日志输出较多,设置为 WARNING + logging.getLogger('websockets').setLevel(logging.WARNING) + logging.getLogger('websockets.client').setLevel(logging.WARNING) + logging.getLogger('websockets.server').setLevel(logging.WARNING) # 配置日志系统 From f872d3ef5698be3f01f642f714f1df6eee3b3e40 Mon Sep 17 00:00:00 2001 From: Junhan Chang Date: Sun, 9 Nov 2025 01:00:05 +0800 Subject: [PATCH 087/104] add electrode_sheets definition, and fix magazines --- .../coin_cell_assembly/YB_YH_materials.py | 124 ++------ unilabos/resources/battery/electrode_sheet.py | 179 ++++++++++++ unilabos/resources/battery/magazine.py | 267 +++++++++++------- 3 files changed, 354 insertions(+), 216 deletions(-) create mode 100644 unilabos/resources/battery/electrode_sheet.py diff --git a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py index 8bb0a8d..d5f447c 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py +++ b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py @@ -18,70 +18,11 @@ from pylabrobot.resources.tip_rack import TipRack, TipSpot from pylabrobot.resources.trash import Trash from pylabrobot.resources.utils import create_ordered_items_2d -from unilabos.resources.battery.magazine import MagazineHolder_1, MagazineHolder_2, MagazineHolder_4, MagazineHolder_6 +from unilabos.resources.battery.magazine import MagazineHolder_4_Cathode, MagazineHolder_6_Cathode, MagazineHolder_6_Anode, MagazineHolder_6_Battery from unilabos.resources.battery.bottle_carriers import YIHUA_Electrolyte_12VialCarrier -class ElectrodeSheetState(TypedDict): - diameter: float # 直径 (mm) - thickness: float # 厚度 (mm) - mass: float # 质量 (g) - material_type: str # 材料类型(正极、负极、隔膜、弹片、垫片、铝箔等) - height: float - electrolyte_name: str - data_electrolyte_code: str - open_circuit_voltage: float - assembly_pressure: float - electrolyte_volume: float - info: Optional[str] # 附加信息 - -class ElectrodeSheet(Resource): - """极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料""" - - def __init__( - self, - name: str = "极片", - size_x=10, - size_y=10, - size_z=10, - category: str = "electrode_sheet", - model: Optional[str] = None, - ): - """初始化极片 - - Args: - name: 极片名称 - category: 类别 - model: 型号 - """ - super().__init__( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - category=category, - model=model, - ) - self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState( - diameter=14, - thickness=0.1, - mass=0.5, - material_type="copper", - info=None - ) - - # TODO: 这个还要不要?给self._unilabos_state赋值的? - def load_state(self, state: Dict[str, Any]) -> None: - """格式不变""" - super().load_state(state) - self._unilabos_state = state - #序列化 - def serialize_state(self) -> Dict[str, Dict[str, Any]]: - """格式不变""" - data = super().serialize_state() - data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) - return data # TODO: 这个应该只能放一个极片 class MaterialHoleState(TypedDict): @@ -477,13 +418,13 @@ def TipBox64( size_x: float = 127.8, size_y: float = 85.5, size_z: float = 60.0, - category: str = "tip_box_64", + category: str = "tip_rack", model: Optional[str] = None, ): """64孔枪头盒类""" from pylabrobot.resources.tip import Tip - # 创建8x8=64个枪头位 + # 创建12x8=96个枪头位 def make_tip(): return Tip( has_filter=False, @@ -508,17 +449,19 @@ def TipBox64( ) idx_available = list(range(0, 32)) + list(range(64, 96)) tip_spots_available = {k: v for i, (k, v) in enumerate(tip_spots.items()) if i in idx_available} - return TipRack( + tip_rack = TipRack( name=name, size_x=size_x, size_y=size_y, size_z=size_z, - ordered_items=tip_spots_available, + # ordered_items=tip_spots_available, + ordered_items=tip_spots, category=category, model=model, - with_tips=True, + with_tips=False, ) - + tip_rack.set_tip_state([True]*32 + [False]*32 + [True]*32) # 前32和后32个有枪头,中间32个无枪头 + return tip_rack class WasteTipBoxstate(TypedDict): @@ -629,58 +572,23 @@ class CoincellDeck(Deck): def setup(self) -> None: """设置工作站的标准布局 - 包含子弹夹、料盘、瓶架等完整配置""" # ====================================== 子弹夹 ============================================ - # 铝箔(1个洞位) - lvbo_zip = MagazineHolder_1("铝箔弹夹", 80, 80, 10) - self.assign_child_resource(lvbo_zip, Coordinate(x=2737.0, y=301.0, z=0)) # 正极片(4个洞位,2x2布局) - zhengji_zip = MagazineHolder_4("正极弹夹", 80, 80, 10) + zhengji_zip = MagazineHolder_4_Cathode("正极&铝箔弹夹") self.assign_child_resource(zhengji_zip, Coordinate(x=2799.0, y=356.0, z=0)) - # 正极壳(4个洞位,2x2布局) - zhengjike_zip = MagazineHolder_4("正极壳弹夹", 80, 80, 10) + # 正极壳、平垫片(6个洞位,2x2+2布局) + zhengjike_zip = MagazineHolder_6_Cathode("正极壳&平垫片弹夹") self.assign_child_resource(zhengjike_zip, Coordinate(x=2586.0, y=1143.0, z=0)) - # 垫片(2个洞位,1x2布局) - danpian_zip = MagazineHolder_2("垫片弹夹", 80, 80, 10) - self.assign_child_resource(danpian_zip, Coordinate(x=2690.0, y=1141.0, z=0)) - - # 负极壳(4个洞位,2x2布局) - fujike_zip = MagazineHolder_4("负极壳弹夹", 80, 80, 10) + # 负极壳、弹垫片(6个洞位,2x2+2布局) + fujike_zip = MagazineHolder_6_Anode("负极壳&弹垫片弹夹") self.assign_child_resource(fujike_zip, Coordinate(x=2492.0, y=1144.0, z=0)) - # 弹片(2个洞位,1x2布局) - tanpian_zip = MagazineHolder_2("弹片弹夹", 80, 80, 10) - self.assign_child_resource(tanpian_zip, Coordinate(x=2492.0, y=1139.0, z=0)) - # 成品弹夹(6个洞位,3x2布局) - chengpindanjia_zip = MagazineHolder_6("成品弹夹", 80, 80, 10) + chengpindanjia_zip = MagazineHolder_6_Battery("成品弹夹") self.assign_child_resource(chengpindanjia_zip, Coordinate(x=3112.0, y=1295.0, z=0)) - # 为子弹夹添加极片 - for i in range(1): # MagazineHolder_1 有1个洞位 - lvbo = ElectrodeSheet(name=f"铝箔_{i}", size_x=12, size_y=12, size_z=0.1) - lvbo_zip.children[i].assign_child_resource(lvbo, location=None) - for i in range(4): # MagazineHolder_4 有4个洞位 - zhengji = ElectrodeSheet(name=f"正极_{i}", size_x=12, size_y=12, size_z=0.1) - zhengji_zip.children[i].assign_child_resource(zhengji, location=None) - for i in range(4): # MagazineHolder_4 有4个洞位 - zhengjike = ElectrodeSheet(name=f"正极壳_{i}", size_x=12, size_y=12, size_z=0.1) - zhengjike_zip.children[i].assign_child_resource(zhengjike, location=None) - for i in range(2): # MagazineHolder_2 有2个洞位 - danpian = ElectrodeSheet(name=f"垫片_{i}", size_x=12, size_y=12, size_z=0.1) - danpian_zip.children[i].assign_child_resource(danpian, location=None) - for i in range(4): # MagazineHolder_4 有4个洞位 - fujike = ElectrodeSheet(name=f"负极壳_{i}", size_x=12, size_y=12, size_z=0.1) - fujike_zip.children[i].assign_child_resource(fujike, location=None) - for i in range(2): # MagazineHolder_2 有2个洞位 - tanpian = ElectrodeSheet(name=f"弹片_{i}", size_x=12, size_y=12, size_z=0.1) - tanpian_zip.children[i].assign_child_resource(tanpian, location=None) - # for i in range(6): # MagazineHolder_6 有6个洞位 - # chengpindanjia = ElectrodeSheet(name=f"成品弹夹_{i}", size_x=12, size_y=12, size_z=0.1) - # chengpindanjia_zip.children[i].assign_child_resource(chengpindanjia, location=None) - - # ====================================== 物料板 ============================================ # 创建物料板(料盘carrier)- 4x4布局 # 负极料盘 @@ -699,7 +607,7 @@ class CoincellDeck(Deck): # ====================================== 瓶架、移液枪 ============================================ # 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒 - # 奔耀上料5ml分液瓶小板 - 由奔曜跨站转运而来,不单独写 + # 奔耀上料5ml分液瓶小板 - 由奔曜跨站转运而来,不单独写,但是这里应该有一个堆栈用于摆放分液瓶小板 # bottle_rack_3x4 = BottleRack( # name="bottle_rack_3x4", diff --git a/unilabos/resources/battery/electrode_sheet.py b/unilabos/resources/battery/electrode_sheet.py new file mode 100644 index 0000000..e86af24 --- /dev/null +++ b/unilabos/resources/battery/electrode_sheet.py @@ -0,0 +1,179 @@ +from typing import Any, Dict, Optional, TypedDict + +from pylabrobot.resources import Resource as ResourcePLR +from pylabrobot.resources import Container + + +electrode_colors = { + "PositiveCan": "#ff0000", + "PositiveElectrode": "#cc3333", + "NegativeCan": "#000000", + "NegativeElectrode": "#666666", + "SpringWasher": "#8b7355", + "FlatWasher": "a9a9a9", + "AluminumFoil": "#ffcccc", + "Battery": "#00ff00", +} + +class ElectrodeSheetState(TypedDict): + mass: float # 质量 (g) + material_type: str # 材料类型(铜、铝、不锈钢、弹簧钢等) + color: str # 材料类型对应的颜色 + + +class ElectrodeSheet(ResourcePLR): + """极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料""" + + def __init__( + self, + name: str = "极片", + size_x=10, + size_y=10, + size_z=10, + category: str = "electrode_sheet", + model: Optional[str] = None, + ): + """初始化极片 + + Args: + name: 极片名称 + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + category=category, + model=model, + ) + self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState( + diameter=14, + thickness=0.1, + mass=0.5, + material_type="copper", + info=None + ) + + # TODO: 这个还要不要?给self._unilabos_state赋值的? + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + #序列化 + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data + + +def PositiveCan(name: str) -> ElectrodeSheet: + """创建正极壳""" + sheet = ElectrodeSheet(name=name, size_x=12, size_y=12, size_z=3.0, model="PositiveCan") + sheet.load_state({"material_type": "aluminum", "color": electrode_colors["PositiveCan"]}) + return sheet + + +def PositiveElectrode(name: str) -> ElectrodeSheet: + """创建正极片""" + sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.1, model="PositiveElectrode") + sheet.load_state({"material_type": "positive_electrode", "color": electrode_colors["PositiveElectrode"]}) + return sheet + + +def NegativeCan(name: str) -> ElectrodeSheet: + """创建负极壳""" + sheet = ElectrodeSheet(name=name, size_x=12, size_y=12, size_z=2.0, model="NegativeCan") + sheet.load_state({"material_type": "steel", "color": electrode_colors["NegativeCan"]}) + return sheet + + +def NegativeElectrode(name: str) -> ElectrodeSheet: + """创建负极片""" + sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.1, model="NegativeElectrode") + sheet.load_state({"material_type": "negative_electrode", "color": electrode_colors["NegativeElectrode"]}) + return sheet + + +def SpringWasher(name: str) -> ElectrodeSheet: + """创建弹片""" + sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.5, model="SpringWasher") + sheet.load_state({"material_type": "spring_steel", "color": electrode_colors["SpringWasher"]}) + return sheet + + +def FlatWasher(name: str) -> ElectrodeSheet: + """创建垫片""" + sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.2, model="FlatWasher") + sheet.load_state({"material_type": "steel", "color": electrode_colors["FlatWasher"]}) + return sheet + + +def AluminumFoil(name: str) -> ElectrodeSheet: + """创建铝箔""" + sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.05, model="AluminumFoil") + sheet.load_state({"material_type": "aluminum", "color": electrode_colors["AluminumFoil"]}) + return sheet + + +class BatteryState(TypedDict): + color: str # 材料类型对应的颜色 + electrolyte_name: str + data_electrolyte_code: str + open_circuit_voltage: float + assembly_pressure: float + electrolyte_volume: float + + info: Optional[str] # 附加信息 + + +class Battery(Container): + """电池类 - 包含组装好的电池""" + + def __init__( + self, + name: str = "电池", + size_x=12, + size_y=12, + size_z=6, + category: str = "battery", + model: Optional[str] = None, + ): + """初始化电池 + + Args: + name: 电池名称 + category: 类别 + model: 型号 + """ + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + category=category, + model=model, + ) + self._unilabos_state: BatteryState = BatteryState( + color=electrode_colors["Battery"], + electrolyte_name="无", + data_electrolyte_code="", + open_circuit_voltage=0.0, + assembly_pressure=0.0, + electrolyte_volume=0.0, + info=None + ) + + def load_state(self, state: Dict[str, Any]) -> None: + """格式不变""" + super().load_state(state) + self._unilabos_state = state + + #序列化 + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + """格式不变""" + data = super().serialize_state() + data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等) + return data \ No newline at end of file diff --git a/unilabos/resources/battery/magazine.py b/unilabos/resources/battery/magazine.py index a5a15cc..f8d2444 100644 --- a/unilabos/resources/battery/magazine.py +++ b/unilabos/resources/battery/magazine.py @@ -1,10 +1,18 @@ -from typing import Dict, List, Optional, OrderedDict, Union +from typing import Dict, List, Optional, OrderedDict, Union, Callable import math from pylabrobot.resources.coordinate import Coordinate from pylabrobot.resources import Resource, ResourceStack, ItemizedResource from pylabrobot.resources.carrier import create_homogeneous_resources +from unilabos.resources.battery.electrode_sheet import ( + PositiveCan, PositiveElectrode, + NegativeCan, NegativeElectrode, + SpringWasher, FlatWasher, + AluminumFoil, + Battery +) + class Magazine(ResourceStack): """子弹夹洞位类""" @@ -32,6 +40,18 @@ class Magazine(ResourceStack): ) self.max_sheets = max_sheets + @property + def size_x(self) -> float: + return self.get_size_x() + + @property + def size_y(self) -> float: + return self.get_size_y() + + @property + def size_z(self) -> float: + return self.get_size_z() + class MagazineHolder(ItemizedResource): """子弹夹类 - 有多个洞位,每个洞位放多个极片""" @@ -98,6 +118,7 @@ def magazine_factory( size_y: float, size_z: float, locations: List[Coordinate], + klasses: Optional[List[Callable[[str], str]]] = None, hole_diameter: float = 14.0, hole_depth: float = 10.0, max_sheets_per_hole: int = 100, @@ -112,12 +133,17 @@ def magazine_factory( size_y: 宽度 (mm) size_z: 高度 (mm) locations: 洞位坐标列表 + klasses: 每个洞位中极片的类列表 hole_diameter: 洞直径 (mm) hole_depth: 洞深度 (mm) max_sheets_per_hole: 每个洞位最大极片数量 category: 类别 model: 型号 """ + for loc in locations: + loc.x -= hole_diameter / 2 + loc.y -= hole_diameter / 2 + # 创建洞位 _sites = create_homogeneous_resources( klass=Magazine, @@ -132,7 +158,7 @@ def magazine_factory( keys = [f"A{i+1}" for i in range(len(locations))] sites = dict(zip(keys, _sites.values())) - return MagazineHolder( + holder = MagazineHolder( name=name, size_x=size_x, size_y=size_y, @@ -145,18 +171,143 @@ def magazine_factory( model=model, ) + if klasses is not None: + for i, klass in enumerate(klasses): + hole_key = keys[i] + hole = holder.children[i] + for j in reversed(range(max_sheets_per_hole)): + item_name = f"{hole_key}_sheet{j+1}" + item = klass(name=item_name) + hole.assign_child_resource(item) + return holder -def MagazineHolder_4( + +def MagazineHolder_6_Cathode( name: str, size_x: float = 80.0, size_y: float = 80.0, - size_z: float = 10.0, + size_z: float = 40.0, hole_diameter: float = 14.0, hole_depth: float = 10.0, - hole_spacing: float = 25.0, + hole_spacing: float = 20.0, max_sheets_per_hole: int = 100, +) -> MagazineHolder: + """创建6孔子弹夹 - 六边形排布""" + center_x = size_x / 2 + center_y = size_y / 2 + + locations = [] + + # 周围6个孔,按六边形排布 + for i in range(6): + angle = i * 60 * math.pi / 180 # 每60度一个孔 + x = center_x + hole_spacing * math.cos(angle) + y = center_y + hole_spacing * math.sin(angle) + locations.append(Coordinate(x, y, size_z - hole_depth)) + + return magazine_factory( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + locations=locations, + klasses=[FlatWasher, PositiveCan, PositiveCan, FlatWasher, PositiveCan, PositiveCan], + hole_diameter=hole_diameter, + hole_depth=hole_depth, + max_sheets_per_hole=max_sheets_per_hole, + category="magazine_holder", + model="MagazineHolder_6_Cathode", + ) + + +def MagazineHolder_6_Anode( + name: str, + size_x: float = 80.0, + size_y: float = 80.0, + size_z: float = 40.0, + hole_diameter: float = 14.0, + hole_depth: float = 10.0, + hole_spacing: float = 20.0, + max_sheets_per_hole: int = 100, +) -> MagazineHolder: + """创建6孔子弹夹 - 六边形排布""" + center_x = size_x / 2 + center_y = size_y / 2 + + locations = [] + + # 周围6个孔,按六边形排布 + for i in range(6): + angle = i * 60 * math.pi / 180 # 每60度一个孔 + x = center_x + hole_spacing * math.cos(angle) + y = center_y + hole_spacing * math.sin(angle) + locations.append(Coordinate(x, y, size_z - hole_depth)) + + return magazine_factory( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + locations=locations, + klasses=[SpringWasher, NegativeCan, NegativeCan, SpringWasher, NegativeCan, NegativeCan], + hole_diameter=hole_diameter, + hole_depth=hole_depth, + max_sheets_per_hole=max_sheets_per_hole, + category="magazine_holder", + model="MagazineHolder_6_Anode", + ) + + +def MagazineHolder_6_Battery( + name: str, + size_x: float = 80.0, + size_y: float = 80.0, + size_z: float = 40.0, + hole_diameter: float = 14.0, + hole_depth: float = 10.0, + hole_spacing: float = 20.0, + max_sheets_per_hole: int = 100, +) -> MagazineHolder: + """创建6孔子弹夹 - 六边形排布""" + center_x = size_x / 2 + center_y = size_y / 2 + + locations = [] + + # 周围6个孔,按六边形排布 + for i in range(6): + angle = i * 60 * math.pi / 180 # 每60度一个孔 + x = center_x + hole_spacing * math.cos(angle) + y = center_y + hole_spacing * math.sin(angle) + locations.append(Coordinate(x, y, size_z - hole_depth)) + + return magazine_factory( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + locations=locations, + klasses=None, # 初始化时,不放入装好的电池 + hole_diameter=hole_diameter, + hole_depth=hole_depth, + max_sheets_per_hole=max_sheets_per_hole, + category="magazine_holder", + model="MagazineHolder_6_Battery", + ) + + +def MagazineHolder_4_Cathode( + name: str, ) -> MagazineHolder: """创建4孔子弹夹 - 正方形四角排布""" + size_x: float = 80.0 + size_y: float = 80.0 + size_z: float = 10.0 + hole_diameter: float = 14.0 + hole_depth: float = 10.0 + hole_spacing: float = 25.0 + max_sheets_per_hole: int = 100 + # 计算4个洞位的坐标(正方形四角排布) center_x = size_x / 2 center_y = size_y / 2 @@ -175,110 +326,10 @@ def MagazineHolder_4( size_y=size_y, size_z=size_z, locations=locations, + klasses=[AluminumFoil, PositiveElectrode, PositiveElectrode, PositiveElectrode], hole_diameter=hole_diameter, hole_depth=hole_depth, max_sheets_per_hole=max_sheets_per_hole, - category="clip_magazine_four", + category="magazine_holder", + model="MagazineHolder_4_Cathode", ) - - -def MagazineHolder_2( - name: str, - size_x: float = 80.0, - size_y: float = 80.0, - size_z: float = 10.0, - hole_diameter: float = 14.0, - hole_depth: float = 10.0, - hole_spacing: float = 25.0, - max_sheets_per_hole: int = 100, -) -> MagazineHolder: - """创建2孔子弹夹 - 竖向排布""" - # 计算2个洞位的坐标(竖向排布) - center_x = size_x / 2 - center_y = size_y / 2 - offset = hole_spacing / 2 - - locations = [ - Coordinate(center_x, center_y - offset, size_z - hole_depth), # 下方 - Coordinate(center_x, center_y + offset, size_z - hole_depth), # 上方 - ] - - return magazine_factory( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - locations=locations, - hole_diameter=hole_diameter, - hole_depth=hole_depth, - max_sheets_per_hole=max_sheets_per_hole, - category="clip_magazine_two", - ) - - -def MagazineHolder_1( - name: str, - size_x: float = 80.0, - size_y: float = 80.0, - size_z: float = 10.0, - hole_diameter: float = 14.0, - hole_depth: float = 10.0, - max_sheets_per_hole: int = 100, -) -> MagazineHolder: - """创建1孔子弹夹 - 中心单孔""" - # 计算1个洞位的坐标(中心位置) - center_x = size_x / 2 - center_y = size_y / 2 - - locations = [ - Coordinate(center_x, center_y, size_z - hole_depth), # 中心 - ] - - return magazine_factory( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - locations=locations, - hole_diameter=hole_diameter, - hole_depth=hole_depth, - max_sheets_per_hole=max_sheets_per_hole, - category="clip_magazine_one", - ) - - -def MagazineHolder_6( - name: str, - size_x: float = 80.0, - size_y: float = 80.0, - size_z: float = 40.0, - hole_diameter: float = 14.0, - hole_depth: float = 10.0, - hole_spacing: float = 20.0, - max_sheets_per_hole: int = 100, -) -> MagazineHolder: - """创建6孔子弹夹 - 六边形排布""" - # 计算6个洞位的坐标(六边形排布:中心1个,周围5个) - center_x = size_x / 2 - center_y = size_y / 2 - - locations = [] - - # 周围6个孔,按六边形排布 - for i in range(6): - angle = i * 60 * math.pi / 180 # 每60度一个孔 - x = center_x + hole_spacing * math.cos(angle) - y = center_y + hole_spacing * math.sin(angle) - locations.append(Coordinate(x, y, size_z - hole_depth)) - - return magazine_factory( - name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, - locations=locations, - hole_diameter=hole_diameter, - hole_depth=hole_depth, - max_sheets_per_hole=max_sheets_per_hole, - category="clip_magazine_six", - ) \ No newline at end of file From 5399c6c1cf36e446ad1789aa41482c38bbb12898 Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Sun, 9 Nov 2025 15:13:20 +0800 Subject: [PATCH 088/104] =?UTF-8?q?Fix=20warehouse=20mapping:=20use=20actu?= =?UTF-8?q?al=20parent=20warehouse=20name=20instead=20of=20hardcoded=20'?= =?UTF-8?q?=E6=89=8B=E5=8A=A8=E5=A0=86=E6=A0=88'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bioyond_cell/bioyond_cell_workstation.py | 31 +++++++++++++++---- .../workstation/bioyond_studio/config.py | 4 +-- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index 4b27572..48780a5 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -1075,7 +1075,12 @@ class BioyondCellWorkstation(BioyondWorkstation): if bottle_moudle == moudle_name: bottle_type = key break - self.create_sample(plr_resource.name, board_type,bottle_type,site) + + # 从 parent_resource 获取仓库名称 + warehouse_name = parent_resource.name if parent_resource else "手动堆栈" + logger.info(f"拖拽上料: {plr_resource.name} -> {warehouse_name} / {site}") + + self.create_sample(plr_resource.name, board_type, bottle_type, site, warehouse_name) return self.lab_logger().warning(f"无库位的上料,不处理,{plr_resource} 挂载到 {parent_resource}") @@ -1084,17 +1089,31 @@ class BioyondCellWorkstation(BioyondWorkstation): name: str, board_type: str, bottle_type: str, - location_code: str + location_code: str, + warehouse_name: str = "手动堆栈" ) -> Dict[str, Any]: """创建配液板物料并自动入库。 Args: - material_name: 物料名称,支持 "5ml分液瓶板"/"5ml分液瓶"、"配液瓶(小)板"/"配液瓶(小)"。 - quantity: 主物料与明细的数量,默认 1。 - location_code: 库位编号,例如 "A01",将自动映射为 "手动堆栈" 下的 UUID。 + name: 物料名称 + board_type: 板类型,如 "5ml分液瓶板"、"配液瓶(小)板" + bottle_type: 瓶类型,如 "5ml分液瓶"、"配液瓶(小)" + location_code: 库位编号,例如 "A01" + warehouse_name: 仓库名称,默认为 "手动堆栈",支持 "自动堆栈-左"、"自动堆栈-右" 等 """ carrier_type_id = MATERIAL_TYPE_MAPPINGS[board_type][1] bottle_type_id = MATERIAL_TYPE_MAPPINGS[bottle_type][1] - location_id = WAREHOUSE_MAPPING["手动堆栈"]["site_uuids"][location_code] + + # 从指定仓库获取库位UUID + if warehouse_name not in WAREHOUSE_MAPPING: + logger.error(f"未找到仓库: {warehouse_name},回退到手动堆栈") + warehouse_name = "手动堆栈" + + if location_code not in WAREHOUSE_MAPPING[warehouse_name]["site_uuids"]: + logger.error(f"仓库 {warehouse_name} 中未找到库位 {location_code}") + raise ValueError(f"库位 {location_code} 在仓库 {warehouse_name} 中不存在") + + location_id = WAREHOUSE_MAPPING[warehouse_name]["site_uuids"][location_code] + logger.info(f"创建样品入库: {name} -> {warehouse_name}/{location_code} (UUID: {location_id})") # 新建小瓶 details = [] diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index c416ab8..9606c78 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -8,8 +8,8 @@ import os # BioyondCellWorkstation 默认配置(包含所有必需参数) API_CONFIG = { # API 连接配置 - # "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.1.143:44389"),#实机 - "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.7.149:44388"),# 仿真机 + "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.1.143:44389"),#实机 + # "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.7.149:44388"),# 仿真机 "api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"), "timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")), From 538891fcbea5e02335ab3a9753e673170c3b24e4 Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Sun, 9 Nov 2025 17:04:52 +0800 Subject: [PATCH 089/104] Update YB resources: add YB_ prefix to models and update deck configurations --- .../bioyond_cell/2025092701.xlsx | Bin 18162 -> 17833 bytes .../bioyond_cell/bioyond_cell_workstation.py | 3 +- .../workstation/bioyond_studio/config.py | 2 +- .../coin_cell_assembly/coin_cell_assembly.py | 2 +- .../registry/resources/bioyond/YB_bottle.yaml | 26 ++++++++ .../resources/bioyond/YB_bottle_carriers.yaml | 58 +++++++++--------- .../resources/bioyond/YB_bottle_carriers.py | 4 +- unilabos/resources/bioyond/YB_bottles.py | 2 +- unilabos/resources/bioyond/decks.py | 12 ++-- 9 files changed, 66 insertions(+), 43 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx index 9d0696191d2c76ff1788e485ed89d498ebf0f503..5db039c6203fca0056bb6dfb2af945f2ad81d930 100644 GIT binary patch delta 135 zcmey=%eblRTHF>-0OGcy33)P}n84Wl8(Pm^~G@ATR z*Jkp29sbFldJ2;_>3aj^|LL2Hav^k2ws#FR)60|-knFfdGZbdZ?5UG*iS@#ckUQLI4fk2WLI*?7>y@e>*)y<<(DfI*(w<;+1Vt67>>r1gSC~ljg=HiY?X>jih$x}b}j)%21cd^ zmPY0V21YjeWp+0D$x!vq#*-InD+3KHR2QCnLt9h0G{+y&Hw-a diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index 5ca2bb0..fbedd26 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -595,7 +595,8 @@ class BioyondCellWorkstation(BioyondWorkstation): print(f"[create_orders] ⚠️ 第 {idx+1} 行未找到有效物料") orders.append(order_data) - + print("================================================") + print("orders:", orders) print(f"[create_orders] 即将提交订单数量: {len(orders)}") response = self._post_lims("/api/lims/order/orders", orders) diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index 9606c78..b2aed01 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -237,7 +237,7 @@ MATERIAL_TYPE_MAPPINGS = { "100ml液体": ("YB_100ml_yeti", "d37166b3-ecaa-481e-bd84-3032b795ba07"), "液": ("YB_ye", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), "高粘液": ("YB_gaonianye", "abe8df30-563d-43d2-85e0-cabec59ddc16"), - "加样头(大)": ("YB_jia_yang_tou_da", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), + "加样头(大)": ("YB_jia_yang_tou_da_Carrier", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), # "加样头(大)板": ("YB_jia_yang_tou_da", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"), "5ml分液瓶板": ("YB_5ml_fenyepingban", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"), "5ml分液瓶": ("YB_5ml_fenyeping", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"), diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index 8c2cf5f..bd30de0 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -112,7 +112,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): def __init__(self, config: dict = None, deck=None, - address: str = "172.21.33.176", + address: str = "172.16.28.102", port: str = "502", debug_mode: bool = False, *args, diff --git a/unilabos/registry/resources/bioyond/YB_bottle.yaml b/unilabos/registry/resources/bioyond/YB_bottle.yaml index 7cafab4..87a335b 100644 --- a/unilabos/registry/resources/bioyond/YB_bottle.yaml +++ b/unilabos/registry/resources/bioyond/YB_bottle.yaml @@ -63,3 +63,29 @@ YB_pei_ye_xiao_Bottle: init_param_schema: {} registry_type: resource version: 1.0.0 +YB_jia_yang_tou_da: + category: + - yb3 + - YB_bottle + class: + module: unilabos.resources.bioyond.YB_bottles:YB_jia_yang_tou_da + type: pylabrobot + description: YB_jia_yang_tou_da + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 +YB_ye_Bottle: + category: + - yb3 + - YB_bottle_carriers + class: + module: unilabos.resources.bioyond.YB_bottles:YB_ye_Bottle + type: pylabrobot + description: YB_ye_Bottle + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 \ No newline at end of file diff --git a/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml b/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml index a387ca6..2c0c6f0 100644 --- a/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml +++ b/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml @@ -11,19 +11,19 @@ YB_100ml_yeti: init_param_schema: {} registry_type: resource version: 1.0.0 -YB_1BottleCarrier: - category: - - yb3 - - YB_bottle_carriers - class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_1BottleCarrier - type: pylabrobot - description: YB_1BottleCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 +# YB_1BottleCarrier: +# category: +# - yb3 +# - YB_bottle_carriers +# class: +# module: unilabos.resources.bioyond.YB_bottle_carriers:YB_1BottleCarrier +# type: pylabrobot +# description: YB_1BottleCarrier +# handles: [] +# icon: '' +# init_param_schema: {} +# registry_type: resource +# version: 1.0.0 YB_gaonianye: category: - yb3 @@ -154,19 +154,19 @@ YB_gao_nian_ye_Bottle: init_param_schema: {} registry_type: resource version: 1.0.0 -YB_jia_yang_tou_da: - category: - - yb3 - - YB_bottle_carriers - class: - module: unilabos.resources.bioyond.YB_bottles:YB_jia_yang_tou_da - type: pylabrobot - description: YB_jia_yang_tou_da - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 +# YB_jia_yang_tou_da: +# category: +# - yb3 +# - YB_bottle_carriers +# class: +# module: unilabos.resources.bioyond.YB_bottles:YB_jia_yang_tou_da +# type: pylabrobot +# description: YB_jia_yang_tou_da +# handles: [] +# icon: '' +# init_param_schema: {} +# registry_type: resource +# version: 1.0.0 YB_jia_yang_tou_da_Carrier: category: - yb3 @@ -193,14 +193,14 @@ YB_ye_100ml_Bottle: init_param_schema: {} registry_type: resource version: 1.0.0 -YB_ye_Bottle: +YB_ye: category: - yb3 - YB_bottle_carriers class: - module: unilabos.resources.bioyond.YB_bottles:YB_ye_Bottle + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_ye type: pylabrobot - description: YB_ye_Bottle + description: YB_ye_Bottle_Carrier handles: [] icon: '' init_param_schema: {} diff --git a/unilabos/resources/bioyond/YB_bottle_carriers.py b/unilabos/resources/bioyond/YB_bottle_carriers.py index 28c8fcc..29a5324 100644 --- a/unilabos/resources/bioyond/YB_bottle_carriers.py +++ b/unilabos/resources/bioyond/YB_bottle_carriers.py @@ -206,7 +206,7 @@ def YB_6VialCarrier(name: str) -> BottleCarrier: return carrier # 1瓶载架 - 单个中央位置 -def YB_1BottleCarrier(name: str) -> BottleCarrier: +def YB_ye(name: str) -> BottleCarrier: # 载架尺寸 (mm) carrier_size_x = 127.8 @@ -233,7 +233,7 @@ def YB_1BottleCarrier(name: str) -> BottleCarrier: resource_size_y=beaker_diameter, name_prefix=name, ), - model="YB_1BottleCarrier", + model="YB_ye", ) carrier.num_items_x = 1 carrier.num_items_y = 1 diff --git a/unilabos/resources/bioyond/YB_bottles.py b/unilabos/resources/bioyond/YB_bottles.py index c8f1c95..acbbf35 100644 --- a/unilabos/resources/bioyond/YB_bottles.py +++ b/unilabos/resources/bioyond/YB_bottles.py @@ -33,7 +33,7 @@ def YB_ye_Bottle( height=height, max_volume=max_volume, barcode=barcode, - model="Liquid_Bottle", + model="YB_ye_Bottle", ) """100ml液体""" diff --git a/unilabos/resources/bioyond/decks.py b/unilabos/resources/bioyond/decks.py index e9d7593..98970a1 100644 --- a/unilabos/resources/bioyond/decks.py +++ b/unilabos/resources/bioyond/decks.py @@ -89,11 +89,9 @@ class BIOYOND_YB_Deck(Deck): "自动堆栈-右": bioyond_warehouse_2x2x1("自动堆栈-右"), "手动堆栈-左": bioyond_warehouse_3x5x1("手动堆栈-左"), "手动堆栈-右": bioyond_warehouse_3x5x1("手动堆栈-右"), - "粉末加样头堆栈-左": bioyond_warehouse_10x1x1("粉末加样头堆栈-左"), - "粉末加样头堆栈-右": bioyond_warehouse_10x1x1("粉末加样头堆栈-右"), + "粉末加样头堆栈": bioyond_warehouse_20x1x1("粉末加样头堆栈"), "配液站内试剂仓库": bioyond_warehouse_3x3x1("配液站内试剂仓库"), - "试剂替换仓库-左": bioyond_warehouse_5x1x1("试剂替换仓库-左"), - "试剂替换仓库-右": bioyond_warehouse_5x1x1("试剂替换仓库-右"), + "试剂替换仓库": bioyond_warehouse_10x1x1("试剂替换仓库"), } # warehouse 的位置 self.warehouse_locations = { @@ -101,11 +99,9 @@ class BIOYOND_YB_Deck(Deck): "自动堆栈-右": Coordinate(4160.0, 158.0, 0.0), "手动堆栈-左": Coordinate(-400.0, 877.0, 0.0), "手动堆栈-右": Coordinate(4160.0, 877.0, 0.0), - "粉末加样头堆栈-左": Coordinate(415.0, 1301.0, 0.0), - "粉末加样头堆栈-右": Coordinate(2200.0, 1304.0, 0.0), + "粉末加样头堆栈": Coordinate(415.0, 1301.0, 0.0), "配液站内试剂仓库": Coordinate(2162.0, 337.0, 0.0), - "试剂替换仓库-左": Coordinate(1173.0, 702.0, 0.0), - "试剂替换仓库-右": Coordinate(2721.0, 739.0, 0.0), + "试剂替换仓库": Coordinate(1173.0, 702.0, 0.0), } for warehouse_name, warehouse in self.warehouses.items(): From beb90f20d20f64ede05beb37bccb9c1422c1be63 Mon Sep 17 00:00:00 2001 From: calvincao Date: Sun, 9 Nov 2025 20:56:12 +0800 Subject: [PATCH 090/104] Update coin cell assembly configuration: change CSV file reference and modify resource names; enhance workstation initialization and packing functions. --- .../coin_cell_assembly/coin_cell_assembly.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index bd30de0..cf775cd 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -139,7 +139,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): time.sleep(2) if not modbus_client.client.is_socket_open(): raise ValueError('modbus tcp connection failed') - self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_a.csv')) + self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_1105.csv')) self.client = modbus_client.register_node_list(self.nodes) else: print("测试模式,跳过连接") @@ -791,7 +791,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): logger.debug(f"data_electrolyte_code: {data_electrolyte_code}") logger.debug(f"data_coin_cell_code: {data_coin_cell_code}") #接收完信息后,读取完毕标志位置True - liaopan3 = self.deck.get_resource("\u7535\u6c60\u6599\u76d8") + liaopan3 = self.deck.get_resource("chengpindanjia") #把物料解绑后放到另一盘上 battery = ElectrodeSheet(name=f"battery_{self.coin_num_N}", size_x=14, size_y=14, size_z=2) battery._unilabos_state = { @@ -1206,7 +1206,13 @@ class CoinCellAssemblyWorkstation(WorkstationBase): if __name__ == "__main__": # 简单测试 - workstation = CoinCellAssemblyWorkstation() - workstation.qiming_coin_cell_code(fujipian_panshu=1, fujipian_juzhendianwei=2, gemopanshu=3, gemo_juzhendianwei=4, lvbodian=False, battery_pressure_mode=False, battery_pressure=4200, battery_clean_ignore=False) - print(f"工作站创建成功: {workstation.deck.name}") - print(f"料盘数量: {len(workstation.deck.children)}") + workstation = CoinCellAssemblyWorkstation(deck=CoincellDeck(setup=True, name="coin_cell_deck")) + # workstation.qiming_coin_cell_code(fujipian_panshu=1, fujipian_juzhendianwei=2, gemopanshu=3, gemo_juzhendianwei=4, lvbodian=False, battery_pressure_mode=False, battery_pressure=4200, battery_clean_ignore=False) + # print(f"工作站创建成功: {workstation.deck.name}") + # print(f"料盘数量: {len(workstation.deck.children)}") + workstation.func_pack_device_init() + workstation.func_pack_device_auto() + workstation.func_pack_device_start() + workstation.func_pack_send_bottle_num(16) + workstation.func_allpack_cmd(elec_num=16, elec_use_num=16, elec_vol=50, assembly_type=7, assembly_pressure=4200, file_path="/Users/calvincao/Desktop/work/Uni-Lab-OS-hhm") + \ No newline at end of file From 017eaefb8df0747032e32a487b8a7d5b6aa0abb1 Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Sun, 9 Nov 2025 21:43:32 +0800 Subject: [PATCH 091/104] Update coin cell assembly and YB_YH materials configuration --- .../devices/workstation/coin_cell_assembly/YB_YH_materials.py | 2 +- .../workstation/coin_cell_assembly/coin_cell_assembly.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py index 1e929c8..d2e49b5 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py +++ b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py @@ -20,7 +20,7 @@ from pylabrobot.resources.utils import create_ordered_items_2d from unilabos.resources.battery.magazine import MagazineHolder_4_Cathode, MagazineHolder_6_Cathode, MagazineHolder_6_Anode, MagazineHolder_6_Battery from unilabos.resources.battery.bottle_carriers import YIHUA_Electrolyte_12VialCarrier - +from unilabos.resources.battery.electrodesheet import ElectrodeSheet diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index cf775cd..96f3232 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -791,7 +791,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): logger.debug(f"data_electrolyte_code: {data_electrolyte_code}") logger.debug(f"data_coin_cell_code: {data_coin_cell_code}") #接收完信息后,读取完毕标志位置True - liaopan3 = self.deck.get_resource("chengpindanjia") + liaopan3 = self.deck.get_resource("成品弹夹") #把物料解绑后放到另一盘上 battery = ElectrodeSheet(name=f"battery_{self.coin_num_N}", size_x=14, size_y=14, size_z=2) battery._unilabos_state = { From e32dc55db0065d1d2c4af1e0471a3fc15d334869 Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Sun, 9 Nov 2025 22:02:17 +0800 Subject: [PATCH 092/104] Fix import: change electrodesheet to electrode_sheet --- .../devices/workstation/coin_cell_assembly/YB_YH_materials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py index d2e49b5..a39b817 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py +++ b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py @@ -20,7 +20,7 @@ from pylabrobot.resources.utils import create_ordered_items_2d from unilabos.resources.battery.magazine import MagazineHolder_4_Cathode, MagazineHolder_6_Cathode, MagazineHolder_6_Anode, MagazineHolder_6_Battery from unilabos.resources.battery.bottle_carriers import YIHUA_Electrolyte_12VialCarrier -from unilabos.resources.battery.electrodesheet import ElectrodeSheet +from unilabos.resources.battery.electrode_sheet import ElectrodeSheet From 1ecf89ea27f6e573c574b4b0d768941ff3d0a54a Mon Sep 17 00:00:00 2001 From: calvincao Date: Mon, 10 Nov 2025 13:21:56 +0800 Subject: [PATCH 093/104] =?UTF-8?q?=E4=BF=AE=E6=94=B9excel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bioyond_cell/2025092701.xlsx | Bin 17833 -> 18230 bytes .../bioyond_cell/material_template.xlsx | Bin 22149 -> 10381 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx index 5db039c6203fca0056bb6dfb2af945f2ad81d930..a3b130f62b8a05e3b4bc0d5f37e2535f51bde074 100644 GIT binary patch delta 657 zcmZ44&A6?PaRZkk7r&I)3Kcd61{Jo+B8tE2O_l6y@=9~va!V8-lxK>ql8K>`LRx-N zZeodZeoCsXQoMqeg0haXvO&CpHHaOfi_B9iEX^;m)`d`D6%O$VO8VH$H9|MH9-{00 z{-qEGvOO45$kt*=LIj9%g)zbv^(fxcGeGkpLIA~hdwcxeG(lJZF(1RV;P3;Q?hiHy zLmKK+nDrR)I9;<@K&hTtfZOa+?gI`61`w8DU|^W+=peCqyXsdaX18F^&FeL?nJ3HW z#4s97uGG=ld`L%!aWapd9;5MQYrQg2c sCNY!A-9YNF>EshG=1dkAlNnuYn66n(ws*BxK)CHs;0Nh8ung9R* delta 238 zcmdni$GEbaaRZkkSLhd(DN!s83{fnTMHGKA87ghQrCiS}z#0A1U6zx90fZ$O7#Jp> zbCj6OYHhW7xB6}-<^VUd&6Qf&%uEJ)lb`6uFd9yF)YIYjGg4A0u~jNADN?etDYM%g zuV=+55Q!>iG`U;fW^$Y!|K@A@8KTIxd$=0Pq1y_y7z!Fyp>A03s>O6gZ}M$dYo<=4 q$r5hXOlBsNL*2}oR+~-kbF*Rcw48j#&6eqs)nsmWRW=D{kbVG;R6j`o diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx index 2467ab05e52c39e2aa0b3cabc49d44b7c5754247..979285492476768c4e19bf8069b9e8dbafb83942 100644 GIT binary patch literal 10381 zcma)ibzIcV_CMXJbazR2cXtcYje;yJNP|d=NOyO4*CHVu(%s$CA@y7I-pA+h-tT?= z=8xIkvnM{E_spD`bLOZjK*JzGJhdR@HPNTfe>X(%7juA_DiC1r$f62n!vYf!ezI)> zSF%_kAt2bGARsXQ&Sqk7&+PivHZ`(c9-0j!@VoE#ijAc_CJOZ`df#Bz%8@-GORM=d zyJzg{WA0aoE$%0($EYyv^_44T35(c^MYI5pv&Xv|Zwi{?Dp>{u>lCHg3K3&OokMmr zT>9pe^vf_E-yGXKBr9?Y8&U4aTMtu$lps+!){mGt8g*2sX2WVMnVK`XmFo#)>mEzQ%@shhAHUg`l`j5+P<}kw`3MnkC@*c zn&0odU5{Q-0|16*YMred$$~zUqcwN)XKV&{Ft0ACLb^t(Xl01_1R1OZW_I06w#?fa z9lbx{`n;|nQgw4>G=Tn3bT8#(J($7h3W3qZ{5!g40HDPayq_c06gpY40+wMPCHg#) z^DHzZl0WpP5g{mJEsF!EVUszjM(rM#O7{08Nj*`v7rv)?s53b?DG9f`O(qgVGRh$0 zjmlXh5eSvX?{dj|u1KHOaj0esxz-rRHxX=Fq?mI^(z}qKy zb0d{({6xXCH{bz&w)rTi%)yV~8JDWNb>{0bsVOeZ1p|lan#}VrE)r33B<`W%q%G5zK6`7X1B+JV?3QtK6jY#!+T2` zx{c9qZVWhN4H`KHMbsiL=rqa|%joJ&hY1wS*R5wwct(lq%3_3&r-x3Ck+#~e3?`Ev z7)5^Gq7|rKB!bUR&d0qTwaGn9LTQPwp;HJcP+`!%##@d3CeD4P=8K^lX*v`)34a`@ zkpQ7GIaccZ(w2DNc>DhN%|U{h{5l;9K8=HdF?H7X!0BGZAZDHZ`r;n-4HNblT2LaJU1Ob8fAG|+B*nTdCM78iWZt!AwaUmk< zX5<@Salic-t*EzAUTM3Hw;J(B+da$glT%OEN*7)m z@J&c)4=);M5$C79EOYr05#x7Pz(FzJ!b^oMgi~(l#^_Y_+852^8!4L161f~l{h`UL zJ9*L@=j5_?G3CqX{Ka$1NFWu4F{7g~Gq>Hto~`&F^H_Z!x7%Vl+SA^4hQ;*B+BtCC6x2DuXn34i^jxcFz_^s7G2(Rf<%o zZ|4i6;sNgE)3HkA=QXsS6>s^ z6MgXErtyD_-zp%*Pq?RMM!Bu2DzdF9!gUiRiAFeD#Y&V)hs_X>2jxxE5Mo-%!~CKH ze$hWa(Y4Y3P1Z>NaoKWWu2vjLi6)AmYRy z{bHxk#>U01U~-3TyCu;AL3Iz3&ILV4$el)8XyVOzg2pVEZ~+lBAJj=A6w)fqh^)y& zDAh3{o@0tlkzqOGjC>*5n@E9M_j+xsM6$<(*c{7+6B7FqoZjcoaBd0q4INGQ#2>AQ!uK&$JRt1Z*~8ZGDnBai`=85>@IM5|@IouwIa16VWuV&2k-$%%{~% zah7zTlQ2WDh*HCf#Y?6$blZ3-S>O}Wi4BPzW#$9QDj1%TJUPuCq~`@#WDu*>TbH=g zF~$PQZQoTFJ~u56lFenv>0mX@p~2CbwMj<5kRz(ZD8%D|Vt9^F!SbOMR5v+fS!j+g zHirpYX~{NM4iUK_#|C zj_GGg@MxYfoQ9#oD1BHQ+_zj;U4!wL&CN^Zgqx%7KiY(JZ7gAx704pfrV@S{NM{!G zws4CNVqkqfNhb3L7ovk{i+#}cW~-{?V_`JG*aP4>pca}n_zmlUg%A!H)PCMlrMR~6J*MU zt*erO(kwE%voEy$~V(Gn>HO>Y_*(V>d%gOf1SuLrY>f-8B1PASpj zWi%P72M%+TA$Fp+v&G5f&LpziFmspznwIDVSd)e^HT8Hs8(NQvSmm#^`8`0?T%GL+ zXmX=V9yRD%-nPvAhNmHAo=GAuPKSBBd1kyB@%3(o1|&Nac^1WaktE~Oj4bCg%polq z^LP)gjctX1k1^H9`IX-VZ^o^bbzvre+l&+UG2X3Xk3AFaD&?U&shA+nEUPzHJ?D3# z-?-jqY|A+=Z1i)^Up2lP*(*&D24Tf@`PC017Jq$Od#{FXr@xndR)3v zZ(`k5=u(-ZxjG4-_5`(7hKfp3Oh2N*K~ywXdre(fDFY#7qP#6Raq&V~&@SbfsHmeg zsQSy*7HIjF`? zsJl54RejA=J*+sh$i=)$*=PD=UGM~H)SGGt-bPCqx@No;g zD^Lm3^1+KkGUW6Nxk1*hD=S+WQVcGHt)gx$yBXME|FGI#l&BF3-<30ISL$GqKLUVI z99rj3Rc_W`I>(1VlQ^E3Q=ih^x6{;X9DdgOkTShkHt~WB*J$hux0Pe|b8c+81t2iLEcceet@uqj$f!>+9>ZqD6nt>vq1gv3jK)Pw5Ix zyI!?2P%d(FKY%_8Q2FA1bIl=Q7TlHJRmb>7RdU#c+ESS$#42emfN^E!ynC+3WB zo#i++0A#+hIfk!FuCmDmi~k@NA^W<%=p|2V=<=GbVS_T@NMoX7LP0JaEk!a3aWRAK ztHs_KzWAYiwFi1F@0lA&0?mL53&xUU{nnq4)3E1xfLVIKaF}l5v7U~UFr|kEW}E|Y zA=#K&2k#!r#~9&60jN7B*n65l?@iQO=HzZ>bvpNzaW`Z~L+PVqKWG2-iJ-$#Dh9LV zX!wXMyhDdgzwf|$@pF!^khI91Ug6JZ7p!=Eo1G34I2ADNEhz*mTa+83zkuioO-!3m zYMk;ldN|z-<}I;))TOSzowtLVuL!;g+3qg11R-3p*p3RgMjc=lgxpZ?y$$l7&-8or z3^LRlz+H~FD#dH6S&Ene)y=x~AoDCy_Uou9;WttbE$MYYaUOJJ#Bl^Es}p@4L-5{{ z4@4wd&@ra;gLz4+ihV(m($NYcu%W}DysXik-o}{cqtb5n zv<{j+xA$J4o4_mX-cn3fsma4;6quchRiwYZq>0w5Ra5w;&6Ss!w=<~SP(shW4a{h< zM}{UIpGm8o8l3XoERNJb3N7W%=DzSkQKU0!4vq{gn zo$mZ?tM_JxQ3rZ#n7bpVYwb=qYD2$0)Eq<5K#7rl8b@J7jl+84^APpI~9vV;mTA=Gxh-*f`6pR4XOFnI9UJ zPCHc@ghHoTR;6Iu5JpfuCSYT{;i)m_q{w7|Z|2NutPaKG9L5224y(REKZttKkkP!O*t9Nnc1XjJNJJ*S2)G%i+GA~ms zorMO<>KI+My<7By#u6(^wu`ET^b<&Zf8?kw=2y9t9kV~QYu+{X+GgeZQp1cXT)y%l zwbR4a9WD({@vZGe^{KR=5#>PG4sNx9I#W54)rk#fRpHWzzXi_9*ee@>sg$1Liv`}` zwU!=ModYZN26*uwq&klU2#NQGnYa_e%Rx&aD`$S2=wjMNV+N?~vOKc4HW0TY0@l%T z#Z?oQ0;EH)T6K($rE3$eux96~EY`e~t3D~k2&8I!!j*IGuIQ{K)|2Iuymb25F+wL2 zl+c!dZmNSx!*VLt7Ho;iNjXr*c_^y8CcO3(L}V2*=50!Sn^Al-4^qA2TH#OAnXD-`=J# z-tbyoY-Ad!qS=3Zg(gSFge8qcgHLM{sSow`ZQk~pY~~=9ZuJUAil|INRt}p$wnjhJ z4Ra=9j8#cPNfBpsPo12EGUoHd13v0`^V7;0jn|d_LySXag9p9@h(Xy!Z(qUCs>+MEkxMadzl=is@SM{cFzftJnj<4yxZz7z@kUXG7JX9iB zG$E)0!UR(sOB|)+84T0G^JPfR<)!PJ?5hiM0wc)n`1a#^OwZ(XMJM>XOtfk2p)lJ` z`;nDAB8~bu9xhpB>silHtl-uwdV6J98jA@l{VZ_~l zf)88CiQkh4?S`eoggbXJB857j-55wIblC*tLchp2m;#Mmj zAd?MgQ0Be>h?NN4Bin)#4+M;_FIGFRtzEIjA1{VJw8UbJ6aT1rWu_LG!_Qzdmue7)x9C&_suqtSvl-O?OWu5wN_%J|{P;&AEx;$zU>|&L?p1<-K>gQ3 zcXG3}__@L-_4UH%yRp4lMjk~+tVu(xyONm~N~SK}#EbgWAQzSnrK*qA$=SCRe{ID-PG0)9JK>0aU#H_Dba^oDf{-r z5MO5^xl37ELzw*llKA`;e^v|iN8inhyVgTgfi-kHyxKZC|H$17rJk#jFxNK$Ymn( zDJF+=pf>b-u^QNty$56Cs}g+FO-rE)yV+iWriSMA&-$I8#WT0VTM8)O%uW;Av&_1LO|Y5ap-RtCa_XAinR!U&pLoWr2vbckN)xA!-9yIu=> zy(r>4RBV|=nKlidsF(~0AwDrrnc-_hu`X-L6OenUH`BdtKdzu82!websMgC{nD81I zrcTV5u=*6otvaKPZ>sB(X}n2dF7@r#M8vdLvT^!YG~7&Yxm}r{?~y(-?kIG(bQcYo z<(N#DjwOb2_9x1r8v%E=GSs;=vnM5o4YYB4Ihb}gWHXxhQ&d|OVF;#Q-HxdX+(03t z-qkC{4}V<#QQ}d!rcv?f8ygCo?J2O6TP9&CK)zzakoBcbb*1}2lsyynwP1D}GO{2m zq6vz*fHL>FmKG<2?~7&uR+R9PHjWiZ*)Tu~NoPW@A1&O(=AJ_{Y-!L3H-;Atd03PP zUOh*YOo0Z1&lDVyoi6Nf`-BwjW(=44D~+v}zGj#6=h~}n0xjM(H?`8}hCqI~=xv%3 z1^0X0=%GjL?&gr}ulA$gRpgfw1j(uNG#HXEaIlG+XTux4F3#O|*R!jee`}zzc`*R+ z@ich5uLL_0p^ZAJd#i=9{DyN5Rre*Q+ax_%5)eJ_c)Z&@KZg_%vMvnId&Av$P62=-nTvLHL}W2i5d4rz(97?i5uWHXtR?S?5~ zJ4Bj`I(^w>y^NiuCwFL=LEv2jJEktr=ty+P)L2g3lxkAtw@*p&bM46FiTW%A;vrc& z2=uz>TtJG?3Mp zn1VHkV(DQuUPS#iF2Qiie;+lkk?_zuW0`j&$Q9*;+}CWyH~cQf@;z@adqbf5b)U~G z{vah(YhCQp;cSV70!6fSC*pLUWjqR0mUlgx)^h18)CoO3!-bFwY~Qa@lA@}G^o@FN zxOzz3%cP#SU|vT~emcfML5$%Ur?Iw@HoqE4azv*qam~CoXDwwghTde%g(u5iF^JL))|WfQleSwhX~8x*p%AUztM5}bP0WmN}MwZhE{#$HqG z6O**S2ofZlhQL+q*iv(+6khB**=Gznm3$24I3et@n_}o$Sms5iF%Gyn& zVsqpPiL^E4ik#a_xMDR6-h5!_fwM-;%}ILILGp=Fq2u9JX(S4tOp_j!t-(CR(W2J8 z=v{1$xxMaUhs0G{zcvY6d@PetXk2Wm5GQkF_vE*|<18(y+8oL<&ALn0SK8pNp~Uvk zq~@ukFr1z1wAKZ8k}XxWL8DdRF0A3zzlaht`b?n9iCx$$*PGN=ZZZIQ4x1f}Tc=uF z;#14))gOIix?$Uj)LkCWA~Yi;O0TMm?-RA5+|8;e!_oyCUC+_FZtTqkDgexSyXoW& zF55Tb9_v?2pu1xpdVF6P@=r!0O7(${C@ddLto^0x1iiUV#ZK~`kur}c^m zMdSW(faCN$e2u1i*&lKYsFTX#AFo1HG`jcHI18dM30ffC0=Jf3SiXxS-|d+&z)xa} zKmRf&xEXk|oodx8A-0r^t-3k8 zy4FR)-`}@yxL8GpKO~K1I?0Z90aBG;^WR(_oJKF_W4S5^Qmi&Tu8rnrENom-Y7-4-20cOWxKqft-z@cR4u|>q>R0`H{?OkY~mK@V98XMzdY=}ppilWv$I(krW@*oK!P!G3doxe<=ubLB12>S1+y z5|7XH2|0kU0PmEera)3vOuNC${WQF~@9;5a?N|c^(cw;fofEvzq+1@&mSozKvpw7i z+I-6rTg_TMy<|xfaZ8D;&{1vB!Fzwx)wTQ`qPr)YRNwn_wCgqU5p2}YZZMz|HgIZ2hMD`Q=38CWk{4dR8#i6 zz=&>G%21(JGahe980rG+Fx6f+<$f~%N&oYwg(pKRq;qu4Kt4mrU}{51ZBFQaSxtCr z&z8IowjgEa_luDcNQl?C@fs@d67$C!+@*o!&xepkUN**IDLc~6)s#`|-&d!RAqM4& zc@Ffv{dJurfkgEkD>cel(-N-pP!Gu2ZAZZX;)B}rduWNm+FX#cl18bt65!x)n&6cS z;QjIlwbWiKYNS{?@4RqiZTS^Gn_T(+N&fH(q})`U1iUae4plczzY*;;KgGLKe)n4# z4-nd$PGrhL%nR0AnE9hei2pmO3Qn3r7y&J8|Ge4r;B{_!1pl`TzJf*mi--A%voz|P z{8v_tZgAlM##XD63pVDg6AQ(GlDU^pg5u){&GRVT!&Z=(8skJ0e0bu`R`Rl^c{AZ6 znIT(VSc~sFHD6IcuXC%~v8SufqOWWTXcF2@)4l_ydGFNp*57YSFlLUKD$>AHeS|$o zfIj;5=&q)!Xu1HGR6d`G8KoaL$WGO+|5Um=okUehuxp7S*rOTFfj6M#1^_79A6^kk z%$E4Z@nS>%49N2PQ)lfzQnB}Z-H3A&<}#86o-!4?{NGsqD?$C= z;Ow=X1s;H5Yy!hc0R99=+`rhKu2%mYxcv)Vr8k|d7y(xSOJakqaj9|Y@*B4DTG3|>lGDc4V@2P`OT>jup7kx2J4ekit}t3H+3)BH zUA4Si_z9~tQvh}j$O~drk&X88JMml{&-Jsx?cg*PN!hj6z0kqZ`DnWB>CEhuy#ps# z#oYp_Bdw18^9JgS1JlObHXZifl!B$EaX{f>8nA>pB#CE@7;v2R)Nt z{+X31IC1i*fHD*tR^Sy)YiEughlagL zsEP5aMIqH1^u%Qw=XqS3@9||Cy)l&7htOm>{==Iuk9Qyx=@&L3)RGfRlO;wMI*`il zxr-&1q|x!GY1j$=8Xd0iTzb$|qsKqquAcYn6#8;@?M1t*_jEW3tFV8*JseHw>1?_O^N|HWegvL=sYP34- z`Ka3=Vry-2&FuK_nERb%`#W#BwACGb&ieRDj17kkgrvaTa0(>y5>*Z?`l1y!b3R@e zCrW^2P>un@N1mt7v!WqBXMa3uDV!`G=PD_XF^p{p<4ZPm6!vGXA#s9K1#TCHDKy@lOSR zChmSKfCOjvzzY5j+y8p#r@EY9?P-LKevR-ys&xK2xBslud6NA#YM=f?_IK5essilK R#fk~8aDxZ)*;75q{XYPVKM?=` literal 22149 zcmeG^TWlo9aa61Xh62M8k^n(~AcF;*ADdk6F3BSqk`B3#yVD&{#F08n3IeBhW_EXu zvoqtFSw8FpkY!ssS&;2WiV{JQa|kTRju1t*6-$tn&-^AI0g^uhiH;-`C(uXyV;~=? z>YkaNs-0avj!57{Cr~@n_2}xV>Z+RQp5D0fk$Zn-BK`NRd%pbk-~8Of1pfUxH14_O z&xW=em3RGcyY2hiQ+s`P3R{*w4OhmF9Y&7tEte`&($WRmAc}fU5E;P5iJvQ;S=Tl)d@d^~Tz({W6@;hTfL?ZpLM z$ni%#+m3;$2)lN?Ttbz|rl>4`DyW-MxmcL$c?uJQl58!Aye-TBkCmZzo-#!7z6%!3 zw4v_DsbJ<@yGL8B+p(}jEVLB;q$QQ3Oq^|HgE;&2$8T-_=r<-N@b8zEvvXCR-OI(I z6bnFx_wyCyz}i zU}}g(YrBF!(SB5SLt{s{;Jc1z`MX>89{95>q8I?nrD^#OJ-D0^x$y|v99>KPOXv~; zYr87@47=^D(@JY*;X)(hFA>~$7_ZrE)|-`#pF|L*2_sdqTT(7k1I{y?B(b^~Fs; zYykoyVvP2T7=my|nBc{A(Qk*2zz)QsEpiamWJW_x*q0HRp%XyFeIO7pBQ(Dc-useMF=kmubU_Q@vDFI?=L@Q{M+|$|M>YoF2;w@ zraL-#{pmY@`&=<1%pi?;_`(}UZ@zK(r(b*jwdV`aS3}Xxqi;EVJ2I}>yT*0D4`Y*x zp_1F}@N^VgJ>d`v(F5KEtebI>(r ztod%ys2lbU#e@kVWU->Ab2i1cY?r9!F#ZS@J5>ZhJ%%)jvL1Zf`^mG7!>9r0zT3xUlUy5VutiABOGa(kdjeqRDfK1?y3GKZ7B_#YA&CDT-Ko&qBIsHs@9g3cYkEz-EmxZ6Q9YiMzZ#fAOTq|Xhb^sTE)&D z#DX4N;!K7P2wpc7lEw)A_%lx2+w2K^fl`r=k=-a^-HnXEhj%v9vbm5qb_%tBte!S! z@i|0Aj!4AozNMOL*qlCYWNf5VUHMF#c2q+KC+)0uAz=z*J4cEpOT=oMnT7E}d*TbHa{MFeRbhh&uKtV7$nH05oaFPEtDZodlY3)wOHsidpb1kSlb(=&& ze-PW@X3yF++U_8{F2MYtQyh=wMDW5wPH4P=w`-_v38Jwzi8T3lS`tsS3pePABwFIz znRLb}bm^*@4Dv1~9*nI}b~(7y4+UrZc?~7+#{bWTg6RO>Ak-|hfC(>mLt)@dSuXiu z-18v{1wF@Xg^>4U>;P|pH?#l-<-NGEYGwCxQD zOVay}VOB{}k7DU2jHE~9#Ek+acH7>u-7SoPa~Ok5Mh{$FMw28ANIdR;^gfwE?Jm|R z`T0g7S(ZB09QdCuT-0QSL9to_{WsNrCZg09EnF16Bu{f>ejo_YRnkm4D@gKNSV+1$ zqX;KSz`0zjBG%A|tFx(aV8!7PLt|FY1n?fn!2d!1b3)U#r5j^H-SV$PY&Br{LFvx` zub#xH1|Ea~QeCy7?oo}(nUIxIZBm?~*3x8s&Q(y>FH<&3b`pD^QrTG%MY|(&@jR8C zXqf0)wT)CYSe8#wXzHZTA<{5%p)0=@<+pEwp&Rhlf8eUssVIOuIS+!3PySo?WNBjnI_Z}QY$2Bi7c^^3>_^atQ9>pP{ITsNmaJYh^xmF zXB4t$2-z92e4$pv0M^Hnz>SY~fUknA8Z}O?R8y{ZH!3@U@Wm=CeDXP|%G876SVNsb zW%1s4l_i=2j`xcMe6tH)n^kLIU5o&R+ey55NWVsWhwf+n)DU9v}*dU9~#nz$wM-( zk}HflEEOa#t2?l8;qI$uNv9lBHK+-S-k^GrKk(K8<3T0>&LV7BS%NGCrM}mK8%%C^ zjVfF&d9b-f4ng4^gAI9WzXFeqvR}!#j1wMREabO*^Ts9_|p{d!3wtA^)Nh(AIbf0wtG)$=N{lwwzTTA5~bozv1jZz%F`vOob?~h>-+%cpGD?ZKMiDC%S|1B-jD0=1)y|p=wTwsDwC>Ml6xsOoj2tgJyK_Kbn1es@oP$(CKLb;Dn z=m5@;!tu1ZZc}koQk7kZH0wIHoiK8v;G#V zj0a!)^TQu}@94Q73}-urSzH4!ZpB4F5zEsWdv#RA@e6Q?1Ux znqQ(DO3j3Aj;C%haAUomIhJI9ks-`gr&!d##4x~}DT~Vt;pnv&51#l+hC{WYvtETJ zCti^-@Q_w0H3K(CQWI&aI`hG0KDhI;gn?Oi4F+cZ)?{E!O{LM9uS!!FuT2=3b=P2E z?lBBJN*bN`sx)))0)>Hxq@mQhJL!K-MyrL47>9F&^9;4{kB=;Wz&hp2v?~dbT)NRdqsv%e~w!y%Ry2-$rn%Z5*`znptJPR@~(@}$gb$3Y= zq0BXyNK@1C9xn3+<=KaUxisU!O$Ht%jgI#<8t-}bVc;QYC^fefqaG!Vj`uYhv3d4k zVAfrOfpvGvQX|3CQaaw(XvF5(hk=Krq0~%uO-8MSrP5@NIcqgq|MTp_z(dkdY6fmH z>QT~U*=Marv(L~IGJ~@@*I;1XUt%i8y*NtNbZCdm{G_TdhIIxX5{Hrx*@}WSb80Ic zv}-hIR~W-OgAa*A$%l+Z!K1{{;kZV_afLCgGdK%L4F=ZzCB|ahM;J@T;~EQ@Z1{Bs z9}Gu<{BwU(9`3sxr5S5I(Be24W{XK)5?Ffh|?lYzN7>f>{@$l-M1GalP&hM^zA z@<;v_m-}F4Z8JuM)d)ZR#nlia`W#zpDd(wxiT?DREmi&gV-sJz^^u7Q{QJ0Cs>*+U zefAZql*_p-Q0=)PEN!wis($)e-joUJsOkiOGGBPFTquW**;5~xtHt*{YxraClk&3m zVWHYFMF`8lrIq^(V<{c0T#wH!{sN3alhw_KuQpaUu9pm0zup`K0c;wytsB`X49SL5 z(+~Egbp!ae+g^2H%N3Yf-rNxGz=qBHa1VPAyK;BG5e2Rjw;Z_*5dg3xRQKJ@p3_l2 z0-Lx*vL>r2WkcQX%dgKRFA+uHH}cZ-?GV5A1xC0uyAGe^<4swPw@d~sn>vGd5ZZ`_ zYG%XK`x2B!+S^DYl>;7(n+TIz&=xcpgV0$neW7i&+w-PnPIg4ao}5{jvnR#;?1jmi zS?koM&FSibS^Gn20L2T4!K{mkc~;RpxJ|-_ZwcZ1+)hK8O%AC8nsG%C;r@JFo7CNx zq5t%D32!p#U}0X04SNm9SP@&U&}V}mGu+U`7z0X8_I}EaHRn{-sKb@}mNGrFN!$L% z-~8#?Jrfi7mrUAp8TIbAxujgqZPK3Gs&XpXMhNrEvaiVl6}Ea#^qJ&L6I_v(tH$Yy zapHpqUwH*yW%{#cjj8kLLfeU%M2yTc-$+GiNn7SXhdH+wZ!dWW#_lpoC)nO_> zBL}EJDXsRnu*i9;!&x#%ESr#dCWCBR=|elxO-T|XJ-H))a7TKg3)I6W73laAU0{OR zo@HC(MS@K!<0Jx)I6sM~%vwKi2v6$6sVrm9HTnC0{qm2h;Bff&OW<^wev`qRJu~Nm zqD+QENwvT==&01uCWX2`_q}bau%QT&EuJ;BvWau}fz73IzH#Xi?6QLo%Qpn1KG+2k zFYds;RoGe$ES+;U<#b?3p(h6r(EZHAag4DP5UmNGz%nu(+0*^qp4$j}qx zUDn2CLG7QO{e$}-h5Uhk_vif!XZYP^5i?jsCc0~tP8Lo0$m*0)C@nOG{Oj%~xPR`URSEedu*hGfMQ!QF1Z20TzW2*855|YF0J8}NhsXVG>Dqx1a7eTd> zjB9sycWSpL#egcVQhs>-s@$|HCLIsY(K2zHAw9t8+y8v?v0we@#037~0Y<-Dhh2Pd zOwMP~WuxD_J5xv=C(q{OcrH`PF^QUpQYG$1x(FXRSdDxt&8#dXyUmRzhIMr&8Q895 zBr3ttESDevrV*ixP)OkgbZtC$X(Zi_XHE5^NqY|}t|>X?r+`3&`%`@Z2Yoa>+#(1l zczRPY|DCaNA%7Z1CshcAFfNZLn*#cVIx1>J2kLQU>XU@C%d z;NfsKY%it=@UuuFlA^%&Vv4TQDWt&mVhX!oOtCkKkYbwMku(S?rnwmukL6}yj~a{w z6^(xkO*}#Y&^}5s2`cJl3{7Bht>vc)LW{KkyEkEO6O-l%3;Z~V5m^4})B8S|OvDG_ zEWyx$505dosUp41E1y&K#%2w9v`;23PSO+fuM&Iyl)v*61S>nq{}?&sZuk5le|IB- zWOhu4`rC)o8F{?g$}lxL1jCxZ*cImPPyx@ z3miOsuaJsRmPs9|hD1V=OYFGDiW zPRU96#(7Qp#ZO4mY@+3l>*QScJ59~yQ@ Date: Mon, 10 Nov 2025 13:27:54 +0800 Subject: [PATCH 094/104] =?UTF-8?q?feat(bioyond=5Fcell):=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=BB=98=E8=AE=A4=E6=A8=A1=E6=9D=BF=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E5=B9=B6=E6=B7=BB=E5=8A=A0=E6=B8=A9=E5=BA=A6=E5=AD=97=E6=AE=B5?= =?UTF-8?q?-=20=E6=9B=B4=E6=96=B0=E4=BA=86=E8=87=AA=E5=8A=A8=E9=80=81?= =?UTF-8?q?=E6=96=99=E5=87=BD=E6=95=B0=E4=B8=AD=E7=9A=84=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=20Excel=20=E6=A8=A1=E6=9D=BF=E8=B7=AF=E5=BE=84-=20=E5=9C=A8?= =?UTF-8?q?=E7=89=A9=E6=96=99=E4=BF=A1=E6=81=AF=E4=B8=AD=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=20temperature=20=E5=AD=97=E6=AE=B5=EF=BC=8C=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=80=BC=E4=B8=BA0=20-=20=E6=9B=B4=E6=96=B0=E4=BA=86=20create?= =?UTF-8?q?=5Forders=20=E5=87=BD=E6=95=B0=E4=B8=AD=E7=9A=84=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=AE=9E=E9=AA=8C=E6=96=87=E4=BB=B6=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=20-=20=E6=B3=A8=E9=87=8A=E6=8E=89=E4=BA=86=E9=83=A8=E5=88=86?= =?UTF-8?q?=E8=B0=83=E8=AF=95=E4=BB=A3=E7=A0=81=EF=BC=8C=E4=BF=9D=E7=95=99?= =?UTF-8?q?=E5=85=B3=E9=94=AE=E7=A4=BA=E4=BE=8B=E5=92=8C=E8=AF=B4=E6=98=8E?= =?UTF-8?q?=20-=20=E6=B7=BB=E5=8A=A0=E4=BA=86=E5=85=B3=E4=BA=8E=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE=E7=A0=81=E3=80=81=E5=AE=9E=E9=AA=8C=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=92=8C=E7=89=A9=E6=96=99=E6=A8=A1=E6=9D=BF=E7=9A=84=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bioyond_cell/bioyond_cell_workstation.py | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index fbedd26..f49eae9 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -256,7 +256,7 @@ class BioyondCellWorkstation(BioyondWorkstation): def auto_feeding4to3( self, # ★ 修改点:默认模板路径 - xlsx_path: Optional[str] = "/Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx", + xlsx_path: Optional[str] = "/Users/calvincao/Desktop/work/uni-lab-all/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx", # ---------------- WH4 - 加样头面 (Z=1, 12个点位) ---------------- WH4_x1_y1_z1_1_materialName: str = "", WH4_x1_y1_z1_1_quantity: float = 0.0, WH4_x2_y1_z1_2_materialName: str = "", WH4_x2_y1_z1_2_quantity: float = 0.0, @@ -323,6 +323,7 @@ class BioyondCellWorkstation(BioyondWorkstation): "posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]), "materialName": str(row[5]).strip(), "quantity": float(row[6]) if pd.notna(row[6]) else 0.0, + "temperature": 0, }) # 四号手套箱原液瓶面 for _, row in df.iloc[14:23, 2:9].iterrows(): @@ -334,6 +335,7 @@ class BioyondCellWorkstation(BioyondWorkstation): "quantity": float(row[6]) if pd.notna(row[6]) else 0.0, "materialType": str(row[7]).strip() if pd.notna(row[7]) else "", "targetWH": str(row[8]).strip() if pd.notna(row[8]) else "", + "temperature": 0, }) # 三号手套箱人工堆栈 for _, row in df.iloc[25:40, 2:7].iterrows(): @@ -343,11 +345,12 @@ class BioyondCellWorkstation(BioyondWorkstation): "posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]), "materialType": str(row[5]).strip() if pd.notna(row[5]) else "", "materialId": str(row[6]).strip() if pd.notna(row[6]) else "", - "quantity": 1 + "quantity": 1, + "temperature": 0, }) else: logger.warning(f"未找到 Excel 文件 {xlsx_path},自动切换到手动参数模式。") - + # TODO: 温度下面手动模式没改,上面的改了 # ---------- 模式 2: 手动填写 ---------- if not items: params = locals() @@ -473,7 +476,7 @@ class BioyondCellWorkstation(BioyondWorkstation): - totalMass 自动计算为所有物料质量之和 - createTime 缺失或为空时自动填充为当前日期(YYYY/M/D) """ - default_path = Path("/Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx") + default_path = Path("/Users/calvincao/Desktop/work/uni-lab-all/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx") path = Path(xlsx_path) if xlsx_path else default_path print(f"[create_orders] 使用 Excel 路径: {path}") if path != default_path: @@ -1160,20 +1163,23 @@ if __name__ == "__main__": lab_registry.setup() deck = BIOYOND_YB_Deck(setup=True) ws = BioyondCellWorkstation(deck=deck) - # ws.create_sample(name="test", board_type="配液瓶(小)板", bottle_type="配液瓶(小)", location_code="B01") - # logger.info(ws.scheduler_stop()) + # ws.update_push_ip() #直接修改奔耀端的报送ip地址 + # ws.create_sample(name="配液瓶", board_type="配液瓶(小)板", bottle_type="配液瓶(小)", location_code="E01") + # ws.create_sample(name="分液瓶", board_type="5ml分液瓶板", bottle_type="5ml分液瓶", location_code="D01") + + # # logger.info(ws.scheduler_stop()) # logger.info(ws.scheduler_start()) - # 继续后续流程 # logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 - # # # 使用正斜杠或 Path 对象来指定文件路径 - # excel_path = Path("unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025092701.xlsx") + # 使用正斜杠或 Path 对象来指定文件路径 + # excel_path = Path("/Users/calvincao/Desktop/work/uni-lab-all/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx") # logger.info(ws.create_orders(excel_path)) # logger.info(ws.transfer_3_to_2_to_1()) - # logger.info(ws.transfer_1_to_2()) - # logger.info(ws.scheduler_start()) + # 1. location code + # 2. 实验文件 + # 3. material template file while True: time.sleep(1) From af411ddfe62b1564fb8c992edc79869002e3e8e2 Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Mon, 10 Nov 2025 13:34:49 +0800 Subject: [PATCH 095/104] Fix import: change electrodesheet to electrode_sheet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改路径 --- .../workstation/coin_cell_assembly/coin_cell_assembly.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index 96f3232..4df87c2 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -139,7 +139,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): time.sleep(2) if not modbus_client.client.is_socket_open(): raise ValueError('modbus tcp connection failed') - self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_1105.csv')) + self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_a.csv')) self.client = modbus_client.register_node_list(self.nodes) else: print("测试模式,跳过连接") @@ -1008,7 +1008,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): # time.sleep(1) # time.sleep(40) # 数据读取与输出 - def func_read_data_and_output(self, file_path: str="D:\\coin_cell_data"): + def func_read_data_and_output(self, file_path: str="/Users/sml/work"): # 检查CSV导出是否正在运行,已运行则跳出,防止同时启动两个while循环 if self.csv_export_running: return False, "读取已在运行中" From 0d64563fb692c4a77a401901e0768e93c47d4dbf Mon Sep 17 00:00:00 2001 From: Junhan Chang Date: Mon, 10 Nov 2025 15:40:29 +0800 Subject: [PATCH 096/104] fix serialize for magazine --- unilabos/resources/battery/__init__.py | 0 unilabos/resources/battery/magazine.py | 9 +++++++++ 2 files changed, 9 insertions(+) create mode 100644 unilabos/resources/battery/__init__.py diff --git a/unilabos/resources/battery/__init__.py b/unilabos/resources/battery/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/unilabos/resources/battery/magazine.py b/unilabos/resources/battery/magazine.py index f8d2444..d3ffcd8 100644 --- a/unilabos/resources/battery/magazine.py +++ b/unilabos/resources/battery/magazine.py @@ -52,6 +52,15 @@ class Magazine(ResourceStack): def size_z(self) -> float: return self.get_size_z() + def serialize(self) -> dict: + return { + **super().serialize(), + "size_x": self.size_x, + "size_y": self.size_y, + "size_z": self.size_z, + "max_sheets": self.max_sheets, + } + class MagazineHolder(ItemizedResource): """子弹夹类 - 有多个洞位,每个洞位放多个极片""" From 39a799cabd36ab5eb07aaaa91423ebca57db887b Mon Sep 17 00:00:00 2001 From: calvincao Date: Mon, 10 Nov 2025 18:28:38 +0800 Subject: [PATCH 097/104] =?UTF-8?q?feat(device):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E5=92=8C=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改 bioyond_cell.yaml 中的 xlsx 文件路径为用户目录路径- 在 bioyond_cell.yaml 中新增 warehouse_name 字段并设置默认值- 为 bioyond_cell.yaml 添加 resource_tree_transfer 参数结构定义 - 更新 bioyond_cell.yaml 中的状态类型和设备 ID 配置 - 将 coin_cell_workstation.yaml 的图标从 coin_cell_assembly_picture.webp 更改为 koudian.webp - 移除 bioyond_cell.yaml 中冗余的 display_name 配置项 --- unilabos/registry/devices/bioyond_cell.yaml | 54 ++++++++++++++++--- .../devices/coin_cell_workstation.yaml | 2 +- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/unilabos/registry/devices/bioyond_cell.yaml b/unilabos/registry/devices/bioyond_cell.yaml index c6ad669..81d78b9 100644 --- a/unilabos/registry/devices/bioyond_cell.yaml +++ b/unilabos/registry/devices/bioyond_cell.yaml @@ -4,7 +4,6 @@ bioyond_cell: class: action_value_mappings: auto-auto_batch_outbound_from_xlsx: - display_name: 批量导入上料 feedback: {} goal: {} goal_default: @@ -138,7 +137,7 @@ bioyond_cell: WH4_x5_y1_z1_5_quantity: 0.0 WH4_x5_y2_z1_10_materialName: '' WH4_x5_y2_z1_10_quantity: 0.0 - xlsx_path: C:/ML/GitHub/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx + xlsx_path: /Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx handles: {} placeholder_keys: {} result: {} @@ -464,7 +463,7 @@ bioyond_cell: default: 0.0 type: number xlsx_path: - default: C:/ML/GitHub/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx + default: /Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx type: string required: [] type: object @@ -600,6 +599,7 @@ bioyond_cell: bottle_type: null location_code: null name: null + warehouse_name: 手动堆栈 handles: {} placeholder_keys: {} result: {} @@ -617,6 +617,9 @@ bioyond_cell: type: string name: type: string + warehouse_name: + default: 手动堆栈 + type: string required: - name - board_type @@ -785,6 +788,39 @@ bioyond_cell: title: report_material_change参数 type: object type: UniLabJsonCommand + auto-resource_tree_transfer: + feedback: {} + goal: {} + goal_default: + old_parent: null + parent_resource: null + plr_resource: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + old_parent: + type: object + parent_resource: + type: object + plr_resource: + type: object + required: + - old_parent + - plr_resource + - parent_resource + type: object + result: {} + required: + - goal + title: resource_tree_transfer参数 + type: object + type: UniLabJsonCommand auto-scheduler_continue: feedback: {} goal: {} @@ -1072,12 +1108,13 @@ bioyond_cell: type: object type: UniLabJsonCommand module: unilabos.devices.workstation.bioyond_studio.bioyond_cell.bioyond_cell_workstation:BioyondCellWorkstation - status_types: {} + status_types: + device_id: String type: python config_info: [] description: '' handles: [] - icon: '' + icon: benyao2.webp init_param_schema: config: properties: @@ -1090,8 +1127,11 @@ bioyond_cell: required: [] type: object data: - properties: {} - required: [] + properties: + device_id: + type: string + required: + - device_id type: object registry_type: device version: 1.0.0 diff --git a/unilabos/registry/devices/coin_cell_workstation.yaml b/unilabos/registry/devices/coin_cell_workstation.yaml index be2bcaa..eabebad 100644 --- a/unilabos/registry/devices/coin_cell_workstation.yaml +++ b/unilabos/registry/devices/coin_cell_workstation.yaml @@ -502,7 +502,7 @@ coincellassemblyworkstation_device: config_info: [] description: '' handles: [] - icon: coin_cell_assembly_picture.webp + icon: koudian.webp init_param_schema: config: properties: From 0c9f26e8fcab1960f7a9396cdc0364a505d3709c Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Mon, 10 Nov 2025 18:35:21 +0800 Subject: [PATCH 098/104] Update Excel files: modified bioyond_cell and material_template with new data --- .../bioyond_cell/2025092701.xlsx | Bin 18230 -> 9782 bytes .../bioyond_cell/material_template.xlsx | Bin 10381 -> 10375 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx index a3b130f62b8a05e3b4bc0d5f37e2535f51bde074..2a45f93792e954b6891993f8d5205786d271caea 100644 GIT binary patch literal 9782 zcma)ibwE^G*Eb!~0@5AQIW*ED-JQ}YLw89FNP~2@AV^Dhr*wCBH@t&B_j>ic@AG~8 zk3BPIR_wFZ+3W1J)=y3n66zV)(+HGV5qSFiYruoP7y=FCY=PExjB+3{4A2F*A7pmb z1auDIU|>K9Ffi1AlIdAnGdRDqOotXRRzn<` zNR>25jFdlsFGNc@Ll4ZIh@d3TMZUm>MrRX9_o=Km#Nax8F9{)}h^7>uPm_~2>7b{Y z5%5~0M~RwYm5olhkCxj`TfYsa9K~E9C$-N|#UWoZ9hsB*krtKW!?nNz$C#dD`^$@7 zBwcXL&e}t__D+R&X0e4}wsnSEr@{sZ))n?>=nQmZ5!YarJvEfw85%r+6&`fN!L0J$ zTrEn4Q{8a@oBeAcTUz&nj{)=B(u>2fsr@Uq3*5TVn6!9!N?%PwA)X^s&^?d)a3-)< z^SXbXsCn9DDW0U|USzK^v;gnq2Wf899wtT(4 zmrqm`);2i4wr9nS@3(KPe^sgNt{Z#`oA7Y2ScLMsi_UD)O87u75(K#j?Vm0(0NNTo z`RHS0i$og}cGscLA;`s}_8Jrw1o?Yw0@j(%2tC!sQ`saR9NnN7n?L(-5J`FmBg(aZDNL$~Z$LZt-~TXTZu4SBFUSny<~QV72G0Od0CvmyhD(n6JpEH*U&!^I`b z7wQrut#}Pz8;Yss=$IU>P1Eh-}C_nN~14Z-meYA9&Fl zC;ZhcF(1bPcOh*lY5UrU!(6SY@EJ(Qvn8%ldBYu0R z)!vz>57*lrSic)vH$tbA8f0t~kg?H!Gq!`BJ@DO=xpN}5CEJ)V0uG@cg?l`b!aa>E z2sCHkJ7=UHs7NGQ`BPGha}%t1aTv%Px8gPM)&~Y|zIv(K7(Bvb{SJl#!5b2ammy&X z<{NGE=xXPXw}~Zfoug6|)>1c;THK`Sb%P4}q~lVj9h}*-(rQNy0n@3}kO?S;IEL-m z7sU=)3xea9kgJvYaeEsLH%ocTkVEUdhJ4Bo@wnNHYWwXv-=dVuG5Z73C1 z^{(drvcoOgW$f{!T#}gY%04zit4ytC(V3e;Ye#Oc0@OYT7dXXJt!)Q);TapstI4aXq`HIhJXU6q^uGd@^kTl;tm*ObnWIb6 zBnALBsCT2CD0`-tHp6uWJ3ec%=F$a4^zy0G#ySwZfw72_Gdm5<5s1?wS87oz!cTw~ zZ9B;zcdQr9+9n4`h+}5ZHlq77ev)q%<((p@Q&vW8(Jv(S`WD9WE#%^bC#?>ZDRSk( z$%q2JvLVsXE3R(F4-hRBx->jX9~5&O)r`8f)48LUh=;NCDU|B1%E;i71#Lp8!p7Xx z4;V;t`Bv9ltTq!We9y=bq=~E7nb_vUI-=2(V-Q`}`I#^W)3HR;^^Yetedur7o$4MK z!@;Pkc}3|y%}goSGaE1?U?60u2IV+A4#b&vuhhs6>SdS)LvNOJM59lQQpwP!Q4OZQ z53VYj@z@sBy{DD+CrRGg3m@yMTkSH!llI3fqnK|u?_SNLQrhL)o*F#mfJ)Y6i#)6Lh>+%!-W_?17NrgXP8i( z1*K;Uqx%F14Z-0)y9beNK?@3#5V6&oh^=L*T=KEmE}iTFbRYCVND#Y zbzq5hmJA5o3w?(ij&hK1_%dOOL$>nHly_Ag6mbMaRrdmdm187mVSFLU$~E)apU0*s zp))mor+uOIzA~{Z(7E+!2~}AOB4uYjDauS*J!gD_)!8 z&#lqgeU}mzRo|t{-sApWuE_SI#9c&{;<$c_m_-GBx!9dX|{6i$8BG#wTFsyVjm z{OUr6wXQr7=jAa{x2#?lJC&>P8N#IG>*B2WI$xlI$VrI48_ppfT-1GHhakt435Y-T1Nc%$wp127 zEq)8;P!`em5#WnVs=*PF$VsB5W%1LaV?2!^KeB3n9g4{teB3LyL^h099y=~!1d9>bq7-n%@SWcRwUg&VL!BWy8|_-o$QVmo;uaj+a2MQUE@k zR>^fIYSd8Gu(O2Jy|Qa}rD0Jwuj6zCV|WbuaS+?-I?#b9*KTUd6gUCC@5s6jZP-4I z#OMyWgcpm*CoIU0F`bi#eJn!XTZzrTl!*X_WT-X*RbVC|%IQaEwdzbvHG#e)?IQ`? zlcU-UIMWgf@X0O2sZZ0-4@ziM9cuD8zoT|Nzw7B~GkJ;P#p!akwYGez@`=nD^5tsT zQeO%G^?e`82vD}k_4_g3LMVZr!I`_#sKS|K%S^XS98?#;W= zuwlgwVp16A{qC&61WOE?MIHX1ypvqNJa4-fXStANpfsA}>kMrCHp^)vG@Fn;%yyb> zUjuy8&TJRHEV9fj9wc-_z)#%1I`1JtpyOmlRkKFsf1o(lIwmQehMX*t2tS|B+-|gU ziX*gdUG9cb$$9G1AdIX{jsay%xO(fy1<>h2_cuuE0uRI;J@h?gfaf~WEkjZ#g94T7(x?l9osQ_ik*}lQ>R90Z2sw?xVl-?- zCicF~y6>TFwa^)BJNQe4HjnV_W-?#iwbR}(Hb!Cw@3HrO`POS`L0y{*OOW@@7B~GnV12)qf zhc>#er|8rmzYnsvW_PUIsYk78wS*d?@@h-dQBS^*9F}J_9aG%}e`Yg-C~E!PKh5Tu zV6-b^&@}d!5FrZ7iB$EFTrm?ujyrW_|Gp4t=+zh-n(UbtmNI5QNtxVNks-r<-Qr36 zGVM^vRO7N_ObfgS(#LpARCA6BLjY+84Qw4C^ILhi`pY%+*CLF#gc;yTK^}<9urihW z!ZgdRtr!)a-m`22RW9on6GfvjhZTr}VFq!coV#&1j+7;w2psp>r7gU%{g8|GRd}0& zhrwU2LTq!AJ0Dq9&uqas;4Fj*-lH-&L5tQZB!XkntPJ~q4+_u*-)-C`h@iI?Mksmj zUZcs^kMR3_9^Ti zHtw(}Uwpo`JTE^H<<%wY3){jf*H)k}d1i8K0Vpe281^%IvGiTTf@dPR>+|^>XV6N0 z7n9nai9!vm&<&B=V?JEMy-o(!7~f*xLh#b5?>dU0itcx9Bo;9av0DqUTS6YwXz|Zw zW5zs00~(ELx<{gw@s}9WGi63A9x`Pi(lI#O)@lZmous-Y9v~vXuKi7UeQ&dVna3TSLFCBTjxV z45_K-Wx8S~^ksq;%qgb+ONocWOV5m*>CIL9gq+VUmxWsU5$Wqt*|E&|ppQK0Eaq-Z zVV!3UizjPqY;)C4OsF(lo%xLxqKC?2rhQi|AkSf93RWi-Qe=3sS$e*m!~UL5 z+}Yp`G=S0x9w;!dr=%P7y0kOZvo$hQw6`_0GO>F)qxwh70!Nv!I}gR&1H1EV)b?w4 zdgY?b03QT)W+ag2vL-4bar4F)eDGj{+1~}VO`L;4WWd`9+TW??$zQw+8udV6IrmhIj_oXZ;=C{J9zUK-Nzw%aFci|K_fx{X6?Ho35Gas_M?wN@Lck z;iYuhRY5M%`0Qn^<15z3IKsivl909eMuyL)r)x{!5(i6i`6t?olk1(L2vc%Gu==8H z%5GUCZ#-P$JrlPZ*VpN|upbvc`BRkZ?pS(pCEd|79`?zkQ|8??R+etRD{`=Fu*g_t zJAm+6s+-Nz#~}?os@F@<3w1L_v;E?FD2q`Q#yX^>XyfA?>Rrcrws-fGw=^Qxf{<;I z6}rc1?g?6_H>)3i_#puPkE($VpkyxWmTYy^~qd>});gt;A87?;TR zVQ_^S+K>BAZ|@o|bH1{D6tYgU7F4$s^bq#-Zf7_(s52NN8{?0N>u~^FBtLfMgklyj9hKFcj{+gO|&0Lu0-%r5lnmTur}_ zGTr;y6XD8Inl8_5(K`!KC$mKr(Q75Fm>wnIWx57KUF;5R<9T~FFxj#D4f%U`dB;dd zM|5=_BmZZqG1io{WCB9QM)w;BmfPcf?VG#p>8#7`k=3)S@$GUH{-&YPX+1$XF5`*A z{0sYWiBw_lVTMr_hUX!o?STR?eJ>(^9*3zOTxbyTbE+ewW^^LdR_BrL!>0*4$xxTKYqoA z=dbZfIEBI$`3AZaa8LqquES$tzf2&@x#>y@b_rq?;WYV3p$*4OM`(S{U@5(yI30i z2qWWKn&GpZn4XNoj{?J{M8T#VNepvE6X)ii1iUNY-eOS=zkpHAtPpXTj)?g>OYE)L zHBIi1fMK=E4mS*Vzsol^%TA9=_A!Oupr=mu9^n;heSat*W=rXL!FbyV%jUxXS6c#! z)0fnmFzdc&LbI3Lne`MOeAdtJ8uyWSR#2?4E32sdBDc?_yDp2uoX!1L#^-^a1}{Tp z2zGBGjb1~P9<_!!W4!K~J$IerDbr$|I|-*pIY7K?W+UT73OGOFEbaFU3ew8^BBoG& zkXZGy6TfO+2aWOz&Yiuz*6#JeSDfj$_8uljxQi$Be|zryQBI(vQBk%1nnlcke>+gBwPckF)8Qnhe$q9rC)q+xD2-=|&XV zB4^Gh%&@2lA*a{I0}El8Fu+laVp>#|z$NiesG~A#IVvT?4S@0rkniR!h&MxksuFyQ zSAO!sr98cvYognA?0s=1+ydI(< zj|}^nvNC|ihqjJ~2{F8=nRQ7-EDV@T*cRXI`x0hseaEH_x;XH?3k_{eE(RH#N7n%v zeSkLab4fb{`*SO-o>x*U*DQ8v}cuY=UB_H+Zs94)izS92ZJ}Ach^n`faV=` zl+c3~S3__XjooO6(!3JfKylfw8Xb~3R%Ri?ELdH$&pErUnpRb_@3a-y&-;Mh?%MBm zrJ=_nRFKBiZgq=>Z$;jacbhoCQ4R3{cn1?YoDGQOL$W2b^4}`Uv3!p{JLUQ zT(dzTo~3LXPko)^mk^~Z5r0fRyyM2&Kl(`{>F;Zk04Q&Yj|y#4J&Y9nF?Wxw{Z}OD zgcd0G=_tFpLS?zWaV{&8bv$8gK}%dLrGam@yw-@a@+A0!-?o@KTjziSOBQ6LN`Phe8rR&9oqpBxo_>3I%7X!BPA8 zkYrQE>5(O1GnkXTmhTr0PEY@c(a|?Eb@`?gki2YTe3N9dE|jRwgB4{>r!A&9HUX_2 z#n{F4b{=VHRG8+L`#x$`G5(=($~gC$mo3U3p{LG-YtSLa*o(89r6xe(s>fS{J5UP85t|f=(VZN(mN{0b&gZ|~f;5V4T+1$H5MQxuj!2vus7c9D^rD(+QO*WQzF_^Fu3I1TST8jo zg7BF|O~5i{Y_XwzGAHJp*mIifuUs@GFM?TO*9B2BF$@b&VlsPqvXlx^Gif+E1D&6- z7`miFglES?IHTnd!w<^$su`3;pPCaIwiM=zVRCVj>m;X1U8p?Ti{_yeereHr9h)tI zhp(a}Ug+4Y#}=!UZ~mU93&s>aCp%H2l`w=(vi0FsdN>M)Scw{mxyCTq&ZyF`&>^0q2ca`gBr&NiX=prU%DEP+?L_t-1Kg3lNvl2nWXtpM5@o_IM9UH@T6J_u5i>3 z26&W~i=$Y3FLonNgETH9^x-m8Rgx>U> zcRk>EGfg|$%i((It|RbJOKLPLRCeo_y~ive+UsHcWY1TLSfbGnY(x63NhhGAW0gPL z>%L{p$s{`bA@O^Lz1T=7xhhX&r}N!W9prjyLyEv6>bjGZjZa(#BO7LL+&>B-aRW^!`;35Pxe z!5=Rl`-HV3Uqnt&rN+bcB)q(5|1o>zND&d<=1yo80D8})8ShUQWmuCiKiu(J3}p%~ zXD**yFsAamBuAF2$=Ttc!G?+n5_XFbbGfer9D6X#&YDgomz9HFo3j57cRCbC+ng`jjmZp zm!*(@J6aPz&;gR6ud=Go{-i!c>to2=lcFV}87c-_E*PTB=& zcx~nTlaS695BuAi8AQMZ+KoBZLSNFyeQ;e5i??CPTcVETWKpY!%aaIT{oxDr7Pzg`H<19uTsc4}yRMdzEMRkirF(ISr1|ip8SRz`gxRTn#6n*Wxh6 z>zziVNWnDDS-!~1l1m(B@si!+yul@K@rf#7SU&a_8j#J~ z5XcJ9&Y5nZW)FTQ9sk}p0aa$fbZw0+|Lm8zaXQvNg8o|sB_{}f(=a?y7Do+9v@@Z0 zg1U#O8;#OVm}t}XjHG+gh92JWQjf!w=uztXjSYhGbYr!!;R)9pNsI1=b$Ii{I?TCY z^*#>rJ_5jQ$42=hcW1SEAF-l_aYz>>>sF|`ofG|AKi>`Bm>CB0NNsn8VU|E1>S(i( zZ6!H@G#<9sd0hMkWWMYTR&rLoC!(EcgmTin9Sbx;Zgns=oc{IKKw#nS;L@vvEa4$m z+BK!f6bH))%urwVtf?l{^VcviTtl`Fe0_&b5`jg<#`H+9wfy51gXh+@nr!G2y0m17 zf+^Amt_4O%H(OgGM>H$?#JK$^GIt-zS-RTS;#~OHbj55>=s!FCKda#XX3lQQY1=)J z8EZl2#07nV()yodPYtWTvc3OUSK7Rd3Dy77e?hRnF)k%eL1N8Pf@F5$m3KsE+y{F2 zfwQja5^EA0;%q!16cDE2azJc$euy)OQECjfB%MYAbxXRm;Sj?eP~s#@98_8D3P*@3 zC06`9NMiH?Uvu?;B(L7}JmNW(yvhKd)%g>!WDZ`C+ zy|da1bbXU=*)3YFJ;`{B_2X>$K3gD;Cn7GA5OkBAd{|W@<#GyRI@#+QZ~8r1$v@Vx zGXa%8kf48d3N(d*R`y0#_PRgUZ~#NWupS} z709uRX24los?X6yD)n0k!S|s_;@tb!O^!4x863*$w)=W5_Gt~v8X#zfJd zPEs-B{S;fBVc9ex%SMiF%$d|-n|%riCW^$Vd9B0Bz-dcQ!0`v9JSs>>md{D!f)Ns)`anpoYegMk+Si?|3U%!DA|K zuuW}wa~S#_XZbm9I5k%te9ZiyA;|oKx#5{4d(DY0zK4KJKw*<|*woo*L7V_Cf+U*K za8;2&?0a$&N+kZ&M?*=XW!o!;tg0LR6^Q8SPpl}oaq1gVSH0GjB2ee4q$y&1QeCKm zRF+^-Y~!gH2l@CR4d;;)Fc0$rFJ1}C*cW(j0-Nl>)Hk4x`ns<3E>8zb+bXEfd!OwO zE^X2a&3F`UFPt|;a@yIcFccM_SBgAIi8d~jeo#ee%JRW zu;q#Pr@oi}Wcv@ytF1DV+#J;O+Piu ze~zb{js%sKK;h=UG5l^NcA|g8r#~0^XCQmh^V3S&|781LVeL;jf7UsF$q4|}!Tzo4 z`KN?GYa72LjDc_%PZIvFbo~F{I}_Xw*1sB=zds>=wlaTdRQ)I0ug2z|Oa8Ok_e+2S z2&V&D@;}Y?-xmFZcKA7-JY4qE!@pr4eqZ^Y=!YlXpI%$|U%bDt5OR{xPsalgh6oJ| M7DR_b`t<9608&{WHvj+t literal 18230 zcmeGkX>TM))ggdT0EGyM5E4>rg1Ca+Gvi}zV>{S$WDg&MXX4!i&{ES~Gt=w7=K- z)w}A|d#_$yQ#*CzYu`2${Qc;L&p-dnYlepK^G+z-(&e)b)m?eha@L!cwLY?C=p$G% za|b+08`N<%%bd*>M)Em{nu?`rW_vbwSED*SnUh?Pm@3gNlg{S0shhiF?#(yNtWuqL zfaG>G+m(Qd>CWalo@YG=-KerE5^r%V5FU?{+BlfGBa=)SJzrN?4IAv#$c` zCMRbLQyio)9w>=Mg0NSa{ohxH(Lu`KdfPhmXs8YT-cJQ%17(kWu|z$h5|2bu7=B40 zhb$P|Tum_c{LL5E|M;$eoTaq(H~98JhiZ}Fh1qW!ih2<{y3I` zpUh8;%@+lD2E^gVQ}AO8^QF>+0MCFp{6q@AdTeT9OoV4Z9bGfmQ_$F<)v8ZD1Zi3N z@l5bZ0H4hJ;1Lv9Lug&dqGg$0FIzLnIm@X600Lr+HYE>&aEmBl#U)}i9gQFZGEGUW zh8HPrst9>WuHtAmMBI}$0gs62UAy|TYgeC=u3f$I;=ND5c<*yB-gn=N_g?0-1jOUS zY{wD*^7z;O`knLApP%~vUmm`c4G*C$Vzl$*#g~5lP&OcVPz1R9jVpUkU)lZUmtXnG z!x`Z74r#{08=65~X_;y3m1Psyv+8LxPUPl02-X?k$+yWJq zx8^|b$O62xdrpwG-f_B~mcjW{#}ELQEK6UooJ<9rLS8WqH@ap!o4M1C!>tmsk;S`2 zOrdKlT^piiV3dOHOC2hRS<{K>K?-70Pr^EDozjff-K}YYUY9h*v0SU=Nq1|~0?{&6 z($JcMAw@{XH3Rx(JFCsm)q-x8)}#eX&uVpzGC2b+Ux4Z#zmHuHt&o(`~9-Qbnb%n}xIDKzn1fPZ5Y$nccpmZyO?{<7w+z zG#80w#UabqdZ|MUI}5h1nVmT733nY#lt?uTl8v;JE?J_7;5+mKF?)9RnueKV6gTz{ z1b1m|L8Qn)u%_!|h;=8m1pBRT>n%cAl!1^gL~27b`-N>?gSgE(yc)J=bMr8WWjW9B zM6m7!_g@&I%cN`8D0oHM`d^LEuSV$H|9ph*<;#PQ%TXwXD2)jT&$U_RZS+jMTbi!d zaZbMIvfSH+0Z6SvA>y%R6&g;&><&DlCj$ckuI&(pBRQ6LTJt*f4#5SK0-Hv*U7vK@ zm23;vZ1Su)lQuGi+~4D~&2gNE@W|m3Z^=@5@hBDt(~Xb}ld8?;v;m`0Bydn>qsM%r z(6*y6E1*yZ=2l>$v7jh$3!988X7h4TdDt{447CZFjRg4+Xn6vbwvaU>V??L0h!xC% zgKj~s2G0ZTBEQwVAswPp-bF%jOq&JPcYz6|+K0{KHgY&^hFSxq?Joo~i{AEgKqUtR z0>!dUN_vxS#(hgSrOnyg)Oc|`Cn?09&9z{r3y+2ko#tXLNc1)=iYlJNt#)5uWKJ5j zcq-ef3b1(5!(jwBwjI|i6Su?rkX89zrJKl!%U>KHLt`6E12Tfi!K4_S1QYp>NdYz@ zb}enSsN%&tJ63|aK%1cYU*8Ql|c7{PO&|z=E8ynb7*{l)iu;Ms}f_Y zA8F!iS`3bt6W#6*KU(7IOyF@GoSAAU1AB{!2W`ugoi*<8O+k+zR8!(+{D0OI91dUw zVN}H)P+)P_ArgAZY|e7Ljs;Q3?r2KYfpMS34zLzjp@nKtK5V40Z-;K+H}^SCgSjHay3jWZ;N$Nf!W>GP5U7L&Dq#IOkU<{nV7@U(jVCpQI z_+fy-CGAgq_dTfD#vCQ?Z$cyshfeMT|L(+vJ2NyYkxQWc2Ko<0WZDuF7mm-+gE2DR z5Uhx@q_My&gu!oW%4c;%5e_l{$HrP7u~IEAjt9a)FQ!`zg<(Ep!#a?J|H1gDIf_o1 z8RLN3uvQ_qO0fOF^(TQ<4`WmV3&H@&pPG>O@Ir1(NQ$f0Pfk&5!C*b$EGXogP&P_- zkp4cTvcn`wWGDFIAeHTFSkN^u>#1t!*`$j?Q7f2-u#RCHUGcprt9cGOx(uuSU7hbv zxi-8pKd@^Kv#zG4Zu^hA%LiO;bvTDXNZnkeElE?ig84_N3GPCCha^am88o7yp@oQ} zS(644(T7J=K3ayz#r=^(48|Ekc8JUt)UpV`{+J)QacT$fJjn7^qjLqC(#`8p*#U&l zR$1oE=b$R%6UDwAbp(}pTm4n$YYLQ?HaytjbO=nn>MZ`kpu;VlnQUS^_~v)s`oz0m zKQx4&H}maamu+(6X7bTDf*FlEZdV`ttb=Pil&I`ffw&vYPD@O0K;S&up$64bYK~>Q zYYo_xbHP5%u2D5SS5mC52~@|!B8k`FS&A_@Nx@HIlq?mN1I|hxkZ$|nZMWsmN*}`K zj}6D)-t)<><$Y+Fe=&#;pOw($Il(yHVv;ULy7rsL_(Pmcsw&PkRmlquBm|s_$ZXt5 zKGD9YK%jod9sL_QmZYB&mT51bo~rfR*|YvI!i-Od!f@P|p8wL$gBRqNp1%lR5-;m? z`1kIk_wonB;NLqJzQ7+`mw)d){&gRR#Wmlc=4LRwg?5F30So44+)wfFU0|E!iltkQ z#Jtg8?!fa79HIo9KK`~t3$~#2ZC=dqc!=QzJk+9Ja}Mka){ii(<^(-eG0XPC{Ia7>9cfy3G2n&AE(A?UpHB4tK~Bzwyke3ca0{6qX2kOTp0Vc|4WT*Teyp4)lg{O%*qzVgV^?{nWT!d4To zU;N?T!_Vx!@cEa2ct6Bm-~lI5_8z}1BHbw>z5MITI}hByAJTCV3E1phzPkI&#g{K% z>dooIen_B#-Ah+Q@lJ|Ji(0jQ;(hHz!%roF%GH&H6x5;!Rc@41AWI@heLe-TEJ7@5 z%PXTPpp_(0ZKYT(rod_P6r+X+Q?4vmQZRRk zFpZ^pEd{fdf=TzoX%VK9rgBDvC}(KqZV{xilvd3>B1k>m>iI+fC}&WaPxyXS6h?-a z5(XAb6?kBQ{4^F=8kKruIA0Vk8=_hqrd%uLi{tsJB5XnxM97i|IVBg5$;ELIrYyqn zn6h)_E8tTiU_}HhAtmR`qtjlw{{*5nZ^xoR8%yMw(;tykGZMy6YG3D)!K0OhpH9F zu^bp_rEbHV zNK=RdGmhNcO_DSdw3SWbWAhBCOVs*_^qIx-d~G!+!BnZ$K3->}8GUT?#w?@D3XmZFVF;a&2Ansv6t7fC>nqB}=b&v=+A!SotM> z`p+?0LCG@Mk#$CkxGvZYdsHmy;1OXUgtLGpILD7KGHb6aG?*2&x?b0z2!?7-G}B+q zfDDmqq`A%?KsjK+xbwiunYN%n>N?tN?$b@R*_>2VWw=EOba-rPf)10(@ngfI%4lme zujGqU%IIf(l86e_pj)T??wQB#!L=0%2N2;!9^jWwwSn$d~v;-)@MO(OO(@DE;R z@FkQE=73CWnAQqyMQAn|3>)94xsHNS0F>%)7;$}slmk_x4(D!~3H6+C+P^Qo>Cy8y z3=QGOciJFhzrMAxfXcv}c3=~jK(d+x{-x4ob_=7?(cIJiMJSy7u?b2rIq_|9=i9&9 zee`lc+IjMU-3wpcW!XDig5t?FAJDH|ec+`R9^3iKmtMK=2}rbe9=x=7@uxgx=Kv63 zahilPA#|*14IbEqCY-qkOv5W#iOxEC?v{Tn!u2iuyb&TSzrXcga%T%hExs6&h`Wcn z4;GrStiZT6Y==%6-4hDdK^a7FStx#2D7ZuhtAhs>X#Y!O;NH|!soGy8SUu?{5m?0F zBobwg^aJ<)0uKme>3fyx$K>=6Y%nU4; zDevG70_Tj`F#rVPFAPPuVXqcKaqvt+xKFHb zIfhXaa6O1Cam$S+06iNU0Ut^;b2I+-xKzPGfnTBqwmHdH3ASc72jMe_Co&8EN6S1zP42H{~IfMF0| z_o+t!zs&Ep_F%xLN_|_ITWg;kXATmYAv>h$C1p%g(Pyvl)4DKi**AL7sBmtTPNeSknkZklCOPS;P%6vFQCX zC6G$Q{(AptNs>S#@yoTu}}yE2WCJ8 zfM%l>MZS*p=ylUTzriwF)mOsBt8-ZYyfC&8b>$-kh`5mO6JOqyMx#Y86Hg9SlyYl^ zu^$1Edqk(IO(V-@dOo{h=}Qvvct|`o&pbiMp$9e1#aed5=tYU;!~ApBs`9j5<%+YF zC+(lv#snZo-hnBF_6zPy8AW_{e=>aV2Id62NLynD-+m3qzHs}MBnL^JQExjf#Qjle zf=*ZS6h0Xifn5$a3x+f+nTz5u35FLpLaBEi(fZgNss>XE?Yy+jDjU0bBe*%fr^=CB z237H3kib;-*Yu@5SG1>fNr;t@>A)JrW!U6UdI5PK4Bq@r+ur=OzxU(W^q1=>t1!x? zdgwPS!*5)}z&MKfVQJJmIv%fao}GooP(!>!)zCxei(A#)buhv0}KLzFhfXJX0YUJ+T7XWx8(%)=!5_VuNXyG>^jo{=i~4mlD-N4 zaw%`SyxPd{I6|p58uhr<*LAf?p>U;LKBU^=%v4LuRu(Ad)X{u_+2;==A&QAZDgXz{9(y8kyh4q9cWqpqI9H=Uh%F}vovvlJwRg2ALCLe~VLe0mPFNc0Lp}5j%j6SH?H>nRUi*aBdOnBMw zE~OpMr))fPzv@*hqFgD-=5bjGId>T&#{bK)@2lR~*=(I@y+zt|?~eB^Un)WSm0d3I zlj~w!HUmB?CZ%xNPktk)bW1XU6U2!MfYuB+j)lYQv+|4(NTU!0!Uq8zqy&J(G`!!8 zI_%yTtFfa~^F)ZG9E?F2clvy4NZ~;5>KcKy=;Dd@D37$g-Ea^a(U-{G)6M>~yC#|V zouI3E^lLWEUn}T|174z9dEJUG}Nj9Hn}ix_JsZ5P1Tu4Xo7ivUMH zk%rZE5tYHyrks?B>9vFz4M31%tH)7fHsWE{PF$yaPf`HRshWB{vNz~Wd2|vf7cG!T z>QKkj2Xj`hTJ0NzHr~u{3EGPMOvzi57$m(GJ>1j6EYwX!dIQg!e0ZJQE6|$8F4ka? zb};o*sIi(2nbPR;!Z5kT);9df;56*p?TJ_Ct7M0jdZ(uPvWsU`0FyN&Mxw3HvjL53 zP&~Akf7(2s!Kp?mWv#_}Du347$bv5^JxCZKW?1q6B%c&n7{6=z()ca7H2L14WOQq? z6KSgX<7%D)=3o`J*^MUlr|bB6RKC9QDC?-dQLLXtex*3XFQMV~A7v0!Y=&x69Qfc%pPFJbzp$b1=fIA(oH`9Wn zMgZgD#UMm>EcL#dahB_E{_Wp9n(>(T!u*>aktW$D3I#yuqQpQ1KyvFe^169T^p;Di zZn=JvIsuA3OX1WP5-881$ky(7%!jAhCY~YmmfA2v3eQ9Z?)s19-trte-kXd`tvM5Q zjT2i>RA24SU)*V}!n#)DCD`S#(v}DY&)D)hQ>NqF1YS=^`SuzG38mol11@u$zP!)X zN>0MVFccFQA5h{yj)nTfE$4H!hQx^-yo#HtSpINWpq|RXCzg7F)%~fI@<6H7x%8>! zj%xvaI~Q3ku_xFewU&=|$3J!FZZw|@k=ZBGNo&loVXr>efAE<-K`{Q~+Yb)a#xkzE zzs6Yioky6&qpDad@BHyTC;MUTlV~>DT?>sH-qfVr_n%~ed$(sz-%J_wKure&YD^JW zet}^8M@|cPx|Czk+!70nB8M01>SxwTy9v`nwxi;$&g}7WN#)G=-^1RUV|J!Um8huU z-~5syuvXadyI(dJNi=7=SNLN!wcJ}RSjT$@^4$EMIiLRB&#igV;XYeAT5;mMqCB&) zSXVk|2(lljrq))C%BISS8`$NE4$JBv@jdlAwGwWHtGqh&o5Fnk7G>z=hkI^CXl+7`?9SMK>* zuNfE9Ep*;aRHZp?IH*upeh{SHxc?BUnk7uOX8`g2=kA6`*B;a}CHc68TYP-Zr?z@B zF^X(zuNP>QkQwgW;3C6l&?Z2__Pn54%n1uJlOr0_p7ylowz4okG%|UsN;ih=TASAj zt1nahD+fynt>_VXe2BtsbF%9loOJBTJLKyttHIchS(6`#{x_ygp`V_h?(=l>;mYt2 zfV_`=u-_DD_K}Ne)f-WtxM*vf`2FvvT}#;R&kPmr3{sh-uedRPW)JTwr^C{v1JsZ<<=83B>gnPX=gH!hgWOPkK2^6 z5Su7R>$kRLhHds$_HWQ@>X&by($u|AwqMwej2IZL%M=8oK;M!`BW}Y^xsbxd#3Z^jJnRAMy*s_d~{7> z7|$h%x~UY;o@pD~%Rc4(WBHwo(u6_w zI;T{gMeBQ5wuAfYbmU5O%3q0--7bS?+fTOEpt-LHEC%1j@?y zT!td*Jrf$MxDht{3OA!3y(M2=xo}a`E%?k`MFoPBa_v9aMx7 zFU^R^Hq~AE6ryS|_}oTN$s@Dqnr)a4s@|gv8FI6+fO)eMzAE^W<@c?lW|L38IfxX= zj$`a*5mbP;8oZc2x-n)X{W4A12-_~r6)4ibn_i4AYPFW<&XQq)93>S&W(-S$n&o_VveK^6Uo~cQPRO{OL}+G}m+;|1H6+9u znQ^t~w2|lveDqZ3CS3MV+=aQ-9@2p$wZdGEPk_2N?;4UZNDMk8jhW35%k?=b_!^edlSO_r$e?-0(i3EI3Vgx@2_( z1NE;}FMbKuXmWcuFvT}sHkYG^ZMv5qu*e=!iGDCqj1D4F;IZ(QDR-`u`hF8_LxC`& zb_b!1XAsWMBR1Ss{F!#c;)Plk+_3S+@C2hTrvQ_?n$HmGoIqy3q@vFqvlT#7(MDm} z@=D#&r3eyzhVG@nSKkfZG&uNJ3fH5%kv$ZL+t_Y!W0{*HK?1x(0lXJk7csis{2 zrf$D5H@cUddB|8T!|TTZ{RE~L|_45=pgZ{tk|fp8yp%U^&( zM32{P@DPcGUIDdGick{hhf<@fK!E)SAsN%lP&(N0DEL3`{?7q%OdsR#5EkGX3I|1k zijwD{N1&Ia8XbxaRAA!)UrU}6{O>;f|6|#R9{wwzX22Tg=rDPaA ICH~_62faC4jsO4v delta 3476 zcmZ9PcQhMp8^#l2mRLdTQW90OHEKm_)7s;$wpNW$)T~`mN=ofeBQ_0+s47auYqV7@ zYStc25o+(vr|)~-Z{2@9e_ZF>*Llu;|ITx+2V)X%@)`@I+tt$zkpuz&s@QBGH`?T8 zyEG(fn(9oWGX#fsGSk34`I5^^YY3TE_Zg7D+IkdDLZKF9>9gTlTB~w9{m^t zUZjfVkj7+f~rS=*-QDSfsui%eBywNN zrpr{_G^L8JU@p7o^W7*Tf_JU~QuX1P+D3E_q30PKz~ggv?6pf}e3KFc0RRBB0E{6C zxmT^cedTs8sv4Wy^U5-g2$Z(qPg}V|v$#Nwstnw;S}zb*%0VC!m!}$LoF;4u+Plrj_YTk|ngmUHakPteNB!3R#l^r$BAlnEm7nHe+K1EtNO{}tUd{>du z`5859mSglJdKw2>S5*LyrTdelh7r59-x!xh$pRSSR>AYD-8Bv6)EdKmqe|1oqe~Mf zM@PQ~B9~GvQan-~=PouJnWe9iJEqaNoJ~^YgsZjUpoV47q_qWKN$wv6biiR!YUFY0 zHKFmQ>%#1+991?!;(l*jBfufw1i(5|f_m5LSM2W`>kAzC;|PyZtEQQh%Et`piAG}m zwyXUPL5nLL^XZ4Xv_-O?5~E><&+DEZu@Q>0>XPzxV#TILs(H-O zb+@bQwd?#ST&iWYA<=AMWx~32aci1>?0n-@QZ@kD{}z8w*CzD3lCpy;hWXC{i1#E~4n{uuXTW+OW6C6c{MYfi+Y&raNy`BefnT{DQdxk5*f+dhvaBp?v2N{|h_F&zVA; zb*`lO3e66CK1WD^ERg*dZ1f1i#1Wk*Z!%%=xFHWx z(hs~;?4tu*Vbg*IbG*c3Z=T#1U9D`^ZDlPD7i{Sf+YD0V}%&iZ~VL@`duiPb1BI8c}nN zkoo5t8^M?4Fi3-rZgsR4z{zDoM@G%u+PyHaFS!J6p6##cEvn%_>y%;wNrU1F231&I;h*m z;AP8iEq2gPZWfon7kELt-Y)b40YL4XW-L|jj^_*=?%HWO&W;_`L|~Tpv+HF_g#4%q zh_NeStC0@4QFJc5uUK^a!{|>)15tudL;1&^$I{l->>9=Q<1d|WbW3bAfe~czWU_0? z`KK`n^i=mH9w$pZ2LG};2Mlbekk(Z$yl5$m_`OVjDW;Z0rzbbg{lc^C$Cr1IUAdE; zlwJ-$j0O7>V9(gUo^xOlSJ?Fpaw-J}o_)-B3_|}kg@{kgILm}oxmqfP5JlwLTQk6VeN!QCnJmKGC6#P8V+o;I zY5{&L_(i>Za$9$GWgyL=Q~rQ`~^itRLkE+@41a z*P1?mM3jD-PY^3z<;d}C&5=XJ1&7OG5xSynh!?trpw4FumG$2K>k?V(+C7ByqocgZ zgX^fB*#c{0uvg3-u--)ph&DZp3+|SDkHW(PPbTRWbO~G4%rIvobutQzWt5A|zCaEn z3+Pf&74It0uVk?ub$S|xENq#}iX!8Vo7JgiuGLSw#k(E4mLs_7Fh#FC?tm_HmE6j> zr@*15pLzSpDVzIiD1=L?mLZ3$X>^uTwaSuQ$vMgUrUU@_@(6PVGO%y(Ebk1x3EX zAgW>X_fU_a(8ohC?(#3wz5A5^8<@T-m5iL62d2v)0D$Q{Mg_V{1$g?n-S_l#lMM85 zk2kYU7?5TT(;!98GyjUN~bu4f1FlrTI=3fAn7+(G1FUx)*nh#1megIvlOjc zR$qKZ1dBOBn8k_4I=ksb?%$_?1z|>`7zRnA^7dCPVyx$PCLz95%JQn-P6B8HP zYwU3izspG!|NN`Y!MF_7L@bQm&@>Nuu^`?u)hocGkTv3F*Kvy{GCF~Ln|_l=N44z7 zE29rft9i{Qfrkf^=s?t5^EJjs{P^W-0-KWFZqf1*_#pRjIUUAr>y9l6ik?H8#Y0Va zC&jS{uYo#hCWL4|t4%CeQYK3CGKCa5$wG-f>GkC%+qJbb?py=qlq=2<@fYeb=6en% zNuPTE^l#5eQsOB^=+*daS+&d8_ARgysRv+QTqlVX1Ks_q2|k^t0_&Al2fRuVMb3P>oV0VwuxoH$>>U^w7>{*@$phbF^I;Oae?1!KG=8K60Dg!1-}SeFIxE(jl>>VQ z69ZnsiounD%UCD4Jdh2W4TtgkKg9oO17auPDnMH-x2Pb`zq2hb008~V`XBS>SWD3> K)Lz2B#eV@}m252l From ed53ef2f64dc35628a794798ecc2c11f299ba8c3 Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Mon, 10 Nov 2025 18:35:37 +0800 Subject: [PATCH 099/104] Update bioyond_cell and YAML configurations: modified default Excel paths and added new bottle carrier resources. Removed unused fields and updated descriptions for clarity. --- .../bioyond_cell/bioyond_cell_workstation.py | 32 +-- unilabos/registry/devices/bioyond_cell.yaml | 52 +++- .../devices/coin_cell_workstation.yaml | 14 +- unilabos/registry/devices/liquid_handler.yaml | 50 ++++ unilabos/registry/devices/virtual_device.yaml | 250 ++++++++++++++++++ .../registry/resources/bioyond/YB_bottle.yaml | 35 +-- .../resources/bioyond/YB_bottle_carriers.yaml | 174 ++++++------ 7 files changed, 459 insertions(+), 148 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index f49eae9..4160815 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -256,7 +256,7 @@ class BioyondCellWorkstation(BioyondWorkstation): def auto_feeding4to3( self, # ★ 修改点:默认模板路径 - xlsx_path: Optional[str] = "/Users/calvincao/Desktop/work/uni-lab-all/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx", + xlsx_path: Optional[str] = "/Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx", # ---------------- WH4 - 加样头面 (Z=1, 12个点位) ---------------- WH4_x1_y1_z1_1_materialName: str = "", WH4_x1_y1_z1_1_quantity: float = 0.0, WH4_x2_y1_z1_2_materialName: str = "", WH4_x2_y1_z1_2_quantity: float = 0.0, @@ -323,7 +323,6 @@ class BioyondCellWorkstation(BioyondWorkstation): "posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]), "materialName": str(row[5]).strip(), "quantity": float(row[6]) if pd.notna(row[6]) else 0.0, - "temperature": 0, }) # 四号手套箱原液瓶面 for _, row in df.iloc[14:23, 2:9].iterrows(): @@ -335,7 +334,6 @@ class BioyondCellWorkstation(BioyondWorkstation): "quantity": float(row[6]) if pd.notna(row[6]) else 0.0, "materialType": str(row[7]).strip() if pd.notna(row[7]) else "", "targetWH": str(row[8]).strip() if pd.notna(row[8]) else "", - "temperature": 0, }) # 三号手套箱人工堆栈 for _, row in df.iloc[25:40, 2:7].iterrows(): @@ -345,12 +343,11 @@ class BioyondCellWorkstation(BioyondWorkstation): "posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]), "materialType": str(row[5]).strip() if pd.notna(row[5]) else "", "materialId": str(row[6]).strip() if pd.notna(row[6]) else "", - "quantity": 1, - "temperature": 0, + "quantity": 1 }) else: logger.warning(f"未找到 Excel 文件 {xlsx_path},自动切换到手动参数模式。") - # TODO: 温度下面手动模式没改,上面的改了 + # ---------- 模式 2: 手动填写 ---------- if not items: params = locals() @@ -476,7 +473,7 @@ class BioyondCellWorkstation(BioyondWorkstation): - totalMass 自动计算为所有物料质量之和 - createTime 缺失或为空时自动填充为当前日期(YYYY/M/D) """ - default_path = Path("/Users/calvincao/Desktop/work/uni-lab-all/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx") + default_path = Path("/Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx") path = Path(xlsx_path) if xlsx_path else default_path print(f"[create_orders] 使用 Excel 路径: {path}") if path != default_path: @@ -1163,23 +1160,20 @@ if __name__ == "__main__": lab_registry.setup() deck = BIOYOND_YB_Deck(setup=True) ws = BioyondCellWorkstation(deck=deck) - # ws.update_push_ip() #直接修改奔耀端的报送ip地址 - # ws.create_sample(name="配液瓶", board_type="配液瓶(小)板", bottle_type="配液瓶(小)", location_code="E01") - # ws.create_sample(name="分液瓶", board_type="5ml分液瓶板", bottle_type="5ml分液瓶", location_code="D01") - - # # logger.info(ws.scheduler_stop()) + # ws.create_sample(name="test", board_type="配液瓶(小)板", bottle_type="配液瓶(小)", location_code="B01") + # logger.info(ws.scheduler_stop()) # logger.info(ws.scheduler_start()) - # logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 - # 使用正斜杠或 Path 对象来指定文件路径 - # excel_path = Path("/Users/calvincao/Desktop/work/uni-lab-all/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx") + # 继续后续流程 + logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 + # # # 使用正斜杠或 Path 对象来指定文件路径 + # excel_path = Path("unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025092701.xlsx") # logger.info(ws.create_orders(excel_path)) # logger.info(ws.transfer_3_to_2_to_1()) - # logger.info(ws.transfer_1_to_2()) - # 1. location code - # 2. 实验文件 - # 3. material template file + # logger.info(ws.transfer_1_to_2()) + # logger.info(ws.scheduler_start()) + while True: time.sleep(1) diff --git a/unilabos/registry/devices/bioyond_cell.yaml b/unilabos/registry/devices/bioyond_cell.yaml index c6ad669..bcfdfff 100644 --- a/unilabos/registry/devices/bioyond_cell.yaml +++ b/unilabos/registry/devices/bioyond_cell.yaml @@ -4,7 +4,6 @@ bioyond_cell: class: action_value_mappings: auto-auto_batch_outbound_from_xlsx: - display_name: 批量导入上料 feedback: {} goal: {} goal_default: @@ -138,7 +137,7 @@ bioyond_cell: WH4_x5_y1_z1_5_quantity: 0.0 WH4_x5_y2_z1_10_materialName: '' WH4_x5_y2_z1_10_quantity: 0.0 - xlsx_path: C:/ML/GitHub/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx + xlsx_path: /Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx handles: {} placeholder_keys: {} result: {} @@ -464,7 +463,7 @@ bioyond_cell: default: 0.0 type: number xlsx_path: - default: C:/ML/GitHub/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx + default: /Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx type: string required: [] type: object @@ -600,6 +599,7 @@ bioyond_cell: bottle_type: null location_code: null name: null + warehouse_name: 手动堆栈 handles: {} placeholder_keys: {} result: {} @@ -617,6 +617,9 @@ bioyond_cell: type: string name: type: string + warehouse_name: + default: 手动堆栈 + type: string required: - name - board_type @@ -785,6 +788,39 @@ bioyond_cell: title: report_material_change参数 type: object type: UniLabJsonCommand + auto-resource_tree_transfer: + feedback: {} + goal: {} + goal_default: + old_parent: null + parent_resource: null + plr_resource: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + old_parent: + type: object + parent_resource: + type: object + plr_resource: + type: object + required: + - old_parent + - plr_resource + - parent_resource + type: object + result: {} + required: + - goal + title: resource_tree_transfer参数 + type: object + type: UniLabJsonCommand auto-scheduler_continue: feedback: {} goal: {} @@ -1072,7 +1108,8 @@ bioyond_cell: type: object type: UniLabJsonCommand module: unilabos.devices.workstation.bioyond_studio.bioyond_cell.bioyond_cell_workstation:BioyondCellWorkstation - status_types: {} + status_types: + device_id: String type: python config_info: [] description: '' @@ -1090,8 +1127,11 @@ bioyond_cell: required: [] type: object data: - properties: {} - required: [] + properties: + device_id: + type: string + required: + - device_id type: object registry_type: device version: 1.0.0 diff --git a/unilabos/registry/devices/coin_cell_workstation.yaml b/unilabos/registry/devices/coin_cell_workstation.yaml index be2bcaa..4a39afa 100644 --- a/unilabos/registry/devices/coin_cell_workstation.yaml +++ b/unilabos/registry/devices/coin_cell_workstation.yaml @@ -79,7 +79,7 @@ coincellassemblyworkstation_device: elec_num: null elec_use_num: null elec_vol: 50 - file_path: C:\Users\67484\Desktop + file_path: /Users/sml/work handles: {} placeholder_keys: {} result: {} @@ -103,7 +103,7 @@ coincellassemblyworkstation_device: default: 50 type: integer file_path: - default: C:\Users\67484\Desktop + default: /Users/sml/work type: string required: - elec_num @@ -332,7 +332,7 @@ coincellassemblyworkstation_device: feedback: {} goal: {} goal_default: - file_path: D:\coin_cell_data + file_path: /Users/sml/work handles: {} placeholder_keys: {} result: {} @@ -343,7 +343,7 @@ coincellassemblyworkstation_device: goal: properties: file_path: - default: D:\coin_cell_data + default: /Users/sml/work type: string required: [] type: object @@ -507,13 +507,15 @@ coincellassemblyworkstation_device: config: properties: address: - default: 172.21.32.111 + default: 172.16.28.102 type: string + config: + type: object debug_mode: default: false type: boolean deck: - type: object + type: string port: default: '502' type: string diff --git a/unilabos/registry/devices/liquid_handler.yaml b/unilabos/registry/devices/liquid_handler.yaml index 99c9233..bfe98e8 100644 --- a/unilabos/registry/devices/liquid_handler.yaml +++ b/unilabos/registry/devices/liquid_handler.yaml @@ -654,6 +654,31 @@ liquid_handler: title: iter_tips参数 type: object type: UniLabJsonCommand + auto-post_init: + feedback: {} + goal: {} + goal_default: + ros_node: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + ros_node: + type: string + required: + - ros_node + type: object + result: {} + required: + - goal + title: post_init参数 + type: object + type: UniLabJsonCommand auto-set_group: feedback: {} goal: {} @@ -6170,6 +6195,31 @@ liquid_handler.prcxi: title: move_to参数 type: object type: UniLabJsonCommandAsync + auto-post_init: + feedback: {} + goal: {} + goal_default: + ros_node: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + ros_node: + type: object + required: + - ros_node + type: object + result: {} + required: + - goal + title: post_init参数 + type: object + type: UniLabJsonCommand auto-run_protocol: feedback: {} goal: {} diff --git a/unilabos/registry/devices/virtual_device.yaml b/unilabos/registry/devices/virtual_device.yaml index 1857582..77ac533 100644 --- a/unilabos/registry/devices/virtual_device.yaml +++ b/unilabos/registry/devices/virtual_device.yaml @@ -45,6 +45,31 @@ virtual_centrifuge: title: initialize参数 type: object type: UniLabJsonCommandAsync + auto-post_init: + feedback: {} + goal: {} + goal_default: + ros_node: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + ros_node: + type: object + required: + - ros_node + type: object + result: {} + required: + - goal + title: post_init参数 + type: object + type: UniLabJsonCommand centrifuge: feedback: current_speed: current_speed @@ -335,6 +360,31 @@ virtual_column: title: initialize参数 type: object type: UniLabJsonCommandAsync + auto-post_init: + feedback: {} + goal: {} + goal_default: + ros_node: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + ros_node: + type: object + required: + - ros_node + type: object + result: {} + required: + - goal + title: post_init参数 + type: object + type: UniLabJsonCommand run_column: feedback: current_status: current_status @@ -732,6 +782,31 @@ virtual_filter: title: initialize参数 type: object type: UniLabJsonCommandAsync + auto-post_init: + feedback: {} + goal: {} + goal_default: + ros_node: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + ros_node: + type: object + required: + - ros_node + type: object + result: {} + required: + - goal + title: post_init参数 + type: object + type: UniLabJsonCommand filter: feedback: current_status: current_status @@ -1358,6 +1433,31 @@ virtual_heatchill: title: initialize参数 type: object type: UniLabJsonCommandAsync + auto-post_init: + feedback: {} + goal: {} + goal_default: + ros_node: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + ros_node: + type: object + required: + - ros_node + type: object + result: {} + required: + - goal + title: post_init参数 + type: object + type: UniLabJsonCommand heat_chill: feedback: status: status @@ -2358,6 +2458,31 @@ virtual_rotavap: title: initialize参数 type: object type: UniLabJsonCommandAsync + auto-post_init: + feedback: {} + goal: {} + goal_default: + ros_node: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + ros_node: + type: object + required: + - ros_node + type: object + result: {} + required: + - goal + title: post_init参数 + type: object + type: UniLabJsonCommand evaporate: feedback: current_device: current_device @@ -2690,6 +2815,31 @@ virtual_separator: title: initialize参数 type: object type: UniLabJsonCommandAsync + auto-post_init: + feedback: {} + goal: {} + goal_default: + ros_node: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + ros_node: + type: object + required: + - ros_node + type: object + result: {} + required: + - goal + title: post_init参数 + type: object + type: UniLabJsonCommand separate: feedback: current_status: status @@ -3600,6 +3750,31 @@ virtual_solenoid_valve: title: is_closed参数 type: object type: UniLabJsonCommand + auto-post_init: + feedback: {} + goal: {} + goal_default: + ros_node: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + ros_node: + type: object + required: + - ros_node + type: object + result: {} + required: + - goal + title: post_init参数 + type: object + type: UniLabJsonCommand auto-reset: feedback: {} goal: {} @@ -4177,6 +4352,31 @@ virtual_solid_dispenser: title: parse_mol_string参数 type: object type: UniLabJsonCommand + auto-post_init: + feedback: {} + goal: {} + goal_default: + ros_node: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + ros_node: + type: object + required: + - ros_node + type: object + result: {} + required: + - goal + title: post_init参数 + type: object + type: UniLabJsonCommand module: unilabos.devices.virtual.virtual_solid_dispenser:VirtualSolidDispenser status_types: current_reagent: str @@ -4278,6 +4478,31 @@ virtual_stirrer: title: initialize参数 type: object type: UniLabJsonCommandAsync + auto-post_init: + feedback: {} + goal: {} + goal_default: + ros_node: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + ros_node: + type: object + required: + - ros_node + type: object + result: {} + required: + - goal + title: post_init参数 + type: object + type: UniLabJsonCommand start_stir: feedback: status: status @@ -4995,6 +5220,31 @@ virtual_transfer_pump: title: is_full参数 type: object type: UniLabJsonCommand + auto-post_init: + feedback: {} + goal: {} + goal_default: + ros_node: null + handles: {} + placeholder_keys: {} + result: {} + schema: + description: '' + properties: + feedback: {} + goal: + properties: + ros_node: + type: object + required: + - ros_node + type: object + result: {} + required: + - goal + title: post_init参数 + type: object + type: UniLabJsonCommand auto-pull_plunger: feedback: {} goal: {} diff --git a/unilabos/registry/resources/bioyond/YB_bottle.yaml b/unilabos/registry/resources/bioyond/YB_bottle.yaml index 87a335b..f8e1726 100644 --- a/unilabos/registry/resources/bioyond/YB_bottle.yaml +++ b/unilabos/registry/resources/bioyond/YB_bottle.yaml @@ -1,16 +1,3 @@ -YB_qiang_tou: - category: - - yb3 - - YB_bottle - class: - module: unilabos.resources.bioyond.YB_bottles:YB_qiang_tou - type: pylabrobot - description: YB_qiang_tou - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 YB_20ml_fenyeping: category: - yb3 @@ -37,6 +24,19 @@ YB_5ml_fenyeping: init_param_schema: {} registry_type: resource version: 1.0.0 +YB_jia_yang_tou_da: + category: + - yb3 + - YB_bottle + class: + module: unilabos.resources.bioyond.YB_bottles:YB_jia_yang_tou_da + type: pylabrobot + description: YB_jia_yang_tou_da + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 YB_pei_ye_da_Bottle: category: - yb3 @@ -63,14 +63,14 @@ YB_pei_ye_xiao_Bottle: init_param_schema: {} registry_type: resource version: 1.0.0 -YB_jia_yang_tou_da: +YB_qiang_tou: category: - yb3 - YB_bottle class: - module: unilabos.resources.bioyond.YB_bottles:YB_jia_yang_tou_da + module: unilabos.resources.bioyond.YB_bottles:YB_qiang_tou type: pylabrobot - description: YB_jia_yang_tou_da + description: YB_qiang_tou handles: [] icon: '' init_param_schema: {} @@ -80,6 +80,7 @@ YB_ye_Bottle: category: - yb3 - YB_bottle_carriers + - YB_bottle class: module: unilabos.resources.bioyond.YB_bottles:YB_ye_Bottle type: pylabrobot @@ -88,4 +89,4 @@ YB_ye_Bottle: icon: '' init_param_schema: {} registry_type: resource - version: 1.0.0 \ No newline at end of file + version: 1.0.0 diff --git a/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml b/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml index 2c0c6f0..4698a26 100644 --- a/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml +++ b/unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml @@ -11,71 +11,6 @@ YB_100ml_yeti: init_param_schema: {} registry_type: resource version: 1.0.0 -# YB_1BottleCarrier: -# category: -# - yb3 -# - YB_bottle_carriers -# class: -# module: unilabos.resources.bioyond.YB_bottle_carriers:YB_1BottleCarrier -# type: pylabrobot -# description: YB_1BottleCarrier -# handles: [] -# icon: '' -# init_param_schema: {} -# registry_type: resource -# version: 1.0.0 -YB_gaonianye: - category: - - yb3 - - YB_bottle_carriers - class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_gaonianye - type: pylabrobot - description: YB_gaonianye - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -YB_peiyepingdaban: - category: - - yb3 - - YB_bottle_carriers - class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_peiyepingdaban - type: pylabrobot - description: YB_peiyepingdaban - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -YB_6StockCarrier: - category: - - yb3 - - YB_bottle_carriers - class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6StockCarrier - type: pylabrobot - description: YB_6StockCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -YB_6VialCarrier: - category: - - yb3 - - YB_bottle_carriers - class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6VialCarrier - type: pylabrobot - description: YB_6VialCarrier - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 YB_20ml_fenyepingban: category: - yb3 @@ -102,40 +37,27 @@ YB_5ml_fenyepingban: init_param_schema: {} registry_type: resource version: 1.0.0 -YB_peiyepingxiaoban: +YB_6StockCarrier: category: - yb3 - YB_bottle_carriers class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_peiyepingxiaoban + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6StockCarrier type: pylabrobot - description: YB_peiyepingxiaoban + description: YB_6StockCarrier handles: [] icon: '' init_param_schema: {} registry_type: resource version: 1.0.0 -YB_shi_pei_qi_kuai: +YB_6VialCarrier: category: - yb3 - YB_bottle_carriers class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_shi_pei_qi_kuai + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6VialCarrier type: pylabrobot - description: YB_shi_pei_qi_kuai - handles: [] - icon: '' - init_param_schema: {} - registry_type: resource - version: 1.0.0 -YB_qiang_tou_he: - category: - - yb3 - - YB_bottle_carriers - class: - module: unilabos.resources.bioyond.YB_bottle_carriers:YB_qiang_tou_he - type: pylabrobot - description: YB_qiang_tou_he + description: YB_6VialCarrier handles: [] icon: '' init_param_schema: {} @@ -154,19 +76,19 @@ YB_gao_nian_ye_Bottle: init_param_schema: {} registry_type: resource version: 1.0.0 -# YB_jia_yang_tou_da: -# category: -# - yb3 -# - YB_bottle_carriers -# class: -# module: unilabos.resources.bioyond.YB_bottles:YB_jia_yang_tou_da -# type: pylabrobot -# description: YB_jia_yang_tou_da -# handles: [] -# icon: '' -# init_param_schema: {} -# registry_type: resource -# version: 1.0.0 +YB_gaonianye: + category: + - yb3 + - YB_bottle_carriers + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_gaonianye + type: pylabrobot + description: YB_gaonianye + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 YB_jia_yang_tou_da_Carrier: category: - yb3 @@ -180,14 +102,53 @@ YB_jia_yang_tou_da_Carrier: init_param_schema: {} registry_type: resource version: 1.0.0 -YB_ye_100ml_Bottle: +YB_peiyepingdaban: category: - yb3 - YB_bottle_carriers class: - module: unilabos.resources.bioyond.YB_bottles:YB_ye_100ml_Bottle + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_peiyepingdaban type: pylabrobot - description: YB_ye_100ml_Bottle + description: YB_peiyepingdaban + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 +YB_peiyepingxiaoban: + category: + - yb3 + - YB_bottle_carriers + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_peiyepingxiaoban + type: pylabrobot + description: YB_peiyepingxiaoban + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 +YB_qiang_tou_he: + category: + - yb3 + - YB_bottle_carriers + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_qiang_tou_he + type: pylabrobot + description: YB_qiang_tou_he + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 +YB_shi_pei_qi_kuai: + category: + - yb3 + - YB_bottle_carriers + class: + module: unilabos.resources.bioyond.YB_bottle_carriers:YB_shi_pei_qi_kuai + type: pylabrobot + description: YB_shi_pei_qi_kuai handles: [] icon: '' init_param_schema: {} @@ -206,3 +167,16 @@ YB_ye: init_param_schema: {} registry_type: resource version: 1.0.0 +YB_ye_100ml_Bottle: + category: + - yb3 + - YB_bottle_carriers + class: + module: unilabos.resources.bioyond.YB_bottles:YB_ye_100ml_Bottle + type: pylabrobot + description: YB_ye_100ml_Bottle + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 From 19dffcb5db76718f7d9486e6a3be1eab989b2502 Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Mon, 10 Nov 2025 18:57:10 +0800 Subject: [PATCH 100/104] =?UTF-8?q?=E6=9B=B4=E6=96=B0YB=5FDeck=E5=A0=86?= =?UTF-8?q?=E6=A0=88=E5=9D=90=E6=A0=87=E4=BD=8D=E7=BD=AE=EF=BC=8C=E6=A0=B9?= =?UTF-8?q?=E6=8D=AE=E5=9B=BE=E7=89=87=E5=83=8F=E7=B4=A0=E5=9D=90=E6=A0=87?= =?UTF-8?q?=E6=98=A0=E5=B0=84=E5=88=B0=E5=AE=9E=E9=99=85=E5=B0=BA=E5=AF=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- unilabos/resources/bioyond/decks.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/unilabos/resources/bioyond/decks.py b/unilabos/resources/bioyond/decks.py index 98970a1..bfed3d7 100644 --- a/unilabos/resources/bioyond/decks.py +++ b/unilabos/resources/bioyond/decks.py @@ -95,13 +95,13 @@ class BIOYOND_YB_Deck(Deck): } # warehouse 的位置 self.warehouse_locations = { - "自动堆栈-左": Coordinate(-300.0, 158.0, 0.0), - "自动堆栈-右": Coordinate(4160.0, 158.0, 0.0), - "手动堆栈-左": Coordinate(-400.0, 877.0, 0.0), - "手动堆栈-右": Coordinate(4160.0, 877.0, 0.0), + "自动堆栈-左": Coordinate(-100.3, 171.5, 0.0), + "自动堆栈-右": Coordinate(3960.1, 155.9, 0.0), + "手动堆栈-左": Coordinate(-213.3, 804.4, 0.0), + "手动堆栈-右": Coordinate(3960.1, 807.6, 0.0), "粉末加样头堆栈": Coordinate(415.0, 1301.0, 0.0), - "配液站内试剂仓库": Coordinate(2162.0, 337.0, 0.0), - "试剂替换仓库": Coordinate(1173.0, 702.0, 0.0), + "配液站内试剂仓库": Coordinate(2162.0, 437.0, 0.0), + "试剂替换仓库": Coordinate(1173.0, 802.0, 0.0), } for warehouse_name, warehouse in self.warehouses.items(): From 04b578a68be1bf8a63fbbab2d49384f5eb993e19 Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Mon, 10 Nov 2025 18:57:10 +0800 Subject: [PATCH 101/104] =?UTF-8?q?=E6=9B=B4=E6=96=B0YB=5FDeck=E5=A0=86?= =?UTF-8?q?=E6=A0=88=E5=9D=90=E6=A0=87=E4=BD=8D=E7=BD=AE=EF=BC=8C=E6=A0=B9?= =?UTF-8?q?=E6=8D=AE=E5=9B=BE=E7=89=87=E5=83=8F=E7=B4=A0=E5=9D=90=E6=A0=87?= =?UTF-8?q?=E6=98=A0=E5=B0=84=E5=88=B0=E5=AE=9E=E9=99=85=E5=B0=BA=E5=AF=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- unilabos/resources/bioyond/decks.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/unilabos/resources/bioyond/decks.py b/unilabos/resources/bioyond/decks.py index 98970a1..bfed3d7 100644 --- a/unilabos/resources/bioyond/decks.py +++ b/unilabos/resources/bioyond/decks.py @@ -95,13 +95,13 @@ class BIOYOND_YB_Deck(Deck): } # warehouse 的位置 self.warehouse_locations = { - "自动堆栈-左": Coordinate(-300.0, 158.0, 0.0), - "自动堆栈-右": Coordinate(4160.0, 158.0, 0.0), - "手动堆栈-左": Coordinate(-400.0, 877.0, 0.0), - "手动堆栈-右": Coordinate(4160.0, 877.0, 0.0), + "自动堆栈-左": Coordinate(-100.3, 171.5, 0.0), + "自动堆栈-右": Coordinate(3960.1, 155.9, 0.0), + "手动堆栈-左": Coordinate(-213.3, 804.4, 0.0), + "手动堆栈-右": Coordinate(3960.1, 807.6, 0.0), "粉末加样头堆栈": Coordinate(415.0, 1301.0, 0.0), - "配液站内试剂仓库": Coordinate(2162.0, 337.0, 0.0), - "试剂替换仓库": Coordinate(1173.0, 702.0, 0.0), + "配液站内试剂仓库": Coordinate(2162.0, 437.0, 0.0), + "试剂替换仓库": Coordinate(1173.0, 802.0, 0.0), } for warehouse_name, warehouse in self.warehouses.items(): From b97be6a5d4861e9a0e3e66adaa12c3c12921de08 Mon Sep 17 00:00:00 2001 From: calvincao Date: Mon, 10 Nov 2025 21:40:02 +0800 Subject: [PATCH 102/104] =?UTF-8?q?feat(battery):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=94=B5=E6=B1=A0=E5=B7=A5=E4=BD=9C=E7=AB=99=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E4=B8=8E=E7=89=A9=E6=96=99=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改弹夹尺寸默认值,确保非空时使用实际值 - 调整new_cellconfig3c.json中设备位置和尺寸配置 - 更新CoinCellDeck的尺寸和原点坐标 -重新分配所有物料和弹夹的位置坐标 - 调整电解液缓存位和回收位坐标 - 更新物料板和tip box的布局位置 --- new_cellconfig3c.json | 23 ++++++------ .../coin_cell_assembly/YB_YH_materials.py | 35 +++++++++---------- unilabos/resources/battery/magazine.py | 6 ++-- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/new_cellconfig3c.json b/new_cellconfig3c.json index 2a5d6eb..d963f63 100644 --- a/new_cellconfig3c.json +++ b/new_cellconfig3c.json @@ -1,3 +1,4 @@ + { "nodes": [ { @@ -53,11 +54,6 @@ ], "type": "device", "class":"coincellassemblyworkstation_device", - "position": { - "x": -600, - "y": -400, - "z": 0 - }, "config": { "deck": { "data": { @@ -66,6 +62,14 @@ } }, "protocol_type": [] + }, + "position": { + "size": {"height": 1450, "width": 1450, "depth": 2100}, + "position": { + "x": -1500, + "y": 0, + "z": 0 + } } }, { @@ -75,11 +79,6 @@ "parent": "BatteryStation", "type": "deck", "class": "CoincellDeck", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, "config": { "type": "CoincellDeck", "setup": true, @@ -94,4 +93,6 @@ } ], "links": [] -} \ No newline at end of file +} + + \ No newline at end of file diff --git a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py index a39b817..bc8dc4f 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py +++ b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py @@ -540,10 +540,10 @@ class CoincellDeck(Deck): def __init__( self, name: str = "coin_cell_deck", - size_x: float = 3650.0, # 1m - size_y: float = 1550.0, # 1m - size_z: float = 2100.0, # 0.9m - origin: Coordinate = Coordinate(-4000, 2000, 0), + size_x: float = 1450.0, # 1m + size_y: float = 1450.0, # 1m + size_z: float = 100.0, # 0.9m + origin: Coordinate = Coordinate(-2200, 0, 0), category: str = "coin_cell_deck", setup: bool = False, # 是否自动执行 setup ): @@ -560,11 +560,10 @@ class CoincellDeck(Deck): """ super().__init__( name=name, - size_x=size_x, - size_y=size_y, - size_z=size_z, + size_x=1450.0, + size_y=1450.0, + size_z=100.0, origin=origin, - category=category, ) if setup: self.setup() @@ -575,32 +574,32 @@ class CoincellDeck(Deck): # 正极片(4个洞位,2x2布局) zhengji_zip = MagazineHolder_4_Cathode("正极&铝箔弹夹") - self.assign_child_resource(zhengji_zip, Coordinate(x=2799.0, y=356.0, z=0)) + self.assign_child_resource(zhengji_zip, Coordinate(x=402.0, y=830.0, z=0)) # 正极壳、平垫片(6个洞位,2x2+2布局) zhengjike_zip = MagazineHolder_6_Cathode("正极壳&平垫片弹夹") - self.assign_child_resource(zhengjike_zip, Coordinate(x=2586.0, y=1143.0, z=0)) + self.assign_child_resource(zhengjike_zip, Coordinate(x=566.0, y=272.0, z=0)) # 负极壳、弹垫片(6个洞位,2x2+2布局) fujike_zip = MagazineHolder_6_Anode("负极壳&弹垫片弹夹") - self.assign_child_resource(fujike_zip, Coordinate(x=2492.0, y=1144.0, z=0)) + self.assign_child_resource(fujike_zip, Coordinate(x=474.0, y=276.0, z=0)) # 成品弹夹(6个洞位,3x2布局) chengpindanjia_zip = MagazineHolder_6_Battery("成品弹夹") - self.assign_child_resource(chengpindanjia_zip, Coordinate(x=3112.0, y=1295.0, z=0)) + self.assign_child_resource(chengpindanjia_zip, Coordinate(x=260.0, y=156.0, z=0)) # ====================================== 物料板 ============================================ # 创建物料板(料盘carrier)- 4x4布局 # 负极料盘 fujiliaopan = MaterialPlate(name="负极料盘", size_x=120, size_y=100, size_z=10.0, fill=True) - self.assign_child_resource(fujiliaopan, Coordinate(x=2107.0, y=304.0, z=0)) + self.assign_child_resource(fujiliaopan, Coordinate(x=708.0, y=794.0, z=0)) # for i in range(16): # fujipian = ElectrodeSheet(name=f"{fujiliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) # fujiliaopan.children[i].assign_child_resource(fujipian, location=None) # 隔膜料盘 gemoliaopan = MaterialPlate(name="隔膜料盘", size_x=120, size_y=100, size_z=10.0, fill=True) - self.assign_child_resource(gemoliaopan, Coordinate(x=2107.0, y=146.0, z=0)) + self.assign_child_resource(gemoliaopan, Coordinate(x=718.0, y=918.0, z=0)) # for i in range(16): # gemopian = ElectrodeSheet(name=f"{gemoliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) # gemoliaopan.children[i].assign_child_resource(gemopian, location=None) @@ -623,16 +622,16 @@ class CoincellDeck(Deck): # 电解液缓存位 - 6x2布局 bottle_rack_6x2 = YIHUA_Electrolyte_12VialCarrier(name="bottle_rack_6x2") - self.assign_child_resource(bottle_rack_6x2, Coordinate(x=300, y=300, z=0)) + self.assign_child_resource(bottle_rack_6x2, Coordinate(x=1050.0, y=358.0, z=0)) # 电解液回收位6x2 bottle_rack_6x2_2 = YIHUA_Electrolyte_12VialCarrier(name="bottle_rack_6x2_2") - self.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=1765.0, y=869.0, z=0)) + self.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=914.0, y=358.0, z=0)) tip_box = TipBox64(name="tip_box_64") - self.assign_child_resource(tip_box, Coordinate(x=1938.0, y=743.0, z=0)) + self.assign_child_resource(tip_box, Coordinate(x=782.0, y=514.0, z=0)) waste_tip_box = WasteTipBox(name="waste_tip_box") - self.assign_child_resource(waste_tip_box, Coordinate(x=1960.0, y=639.0, z=0)) + self.assign_child_resource(waste_tip_box, Coordinate(x=778.0, y=622.0, z=0)) if __name__ == "__main__": diff --git a/unilabos/resources/battery/magazine.py b/unilabos/resources/battery/magazine.py index d3ffcd8..04328a4 100644 --- a/unilabos/resources/battery/magazine.py +++ b/unilabos/resources/battery/magazine.py @@ -55,9 +55,9 @@ class Magazine(ResourceStack): def serialize(self) -> dict: return { **super().serialize(), - "size_x": self.size_x, - "size_y": self.size_y, - "size_z": self.size_z, + "size_x": self.size_x or 10.0, + "size_y": self.size_y or 10.0, + "size_z": self.size_z or 10.0, "max_sheets": self.max_sheets, } From c03abb341a8502cceaf5b97c4661ec158667e19f Mon Sep 17 00:00:00 2001 From: lixinyu1011 <674842481@qq.com> Date: Sun, 16 Nov 2025 16:24:59 +0800 Subject: [PATCH 103/104] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=A5=94=E8=80=80?= =?UTF-8?q?=E8=BE=93=E5=85=A5=E9=85=8D=E6=96=B9=E7=9A=84=EF=BC=8C=E7=94=B5?= =?UTF-8?q?=E8=A7=A3=E6=B6=B2=E4=BD=93=E7=A7=AF=E4=B8=BA=E5=B0=8F=E6=95=B0?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bioyond_cell/bioyond_cell_workstation.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index f49eae9..18479dc 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -11,6 +11,7 @@ from datetime import datetime, timedelta import re import threading import json +from copy import deepcopy from urllib3 import response from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation, BioyondResourceSynchronizer from unilabos.devices.workstation.bioyond_studio.config import ( @@ -547,6 +548,14 @@ class BioyondCellWorkstation(BioyondWorkstation): except Exception: return default + def _as_float(val, default=0.0) -> float: + try: + if pd.isna(val): + return default + return float(val) + except Exception: + return default + def _as_str(val, default="") -> str: if val is None or (isinstance(val, float) and pd.isna(val)): return default @@ -580,9 +589,9 @@ class BioyondCellWorkstation(BioyondWorkstation): "createTime": _to_ymd_slash(row[col_create_time]) if col_create_time else _to_ymd_slash(None), "bottleType": _as_str(row[col_bottle_type], default="配液小瓶") if col_bottle_type else "配液小瓶", "mixTime": _as_int(row[col_mix_time]) if col_mix_time else 0, - "loadSheddingInfo": _as_int(row[col_load]) if col_load else 0, - "pouchCellInfo": _as_int(row[col_pouch]) if col_pouch else 0, - "conductivityInfo": _as_int(row[col_cond]) if col_cond else 0, + "loadSheddingInfo": _as_float(row[col_load]) if col_load else 0.0, + "pouchCellInfo": _as_float(row[col_pouch]) if col_pouch else 0, + "conductivityInfo": _as_float(row[col_cond]) if col_cond else 0, "conductivityBottleCount": _as_int(row[col_cond_cnt]) if col_cond_cnt else 0, "materialInfos": mats, "totalMass": round(total_mass, 4) # 自动汇总 From 096875e9109dab4c0f3bf8615536c9757e481234 Mon Sep 17 00:00:00 2001 From: dijkstra402 Date: Thu, 27 Nov 2025 18:22:46 +0800 Subject: [PATCH 104/104] =?UTF-8?q?=E9=BB=98=E8=AE=A4=E4=BB=BF=E7=9C=9F?= =?UTF-8?q?=E6=9C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../workstation/bioyond_studio/config.py | 6 +- .../coin_cell_assembly/coin_cell_assembly.py | 2 +- unilabos/registry/devices/bioyond_cell.yaml | 105 ------------------ 3 files changed, 4 insertions(+), 109 deletions(-) diff --git a/unilabos/devices/workstation/bioyond_studio/config.py b/unilabos/devices/workstation/bioyond_studio/config.py index b2aed01..736ca66 100644 --- a/unilabos/devices/workstation/bioyond_studio/config.py +++ b/unilabos/devices/workstation/bioyond_studio/config.py @@ -8,8 +8,8 @@ import os # BioyondCellWorkstation 默认配置(包含所有必需参数) API_CONFIG = { # API 连接配置 - "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.1.143:44389"),#实机 - # "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.7.149:44388"),# 仿真机 + # "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.1.143:44389"),#实机 + "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.11.219:44388"),# 仿真机 "api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"), "timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")), @@ -17,7 +17,7 @@ API_CONFIG = { "report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"), # HTTP 服务配置 - "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.16.2.140"), # HTTP服务监听地址,监听计算机飞连ip地址 + "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.16.11.2"), # HTTP服务监听地址,监听计算机飞连ip地址 "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), "debug_mode": False,# 调试模式 } diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index 4df87c2..ef942e2 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -139,7 +139,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase): time.sleep(2) if not modbus_client.client.is_socket_open(): raise ValueError('modbus tcp connection failed') - self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_a.csv')) + self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_1105.csv')) self.client = modbus_client.register_node_list(self.nodes) else: print("测试模式,跳过连接") diff --git a/unilabos/registry/devices/bioyond_cell.yaml b/unilabos/registry/devices/bioyond_cell.yaml index 81d78b9..c379e27 100644 --- a/unilabos/registry/devices/bioyond_cell.yaml +++ b/unilabos/registry/devices/bioyond_cell.yaml @@ -32,111 +32,6 @@ bioyond_cell: feedback: {} goal: {} goal_default: - WH3_x1_y1_z3_1_materialId: '' - WH3_x1_y1_z3_1_materialType: '' - WH3_x1_y1_z3_1_quantity: 0 - WH3_x1_y2_z3_4_materialId: '' - WH3_x1_y2_z3_4_materialType: '' - WH3_x1_y2_z3_4_quantity: 0 - WH3_x1_y3_z3_7_materialId: '' - WH3_x1_y3_z3_7_materialType: '' - WH3_x1_y3_z3_7_quantity: 0 - WH3_x1_y4_z3_10_materialId: '' - WH3_x1_y4_z3_10_materialType: '' - WH3_x1_y4_z3_10_quantity: 0 - WH3_x1_y5_z3_13_materialId: '' - WH3_x1_y5_z3_13_materialType: '' - WH3_x1_y5_z3_13_quantity: 0 - WH3_x2_y1_z3_2_materialId: '' - WH3_x2_y1_z3_2_materialType: '' - WH3_x2_y1_z3_2_quantity: 0 - WH3_x2_y2_z3_5_materialId: '' - WH3_x2_y2_z3_5_materialType: '' - WH3_x2_y2_z3_5_quantity: 0 - WH3_x2_y3_z3_8_materialId: '' - WH3_x2_y3_z3_8_materialType: '' - WH3_x2_y3_z3_8_quantity: 0 - WH3_x2_y4_z3_11_materialId: '' - WH3_x2_y4_z3_11_materialType: '' - WH3_x2_y4_z3_11_quantity: 0 - WH3_x2_y5_z3_14_materialId: '' - WH3_x2_y5_z3_14_materialType: '' - WH3_x2_y5_z3_14_quantity: 0 - WH3_x3_y1_z3_3_materialId: '' - WH3_x3_y1_z3_3_materialType: '' - WH3_x3_y1_z3_3_quantity: 0 - WH3_x3_y2_z3_6_materialId: '' - WH3_x3_y2_z3_6_materialType: '' - WH3_x3_y2_z3_6_quantity: 0 - WH3_x3_y3_z3_9_materialId: '' - WH3_x3_y3_z3_9_materialType: '' - WH3_x3_y3_z3_9_quantity: 0 - WH3_x3_y4_z3_12_materialId: '' - WH3_x3_y4_z3_12_materialType: '' - WH3_x3_y4_z3_12_quantity: 0 - WH3_x3_y5_z3_15_materialId: '' - WH3_x3_y5_z3_15_materialType: '' - WH3_x3_y5_z3_15_quantity: 0 - WH4_x1_y1_z1_1_materialName: '' - WH4_x1_y1_z1_1_quantity: 0.0 - WH4_x1_y1_z2_1_materialName: '' - WH4_x1_y1_z2_1_materialType: '' - WH4_x1_y1_z2_1_quantity: 0.0 - WH4_x1_y1_z2_1_targetWH: '' - WH4_x1_y2_z1_6_materialName: '' - WH4_x1_y2_z1_6_quantity: 0.0 - WH4_x1_y2_z2_4_materialName: '' - WH4_x1_y2_z2_4_materialType: '' - WH4_x1_y2_z2_4_quantity: 0.0 - WH4_x1_y2_z2_4_targetWH: '' - WH4_x1_y3_z1_11_materialName: '' - WH4_x1_y3_z1_11_quantity: 0.0 - WH4_x1_y3_z2_7_materialName: '' - WH4_x1_y3_z2_7_materialType: '' - WH4_x1_y3_z2_7_quantity: 0.0 - WH4_x1_y3_z2_7_targetWH: '' - WH4_x2_y1_z1_2_materialName: '' - WH4_x2_y1_z1_2_quantity: 0.0 - WH4_x2_y1_z2_2_materialName: '' - WH4_x2_y1_z2_2_materialType: '' - WH4_x2_y1_z2_2_quantity: 0.0 - WH4_x2_y1_z2_2_targetWH: '' - WH4_x2_y2_z1_7_materialName: '' - WH4_x2_y2_z1_7_quantity: 0.0 - WH4_x2_y2_z2_5_materialName: '' - WH4_x2_y2_z2_5_materialType: '' - WH4_x2_y2_z2_5_quantity: 0.0 - WH4_x2_y2_z2_5_targetWH: '' - WH4_x2_y3_z1_12_materialName: '' - WH4_x2_y3_z1_12_quantity: 0.0 - WH4_x2_y3_z2_8_materialName: '' - WH4_x2_y3_z2_8_materialType: '' - WH4_x2_y3_z2_8_quantity: 0.0 - WH4_x2_y3_z2_8_targetWH: '' - WH4_x3_y1_z1_3_materialName: '' - WH4_x3_y1_z1_3_quantity: 0.0 - WH4_x3_y1_z2_3_materialName: '' - WH4_x3_y1_z2_3_materialType: '' - WH4_x3_y1_z2_3_quantity: 0.0 - WH4_x3_y1_z2_3_targetWH: '' - WH4_x3_y2_z1_8_materialName: '' - WH4_x3_y2_z1_8_quantity: 0.0 - WH4_x3_y2_z2_6_materialName: '' - WH4_x3_y2_z2_6_materialType: '' - WH4_x3_y2_z2_6_quantity: 0.0 - WH4_x3_y2_z2_6_targetWH: '' - WH4_x3_y3_z2_9_materialName: '' - WH4_x3_y3_z2_9_materialType: '' - WH4_x3_y3_z2_9_quantity: 0.0 - WH4_x3_y3_z2_9_targetWH: '' - WH4_x4_y1_z1_4_materialName: '' - WH4_x4_y1_z1_4_quantity: 0.0 - WH4_x4_y2_z1_9_materialName: '' - WH4_x4_y2_z1_9_quantity: 0.0 - WH4_x5_y1_z1_5_materialName: '' - WH4_x5_y1_z1_5_quantity: 0.0 - WH4_x5_y2_z1_10_materialName: '' - WH4_x5_y2_z1_10_quantity: 0.0 xlsx_path: /Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx handles: {} placeholder_keys: {}