From f872d3ef5698be3f01f642f714f1df6eee3b3e40 Mon Sep 17 00:00:00 2001 From: Junhan Chang Date: Sun, 9 Nov 2025 01:00:05 +0800 Subject: [PATCH] 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 8bb0a8de..d5f447c1 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 00000000..e86af24a --- /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 a5a15ccc..f8d24447 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