diff --git a/auto_atom/backend/mjc/mujoco_backend.py b/auto_atom/backend/mjc/mujoco_backend.py
index dc2d46b..f05fe5f 100644
--- a/auto_atom/backend/mjc/mujoco_backend.py
+++ b/auto_atom/backend/mjc/mujoco_backend.py
@@ -1019,34 +1019,29 @@ def _resolve_reference_base_pose(
For enum modes the entity's own ``default_pose`` is returned.
- For an entity-name reference, the delta-carry algorithm is applied:
- ``delta = ref_sampled * ref_default⁻¹``, then ``delta * default_pose``
- so the current entity moves with the referenced entity while
- preserving their original spatial relationship.
+ For an entity-name reference, the sampled pose of the referenced
+ entity is used as the base directly — so ranges like ``x: [-0.005,
+ 0.005]`` are interpreted as offsets from the referenced entity's
+ currently randomized pose, not from this entity's XML default.
"""
if isinstance(reference, RandomizationReference):
return default_pose
- # --- Entity-name reference: delta-carry ---
- ref_sampled = sampled_poses.get(reference)
- # Resolve the reference entity's default pose.
- if reference in self._default_object_poses:
- ref_default = self._default_object_poses[reference]
- elif reference in self._default_operator_eef_poses:
- ref_default = self._default_operator_eef_poses[reference]
- elif reference in self.object_handlers:
- ref_default = self.object_handlers[reference].get_pose()
- elif reference in self.operator_handlers:
- ref_default = self.operator_handlers[reference].get_end_effector_pose()
- else:
+ known_entity = (
+ reference in self._default_object_poses
+ or reference in self._default_operator_eef_poses
+ or reference in self.object_handlers
+ or reference in self.operator_handlers
+ )
+ if not known_entity:
raise ValueError(
f"Randomization reference '{reference}' is not a known mode "
"('relative', 'absolute_world', 'absolute_base') nor an "
"existing object/operator name."
)
+ ref_sampled = sampled_poses.get(reference)
if ref_sampled is None:
- return default_pose # entity not randomized → no delta
- delta = compose_pose(ref_sampled, inverse_pose(ref_default))
- return compose_pose(delta, default_pose)
+ return default_pose # reference not randomized → fall back
+ return ref_sampled
def _apply_randomization(self, env_mask: np.ndarray) -> None:
deps = self._randomization_dependencies()
@@ -1445,29 +1440,22 @@ def _resolve_reference_base_pose_for_env(
) -> PoseState:
if isinstance(reference, RandomizationReference):
return default_pose
- ref_sampled = sampled_poses.get(reference)
- if reference in self._default_object_poses:
- ref_default = self._default_object_poses[reference].select(env_index)
- elif reference in self._default_operator_eef_poses:
- ref_default = self._default_operator_eef_poses[reference].select(env_index)
- elif reference in self.object_handlers:
- ref_default = self.object_handlers[reference].get_pose().select(env_index)
- elif reference in self.operator_handlers:
- ref_default = (
- self.operator_handlers[reference]
- .get_end_effector_pose()
- .select(env_index)
- )
- else:
+ known_entity = (
+ reference in self._default_object_poses
+ or reference in self._default_operator_eef_poses
+ or reference in self.object_handlers
+ or reference in self.operator_handlers
+ )
+ if not known_entity:
raise ValueError(
f"Randomization reference '{reference}' is not a known mode "
"('relative', 'absolute_world', 'absolute_base') nor an existing "
"object/operator name."
)
+ ref_sampled = sampled_poses.get(reference)
if ref_sampled is None:
return default_pose
- delta = compose_pose(ref_sampled, inverse_pose(ref_default))
- return compose_pose(delta, default_pose)
+ return ref_sampled
def _current_pose_for_randomization_key(self, name: str) -> PoseState:
rand_range = self.randomization[name]
diff --git a/examples/panel_assembly/generated/demo_panel_with_objects.xml b/examples/panel_assembly/generated/demo_panel_with_objects.xml
new file mode 100644
index 0000000..c258225
--- /dev/null
+++ b/examples/panel_assembly/generated/demo_panel_with_objects.xml
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/create_initial_objects.py b/examples/panel_assembly/w1_b1/create_initial_objects.py
new file mode 100644
index 0000000..c9ad398
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/create_initial_objects.py
@@ -0,0 +1,110 @@
+"""One-time script: create the 20 initial switch XMLs in objects/.
+
+Each switch is a simple 8.6cm x 8.6cm square, 7mm thick.
+Colours are placeholder grey — edit objects/*.xml to set final colours.
+"""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+OBJECTS_DIR = Path(__file__).parent / "objects"
+
+# Geometry (metres)
+BTN_HALF = 0.043 # 43mm half-width (86mm full)
+BTN_FACE_HALF = 0.040 # 40mm half (80mm inner face)
+BEZEL_HALF_Z = 0.001 # bezel base half-thickness
+BTN_HALF_Z = 0.0035 # button half-thickness (7mm total)
+
+# Metallic colours: (bezel_rgba, button_rgba, specular, shininess)
+COLORS = {
+ "white": {
+ "bezel": "0.85 0.85 0.88 1",
+ "button": "0.95 0.95 0.97 1",
+ "specular": "0.85",
+ "shininess": "0.9",
+ },
+ "dark_gray": {
+ "bezel": "0.15 0.15 0.18 1",
+ "button": "0.25 0.25 0.28 1",
+ "specular": "0.75",
+ "shininess": "0.85",
+ },
+ "gold": {
+ "bezel": "0.65 0.53 0.15 1",
+ "button": "0.83 0.69 0.22 1",
+ "specular": "0.9",
+ "shininess": "0.95",
+ },
+ "pink": {
+ "bezel": "0.80 0.50 0.58 1",
+ "button": "0.95 0.65 0.72 1",
+ "specular": "0.8",
+ "shininess": "0.9",
+ },
+ "placeholder": {
+ "bezel": "0.30 0.30 0.33 1",
+ "button": "0.60 0.60 0.63 1",
+ "specular": "0.5",
+ "shininess": "0.5",
+ },
+}
+
+# 20 switches: (class_name, x_mm, y_mm, color)
+# 白色: 1-2,4-5,10-11,13-14,18 深灰: 3,7-9,12,15-16 金色: 6,20 粉色: 19
+SWITCHES = [
+ ("Switch_Quantity2_07", 349.8, 120.0, "white"), # 1
+ ("Switch_Quantity2_08", 233.2, 120.0, "white"), # 2
+ ("Switch_Quantity2_09", 116.6, 120.0, "dark_gray"), # 3
+ ("Switch_Quantity2_10", 0.0, 120.0, "white"), # 4
+ ("Switch_Quantity4_02", -116.6, 120.0, "white"), # 5
+ ("Switch_Quantity1_45", -233.2, 120.0, "gold"), # 6
+ ("Switch_Quantity1_46", -349.8, 120.0, "dark_gray"), # 7
+ ("Toogle_Quantity1_05", 349.8, 0.0, "dark_gray"), # 8
+ ("Switch_Quantity4_03", 233.2, 0.0, "dark_gray"), # 9
+ ("Switch_Quantity1_47", 116.6, 0.0, "white"), # 10
+ ("Switch_Quantity2_11", -116.6, 0.0, "white"), # 11
+ ("Switch_Quantity1_48", -233.2, 0.0, "dark_gray"), # 12
+ ("Switch_Quantity1_49", -349.8, 0.0, "white"), # 13
+ ("Switch_Quantity3_06", 349.8, -120.0, "white"), # 14
+ ("Toogle_Quantity2_01", 233.2, -120.0, "dark_gray"), # 15
+ ("Toogle_Quantity1_06", 116.6, -120.0, "dark_gray"), # 16
+ ("Placeholder_31", 0.0, -120.0, "placeholder"), # 17
+ ("Switch_Quantity2_12", -116.6, -120.0, "white"), # 18
+ ("Switch_Quantity2_13", -233.2, -120.0, "pink"), # 19
+ ("Switch_Quantity4_04", -349.8, -120.0, "gold"), # 20
+]
+
+
+def generate_switch_xml(class_name: str, color: str) -> str:
+ c = COLORS[color]
+ sp, sh = c["specular"], c["shininess"]
+ return f"""
+
+
+
+
+
+
+
+
+
+
+"""
+
+
+def main() -> None:
+ OBJECTS_DIR.mkdir(exist_ok=True)
+ for class_name, x, y, color in SWITCHES:
+ path = OBJECTS_DIR / f"{class_name}.xml"
+ path.write_text(generate_switch_xml(class_name, color), encoding="utf-8")
+ print(f" {path.name} ({color})")
+ print(f"\nCreated {len(SWITCHES)} switch XMLs in {OBJECTS_DIR}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/panel_assembly/w1_b1/generate_w1_b1.py b/examples/panel_assembly/w1_b1/generate_w1_b1.py
new file mode 100644
index 0000000..2f931fb
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/generate_w1_b1.py
@@ -0,0 +1,195 @@
+"""Generate w1_b1 panel assembly from objects/ XML files.
+
+Reads the 20 switch XML files from objects/ and assembles them into the
+final panel. Edit the XMLs in objects/ directly to customise geometry,
+materials, or colours, then re-run this script.
+
+Creates / overwrites:
+ - layout_w1_b1.yaml
+ - w1_b1_panel_base.xml
+ - generated/w1_b1_panel_assembly.xml
+"""
+
+from __future__ import annotations
+
+from pathlib import Path
+from xml.etree import ElementTree as ET
+
+BASE_DIR = Path(__file__).parent
+
+# ---------------------------------------------------------------------------
+# 20 switch definitions from Excel w1_b1
+# (class_name, type, x_mm, y_mm, num_buttons, wall_pos)
+# ---------------------------------------------------------------------------
+SWITCHES = [
+ ("Switch_Quantity2_07", "Switch", 349.8, 120.0, 2, (1, 1, 1)),
+ ("Switch_Quantity2_08", "Switch", 233.2, 120.0, 2, (1, 1, 2)),
+ ("Switch_Quantity2_09", "Switch", 116.6, 120.0, 2, (1, 1, 3)),
+ ("Switch_Quantity2_10", "Switch", 0.0, 120.0, 2, (1, 1, 4)),
+ ("Switch_Quantity4_02", "Switch", -116.6, 120.0, 4, (1, 1, 5)),
+ ("Switch_Quantity1_45", "Switch", -233.2, 120.0, 1, (1, 1, 6)),
+ ("Switch_Quantity1_46", "Switch", -349.8, 120.0, 1, (1, 1, 7)),
+ ("Toogle_Quantity1_05", "Toggle", 349.8, 0.0, 1, (1, 2, 1)),
+ ("Switch_Quantity4_03", "Switch", 233.2, 0.0, 4, (1, 2, 2)),
+ ("Switch_Quantity1_47", "Switch", 116.6, 0.0, 1, (1, 2, 3)),
+ ("Switch_Quantity2_11", "Switch", -116.6, 0.0, 2, (1, 2, 5)),
+ ("Switch_Quantity1_48", "Switch", -233.2, 0.0, 1, (1, 2, 6)),
+ ("Switch_Quantity1_49", "Switch", -349.8, 0.0, 1, (1, 2, 7)),
+ ("Switch_Quantity3_06", "Switch", 349.8, -120.0, 3, (1, 3, 1)),
+ ("Toogle_Quantity2_01", "Toggle", 233.2, -120.0, 2, (1, 3, 2)),
+ ("Toogle_Quantity1_06", "Toggle", 116.6, -120.0, 1, (1, 3, 3)),
+ ("Placeholder_31", "Switch", 0.0, -120.0, 1, (1, 3, 4)),
+ ("Switch_Quantity2_12", "Switch", -116.6, -120.0, 2, (1, 3, 5)),
+ ("Switch_Quantity2_13", "Switch", -233.2, -120.0, 2, (1, 3, 6)),
+ ("Switch_Quantity4_04", "Switch", -349.8, -120.0, 4, (1, 3, 7)),
+]
+
+# Panel backplate half-extents (m)
+PANEL_HALF_X = 0.958
+PANEL_HALF_Y = 0.025
+PANEL_HALF_Z = 0.470
+
+BASE_POS_Y = 30.0 # mm, placement offset from panel center
+
+
+def parse_switch_xml(xml_path: Path) -> tuple[list[ET.Element], list[ET.Element], str]:
+ """Parse a switch XML → (materials, geoms, body_name)."""
+ tree = ET.parse(xml_path)
+ root = tree.getroot()
+ materials = list(root.iter("material"))
+ body = root.find(".//worldbody/body")
+ geoms = list(body.iter("geom")) if body is not None else []
+ body_name = body.get("name", "") if body is not None else ""
+ return materials, geoms, body_name
+
+
+def generate_layout_yaml() -> str:
+ lines = [
+ "# w1_b1 switch wall layout",
+ "# 20 switches (various button counts), 3 rows x 7 columns",
+ "# Geometry defined in objects/*.xml",
+ "",
+ "panel:",
+ " xml: w1_b1_panel_base.xml",
+ " attach_to_body: panel_mount",
+ "",
+ "output_xml: generated/w1_b1_panel_assembly.xml",
+ "",
+ "layout:",
+ " coordinate_scale: 0.001 # mm -> m",
+ " face_axes: [x, z]",
+ f" base_pos: [0.0, {BASE_POS_Y}, 0.0]",
+ " default_quat: [0.7071068, -0.7071068, 0.0, 0.0]",
+ " remove_root_joints: true",
+ "",
+ "placements:",
+ ]
+ for idx, (cname, stype, x, y, nbtn, wpos) in enumerate(SWITCHES, 1):
+ w, r, c = wpos
+ lines.append(f" # {idx}. {cname} | {stype} | {nbtn} btn | wall {wpos}")
+ lines.append(f" - xml: objects/{cname}.xml")
+ lines.append(f" pos: [{x}, {y}]")
+ lines.append(f" row: {r}")
+ lines.append(f" col: {c}")
+ return "\n".join(lines) + "\n"
+
+
+def generate_panel_base_xml() -> str:
+ return f"""
+
+
+
+
+
+
+
+"""
+
+
+def generate_assembly_xml() -> str:
+ coord_scale = 0.001
+ base_y = BASE_POS_Y * coord_scale
+ quat = "0.7071068 -0.7071068 0 0"
+ objects_dir = BASE_DIR / "objects"
+
+ asset_lines: list[str] = []
+ slot_lines: list[str] = []
+
+ for idx, (cname, stype, x_mm, y_mm, nbtn, wpos) in enumerate(SWITCHES, 1):
+ xml_path = objects_dir / f"{cname}.xml"
+ if not xml_path.exists():
+ print(f" WARNING: {xml_path.name} not found, skipping #{idx}")
+ continue
+
+ materials, geoms, body_name = parse_switch_xml(xml_path)
+ w, r, c = wpos
+ x_m = x_mm * coord_scale
+ z_m = y_mm * coord_scale
+ slot_name = f"panel_slot_r{r}_c{c}"
+ assembled_body = f"{body_name}_r{r}_c{c}" if body_name else f"{cname}_r{r}_c{c}"
+
+ for mat in materials:
+ asset_lines.append(f" {ET.tostring(mat, encoding='unicode').strip()}")
+
+ slot_lines.append(
+ f'
'
+ )
+ slot_lines.append(f' ')
+ for geom in geoms:
+ orig = geom.get("name", "")
+ if orig:
+ geom.set("name", f"{orig}__{slot_name}")
+ slot_lines.append(
+ f" {ET.tostring(geom, encoding='unicode').strip()}"
+ )
+ slot_lines.append(" ")
+ slot_lines.append(" ")
+
+ return f"""
+
+
+{chr(10).join(asset_lines)}
+
+
+
+
+
+{chr(10).join(slot_lines)}
+
+
+"""
+
+
+def main() -> None:
+ objects_dir = BASE_DIR / "objects"
+ if not objects_dir.exists():
+ print(f"ERROR: {objects_dir} does not exist.")
+ return
+
+ (BASE_DIR / "generated").mkdir(exist_ok=True)
+
+ layout_path = BASE_DIR / "layout_w1_b1.yaml"
+ layout_path.write_text(generate_layout_yaml(), encoding="utf-8")
+ print(f" Created {layout_path.name}")
+
+ base_path = BASE_DIR / "w1_b1_panel_base.xml"
+ base_path.write_text(generate_panel_base_xml(), encoding="utf-8")
+ print(f" Created {base_path.name}")
+
+ assembly_path = BASE_DIR / "generated" / "w1_b1_panel_assembly.xml"
+ assembly_path.write_text(generate_assembly_xml(), encoding="utf-8")
+ print(f" Created generated/{assembly_path.name}")
+
+ print(f"\nDone. Assembly built from {len(SWITCHES)} switches in objects/")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/panel_assembly/w1_b1/generated/w1_b1_panel_assembly.xml b/examples/panel_assembly/w1_b1/generated/w1_b1_panel_assembly.xml
new file mode 100644
index 0000000..7eb1c2d
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/generated/w1_b1_panel_assembly.xml
@@ -0,0 +1,185 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/layout_w1_b1.yaml b/examples/panel_assembly/w1_b1/layout_w1_b1.yaml
new file mode 100644
index 0000000..2a0fa24
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/layout_w1_b1.yaml
@@ -0,0 +1,118 @@
+# w1_b1 switch wall layout
+# 20 switches (various button counts), 3 rows x 7 columns
+# Geometry defined in objects/*.xml
+
+panel:
+ xml: w1_b1_panel_base.xml
+ attach_to_body: panel_mount
+
+output_xml: generated/w1_b1_panel_assembly.xml
+
+layout:
+ coordinate_scale: 0.001 # mm -> m
+ face_axes: [x, z]
+ base_pos: [0.0, 30.0, 0.0]
+ default_quat: [0.7071068, -0.7071068, 0.0, 0.0]
+ remove_root_joints: true
+
+placements:
+ # 1. Switch_Quantity2_07 | Switch | 2 btn | wall (1, 1, 1)
+ - xml: objects/Switch_Quantity2_07.xml
+ pos: [349.8, 120.0]
+ row: 1
+ col: 1
+ # 2. Switch_Quantity2_08 | Switch | 2 btn | wall (1, 1, 2)
+ - xml: objects/Switch_Quantity2_08.xml
+ pos: [233.2, 120.0]
+ row: 1
+ col: 2
+ # 3. Switch_Quantity2_09 | Switch | 2 btn | wall (1, 1, 3)
+ - xml: objects/Switch_Quantity2_09.xml
+ pos: [116.6, 120.0]
+ row: 1
+ col: 3
+ # 4. Switch_Quantity2_10 | Switch | 2 btn | wall (1, 1, 4)
+ - xml: objects/Switch_Quantity2_10.xml
+ pos: [0.0, 120.0]
+ row: 1
+ col: 4
+ # 5. Switch_Quantity4_02 | Switch | 4 btn | wall (1, 1, 5)
+ - xml: objects/Switch_Quantity4_02.xml
+ pos: [-116.6, 120.0]
+ row: 1
+ col: 5
+ # 6. Switch_Quantity1_45 | Switch | 1 btn | wall (1, 1, 6)
+ - xml: objects/Switch_Quantity1_45.xml
+ pos: [-233.2, 120.0]
+ row: 1
+ col: 6
+ # 7. Switch_Quantity1_46 | Switch | 1 btn | wall (1, 1, 7)
+ - xml: objects/Switch_Quantity1_46.xml
+ pos: [-349.8, 120.0]
+ row: 1
+ col: 7
+ # 8. Toogle_Quantity1_05 | Toggle | 1 btn | wall (1, 2, 1)
+ - xml: objects/Toogle_Quantity1_05.xml
+ pos: [349.8, 0.0]
+ row: 2
+ col: 1
+ # 9. Switch_Quantity4_03 | Switch | 4 btn | wall (1, 2, 2)
+ - xml: objects/Switch_Quantity4_03.xml
+ pos: [233.2, 0.0]
+ row: 2
+ col: 2
+ # 10. Switch_Quantity1_47 | Switch | 1 btn | wall (1, 2, 3)
+ - xml: objects/Switch_Quantity1_47.xml
+ pos: [116.6, 0.0]
+ row: 2
+ col: 3
+ # 11. Switch_Quantity2_11 | Switch | 2 btn | wall (1, 2, 5)
+ - xml: objects/Switch_Quantity2_11.xml
+ pos: [-116.6, 0.0]
+ row: 2
+ col: 5
+ # 12. Switch_Quantity1_48 | Switch | 1 btn | wall (1, 2, 6)
+ - xml: objects/Switch_Quantity1_48.xml
+ pos: [-233.2, 0.0]
+ row: 2
+ col: 6
+ # 13. Switch_Quantity1_49 | Switch | 1 btn | wall (1, 2, 7)
+ - xml: objects/Switch_Quantity1_49.xml
+ pos: [-349.8, 0.0]
+ row: 2
+ col: 7
+ # 14. Switch_Quantity3_06 | Switch | 3 btn | wall (1, 3, 1)
+ - xml: objects/Switch_Quantity3_06.xml
+ pos: [349.8, -120.0]
+ row: 3
+ col: 1
+ # 15. Toogle_Quantity2_01 | Toggle | 2 btn | wall (1, 3, 2)
+ - xml: objects/Toogle_Quantity2_01.xml
+ pos: [233.2, -120.0]
+ row: 3
+ col: 2
+ # 16. Toogle_Quantity1_06 | Toggle | 1 btn | wall (1, 3, 3)
+ - xml: objects/Toogle_Quantity1_06.xml
+ pos: [116.6, -120.0]
+ row: 3
+ col: 3
+ # 17. Placeholder_31 | Switch | 1 btn | wall (1, 3, 4)
+ - xml: objects/Placeholder_31.xml
+ pos: [0.0, -120.0]
+ row: 3
+ col: 4
+ # 18. Switch_Quantity2_12 | Switch | 2 btn | wall (1, 3, 5)
+ - xml: objects/Switch_Quantity2_12.xml
+ pos: [-116.6, -120.0]
+ row: 3
+ col: 5
+ # 19. Switch_Quantity2_13 | Switch | 2 btn | wall (1, 3, 6)
+ - xml: objects/Switch_Quantity2_13.xml
+ pos: [-233.2, -120.0]
+ row: 3
+ col: 6
+ # 20. Switch_Quantity4_04 | Switch | 4 btn | wall (1, 3, 7)
+ - xml: objects/Switch_Quantity4_04.xml
+ pos: [-349.8, -120.0]
+ row: 3
+ col: 7
diff --git a/examples/panel_assembly/w1_b1/objects/Placeholder_31.xml b/examples/panel_assembly/w1_b1/objects/Placeholder_31.xml
new file mode 100644
index 0000000..c247895
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Placeholder_31.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/objects/Switch_Quantity1_45.xml b/examples/panel_assembly/w1_b1/objects/Switch_Quantity1_45.xml
new file mode 100644
index 0000000..e63f44a
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Switch_Quantity1_45.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/objects/Switch_Quantity1_46.xml b/examples/panel_assembly/w1_b1/objects/Switch_Quantity1_46.xml
new file mode 100644
index 0000000..2e33cff
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Switch_Quantity1_46.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/objects/Switch_Quantity1_47.xml b/examples/panel_assembly/w1_b1/objects/Switch_Quantity1_47.xml
new file mode 100644
index 0000000..4a1f884
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Switch_Quantity1_47.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/objects/Switch_Quantity1_48.xml b/examples/panel_assembly/w1_b1/objects/Switch_Quantity1_48.xml
new file mode 100644
index 0000000..abd6dda
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Switch_Quantity1_48.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/objects/Switch_Quantity1_49.xml b/examples/panel_assembly/w1_b1/objects/Switch_Quantity1_49.xml
new file mode 100644
index 0000000..0e22a92
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Switch_Quantity1_49.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_07.xml b/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_07.xml
new file mode 100644
index 0000000..a26aa7f
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_07.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_08.xml b/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_08.xml
new file mode 100644
index 0000000..7007e35
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_08.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_09.xml b/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_09.xml
new file mode 100644
index 0000000..846eedc
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_09.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_10.xml b/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_10.xml
new file mode 100644
index 0000000..9e68205
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_10.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_11.xml b/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_11.xml
new file mode 100644
index 0000000..116762d
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_11.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_12.xml b/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_12.xml
new file mode 100644
index 0000000..99b8ab2
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_12.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_13.xml b/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_13.xml
new file mode 100644
index 0000000..0038b5f
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Switch_Quantity2_13.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/objects/Switch_Quantity3_06.xml b/examples/panel_assembly/w1_b1/objects/Switch_Quantity3_06.xml
new file mode 100644
index 0000000..e510c17
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Switch_Quantity3_06.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/objects/Switch_Quantity4_02.xml b/examples/panel_assembly/w1_b1/objects/Switch_Quantity4_02.xml
new file mode 100644
index 0000000..8ff721a
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Switch_Quantity4_02.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/objects/Switch_Quantity4_03.xml b/examples/panel_assembly/w1_b1/objects/Switch_Quantity4_03.xml
new file mode 100644
index 0000000..d2e8153
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Switch_Quantity4_03.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/objects/Switch_Quantity4_04.xml b/examples/panel_assembly/w1_b1/objects/Switch_Quantity4_04.xml
new file mode 100644
index 0000000..6ad09a1
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Switch_Quantity4_04.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/objects/Toogle_Quantity1_05.xml b/examples/panel_assembly/w1_b1/objects/Toogle_Quantity1_05.xml
new file mode 100644
index 0000000..1060137
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Toogle_Quantity1_05.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/objects/Toogle_Quantity1_06.xml b/examples/panel_assembly/w1_b1/objects/Toogle_Quantity1_06.xml
new file mode 100644
index 0000000..c3623ad
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Toogle_Quantity1_06.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/objects/Toogle_Quantity2_01.xml b/examples/panel_assembly/w1_b1/objects/Toogle_Quantity2_01.xml
new file mode 100644
index 0000000..d7c6f01
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/objects/Toogle_Quantity2_01.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w1_b1/view_panel.py b/examples/panel_assembly/w1_b1/view_panel.py
new file mode 100644
index 0000000..8906109
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/view_panel.py
@@ -0,0 +1,12 @@
+"""View the w1_b1 panel assembly in MuJoCo interactive viewer."""
+
+from pathlib import Path
+
+import mujoco
+import mujoco.viewer
+
+XML_PATH = Path(__file__).parent / "generated" / "w1_b1_panel_assembly.xml"
+
+model = mujoco.MjModel.from_xml_path(str(XML_PATH))
+data = mujoco.MjData(model)
+mujoco.viewer.launch(model, data)
diff --git a/examples/panel_assembly/w1_b1/w1_b1_panel_base.xml b/examples/panel_assembly/w1_b1/w1_b1_panel_base.xml
new file mode 100644
index 0000000..e9c8b6a
--- /dev/null
+++ b/examples/panel_assembly/w1_b1/w1_b1_panel_base.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b1/create_initial_objects.py b/examples/panel_assembly/w2_b1/create_initial_objects.py
new file mode 100644
index 0000000..6064064
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/create_initial_objects.py
@@ -0,0 +1,109 @@
+"""One-time script: create the initial switch XMLs for w2_b1 in objects/.
+
+Each switch is 8.6cm x 8.6cm square, 7mm thick, default black metallic.
+"""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+OBJECTS_DIR = Path(__file__).parent / "objects"
+
+# Geometry (metres)
+BTN_HALF = 0.043
+BTN_FACE_HALF = 0.040
+BEZEL_HALF_Z = 0.001
+BTN_HALF_Z = 0.0035
+
+# Default black metallic
+BEZEL_RGBA = "0.05 0.05 0.07 1"
+BTN_RGBA = "0.10 0.10 0.13 1"
+SPECULAR = "0.7"
+SHININESS = "0.8"
+
+COLORS = {
+ "white": {
+ "bezel": "0.85 0.85 0.88 1",
+ "button": "0.95 0.95 0.97 1",
+ "specular": "0.85",
+ "shininess": "0.9",
+ },
+ "dark_gray": {
+ "bezel": "0.15 0.15 0.18 1",
+ "button": "0.25 0.25 0.28 1",
+ "specular": "0.75",
+ "shininess": "0.85",
+ },
+ "gold": {
+ "bezel": "0.65 0.53 0.15 1",
+ "button": "0.83 0.69 0.22 1",
+ "specular": "0.9",
+ "shininess": "0.95",
+ },
+ "pink": {
+ "bezel": "0.80 0.50 0.58 1",
+ "button": "0.95 0.65 0.72 1",
+ "specular": "0.8",
+ "shininess": "0.9",
+ },
+ "placeholder": {
+ "bezel": "0.30 0.30 0.33 1",
+ "button": "0.60 0.60 0.63 1",
+ "specular": "0.5",
+ "shininess": "0.5",
+ },
+}
+
+
+# 14 switches with known coordinates from Excel w2_b1
+# (class_name, x_mm, y_mm, wall_pos)
+SWITCHES = [
+ ("Knob_Quantity1_02", -286.5, 160.0, (2, 1, 10)),
+ ("Knob_Quantity1_01", -356.5, 160.0, (2, 1, 11)),
+ ("Toogle_Quantity1_01", 217.5, 50.0, (2, 2, 2)),
+ ("Toogle_Quantity1_02", 111.5, 50.0, (2, 2, 3)),
+ ("Toogle_Quantity1_03", -100.5, 50.0, (2, 2, 5)),
+ ("Toogle_Quantity3_01", -206.5, 50.0, (2, 2, 6)),
+ ("Switch_Quantity3_03", -312.5, 50.0, (2, 2, 7)),
+ ("Switch_Quantity1_01", 323.5, -80.0, (2, 3, 1)),
+ ("Switch_Quantity4_01", 217.5, -80.0, (2, 3, 2)),
+ ("Switch_Quantity1_03", 111.5, -80.0, (2, 3, 3)),
+ ("Switch_Quantity2_02", 5.5, -80.0, (2, 3, 4)),
+ ("Switch_Quantity3_02", -100.5, -80.0, (2, 3, 5)),
+ ("Switch_Quantity1_05", -206.5, -80.0, (2, 3, 6)),
+ ("Switch_Quantity1_04", -312.5, -80.0, (2, 3, 7)),
+]
+
+
+def generate_switch_xml(class_name: str, color: str) -> str:
+ c = COLORS[color]
+ sp, sh = c["specular"], c["shininess"]
+ return f"""
+
+
+
+
+
+
+
+
+
+
+"""
+
+
+def main() -> None:
+ OBJECTS_DIR.mkdir(exist_ok=True)
+ for class_name, x, y, color in SWITCHES:
+ path = OBJECTS_DIR / f"{class_name}.xml"
+ path.write_text(generate_switch_xml(class_name, color), encoding="utf-8")
+ print(f" {path.name} ({color})")
+ print(f"\nCreated {len(SWITCHES)} switch XMLs in {OBJECTS_DIR}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/panel_assembly/w2_b1/generate_w2_b1.py b/examples/panel_assembly/w2_b1/generate_w2_b1.py
new file mode 100644
index 0000000..8039d02
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/generate_w2_b1.py
@@ -0,0 +1,170 @@
+"""Generate w2_b1 panel assembly from objects/ XML files.
+
+Reads switch XMLs from objects/ and assembles them into the final panel.
+Edit objects/*.xml to customise, then re-run this script.
+"""
+
+from __future__ import annotations
+
+from pathlib import Path
+from xml.etree import ElementTree as ET
+
+BASE_DIR = Path(__file__).parent
+
+# 14 switches from Excel w2_b1 (positions with known coordinates)
+# (class_name, x_mm, y_mm, wall_row, wall_col)
+SWITCHES = [
+ ("Knob_Quantity1_02", -286.5, 160.0, 1, 10),
+ ("Knob_Quantity1_01", -356.5, 160.0, 1, 11),
+ ("Toogle_Quantity1_01", 217.5, 50.0, 2, 2),
+ ("Toogle_Quantity1_02", 111.5, 50.0, 2, 3),
+ ("Toogle_Quantity1_03", -100.5, 50.0, 2, 5),
+ ("Toogle_Quantity3_01", -206.5, 50.0, 2, 6),
+ ("Switch_Quantity3_03", -312.5, 50.0, 2, 7),
+ ("Switch_Quantity1_01", 323.5, -80.0, 3, 1),
+ ("Switch_Quantity4_01", 217.5, -80.0, 3, 2),
+ ("Switch_Quantity1_03", 111.5, -80.0, 3, 3),
+ ("Switch_Quantity2_02", 5.5, -80.0, 3, 4),
+ ("Switch_Quantity3_02", -100.5, -80.0, 3, 5),
+ ("Switch_Quantity1_05", -206.5, -80.0, 3, 6),
+ ("Switch_Quantity1_04", -312.5, -80.0, 3, 7),
+]
+
+PANEL_HALF_X = 0.42
+PANEL_HALF_Y = 0.025
+PANEL_HALF_Z = 0.22
+BASE_POS_Y = 30.0
+
+
+def parse_switch_xml(xml_path: Path) -> tuple[list[ET.Element], list[ET.Element], str]:
+ tree = ET.parse(xml_path)
+ root = tree.getroot()
+ materials = list(root.iter("material"))
+ body = root.find(".//worldbody/body")
+ geoms = list(body.iter("geom")) if body is not None else []
+ body_name = body.get("name", "") if body is not None else ""
+ return materials, geoms, body_name
+
+
+def generate_layout_yaml() -> str:
+ lines = [
+ "# w2_b1 switch wall layout",
+ f"# {len(SWITCHES)} switches",
+ "",
+ "panel:",
+ " xml: w2_b1_panel_base.xml",
+ " attach_to_body: panel_mount",
+ "",
+ "output_xml: generated/w2_b1_panel_assembly.xml",
+ "",
+ "layout:",
+ " coordinate_scale: 0.001",
+ " face_axes: [x, z]",
+ f" base_pos: [0.0, {BASE_POS_Y}, 0.0]",
+ " default_quat: [0.7071068, -0.7071068, 0.0, 0.0]",
+ " remove_root_joints: true",
+ "",
+ "placements:",
+ ]
+ for idx, (cname, x, y, r, c) in enumerate(SWITCHES, 1):
+ lines.append(f" # {idx}. {cname} | wall ({r},{c})")
+ lines.append(f" - xml: objects/{cname}.xml")
+ lines.append(f" pos: [{x}, {y}]")
+ lines.append(f" row: {r}")
+ lines.append(f" col: {c}")
+ return "\n".join(lines) + "\n"
+
+
+def generate_panel_base_xml() -> str:
+ return f"""
+
+
+
+
+
+
+
+"""
+
+
+def generate_assembly_xml() -> str:
+ coord_scale = 0.001
+ base_y = BASE_POS_Y * coord_scale
+ quat = "0.7071068 -0.7071068 0 0"
+ objects_dir = BASE_DIR / "objects"
+
+ asset_lines: list[str] = []
+ slot_lines: list[str] = []
+
+ for cname, x_mm, y_mm, r, c in SWITCHES:
+ xml_path = objects_dir / f"{cname}.xml"
+ if not xml_path.exists():
+ print(f" WARNING: {xml_path.name} not found, skipping")
+ continue
+ materials, geoms, body_name = parse_switch_xml(xml_path)
+ x_m = x_mm * coord_scale
+ z_m = y_mm * coord_scale
+ slot_name = f"panel_slot_r{r}_c{c}"
+ assembled_body = f"{body_name}_r{r}_c{c}" if body_name else f"{cname}_r{r}_c{c}"
+
+ for mat in materials:
+ asset_lines.append(f" {ET.tostring(mat, encoding='unicode').strip()}")
+
+ slot_lines.append(
+ f' '
+ )
+ slot_lines.append(f' ')
+ for geom in geoms:
+ orig = geom.get("name", "")
+ if orig:
+ geom.set("name", f"{orig}__{slot_name}")
+ slot_lines.append(
+ f" {ET.tostring(geom, encoding='unicode').strip()}"
+ )
+ slot_lines.append(" ")
+ slot_lines.append(" ")
+
+ return f"""
+
+
+{chr(10).join(asset_lines)}
+
+
+
+
+
+{chr(10).join(slot_lines)}
+
+
+"""
+
+
+def main() -> None:
+ objects_dir = BASE_DIR / "objects"
+ if not objects_dir.exists():
+ print(f"ERROR: {objects_dir} does not exist.")
+ return
+ (BASE_DIR / "generated").mkdir(exist_ok=True)
+
+ (BASE_DIR / "layout_w2_b1.yaml").write_text(
+ generate_layout_yaml(), encoding="utf-8"
+ )
+ print(" Created layout_w2_b1.yaml")
+
+ (BASE_DIR / "w2_b1_panel_base.xml").write_text(
+ generate_panel_base_xml(), encoding="utf-8"
+ )
+ print(" Created w2_b1_panel_base.xml")
+
+ (BASE_DIR / "generated" / "w2_b1_panel_assembly.xml").write_text(
+ generate_assembly_xml(), encoding="utf-8"
+ )
+ print(" Created generated/w2_b1_panel_assembly.xml")
+
+ print(f"\nDone. Assembly built from {len(SWITCHES)} switches.")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/panel_assembly/w2_b1/generated/w2_b1_panel_assembly.xml b/examples/panel_assembly/w2_b1/generated/w2_b1_panel_assembly.xml
new file mode 100644
index 0000000..8a127c9
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/generated/w2_b1_panel_assembly.xml
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b1/layout_w2_b1.yaml b/examples/panel_assembly/w2_b1/layout_w2_b1.yaml
new file mode 100644
index 0000000..8b2c621
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/layout_w2_b1.yaml
@@ -0,0 +1,87 @@
+# w2_b1 switch wall layout
+# 14 switches
+
+panel:
+ xml: w2_b1_panel_base.xml
+ attach_to_body: panel_mount
+
+output_xml: generated/w2_b1_panel_assembly.xml
+
+layout:
+ coordinate_scale: 0.001
+ face_axes: [x, z]
+ base_pos: [0.0, 30.0, 0.0]
+ default_quat: [0.7071068, -0.7071068, 0.0, 0.0]
+ remove_root_joints: true
+
+placements:
+ # 1. Knob_Quantity1_02 | wall (1,10)
+ - xml: objects/Knob_Quantity1_02.xml
+ pos: [-286.5, 160.0]
+ row: 1
+ col: 10
+ # 2. Knob_Quantity1_01 | wall (1,11)
+ - xml: objects/Knob_Quantity1_01.xml
+ pos: [-356.5, 160.0]
+ row: 1
+ col: 11
+ # 3. Toogle_Quantity1_01 | wall (2,2)
+ - xml: objects/Toogle_Quantity1_01.xml
+ pos: [217.5, 50.0]
+ row: 2
+ col: 2
+ # 4. Toogle_Quantity1_02 | wall (2,3)
+ - xml: objects/Toogle_Quantity1_02.xml
+ pos: [111.5, 50.0]
+ row: 2
+ col: 3
+ # 5. Toogle_Quantity1_03 | wall (2,5)
+ - xml: objects/Toogle_Quantity1_03.xml
+ pos: [-100.5, 50.0]
+ row: 2
+ col: 5
+ # 6. Toogle_Quantity3_01 | wall (2,6)
+ - xml: objects/Toogle_Quantity3_01.xml
+ pos: [-206.5, 50.0]
+ row: 2
+ col: 6
+ # 7. Switch_Quantity3_03 | wall (2,7)
+ - xml: objects/Switch_Quantity3_03.xml
+ pos: [-312.5, 50.0]
+ row: 2
+ col: 7
+ # 8. Switch_Quantity1_01 | wall (3,1)
+ - xml: objects/Switch_Quantity1_01.xml
+ pos: [323.5, -80.0]
+ row: 3
+ col: 1
+ # 9. Switch_Quantity4_01 | wall (3,2)
+ - xml: objects/Switch_Quantity4_01.xml
+ pos: [217.5, -80.0]
+ row: 3
+ col: 2
+ # 10. Switch_Quantity1_03 | wall (3,3)
+ - xml: objects/Switch_Quantity1_03.xml
+ pos: [111.5, -80.0]
+ row: 3
+ col: 3
+ # 11. Switch_Quantity2_02 | wall (3,4)
+ - xml: objects/Switch_Quantity2_02.xml
+ pos: [5.5, -80.0]
+ row: 3
+ col: 4
+ # 12. Switch_Quantity3_02 | wall (3,5)
+ - xml: objects/Switch_Quantity3_02.xml
+ pos: [-100.5, -80.0]
+ row: 3
+ col: 5
+ # 13. Switch_Quantity1_05 | wall (3,6)
+ - xml: objects/Switch_Quantity1_05.xml
+ pos: [-206.5, -80.0]
+ row: 3
+ col: 6
+ # 14. Switch_Quantity1_04 | wall (3,7)
+ - xml: objects/Switch_Quantity1_04.xml
+ pos: [-312.5, -80.0]
+ row: 3
+ col: 7
diff --git a/examples/panel_assembly/w2_b1/objects/Knob_Quantity1_01.xml b/examples/panel_assembly/w2_b1/objects/Knob_Quantity1_01.xml
new file mode 100644
index 0000000..d52aba5
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/objects/Knob_Quantity1_01.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b1/objects/Knob_Quantity1_02.xml b/examples/panel_assembly/w2_b1/objects/Knob_Quantity1_02.xml
new file mode 100644
index 0000000..973949c
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/objects/Knob_Quantity1_02.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b1/objects/Switch_Quantity1_01.xml b/examples/panel_assembly/w2_b1/objects/Switch_Quantity1_01.xml
new file mode 100644
index 0000000..c3492f7
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/objects/Switch_Quantity1_01.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b1/objects/Switch_Quantity1_03.xml b/examples/panel_assembly/w2_b1/objects/Switch_Quantity1_03.xml
new file mode 100644
index 0000000..833652e
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/objects/Switch_Quantity1_03.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b1/objects/Switch_Quantity1_04.xml b/examples/panel_assembly/w2_b1/objects/Switch_Quantity1_04.xml
new file mode 100644
index 0000000..d5cae2a
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/objects/Switch_Quantity1_04.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b1/objects/Switch_Quantity1_05.xml b/examples/panel_assembly/w2_b1/objects/Switch_Quantity1_05.xml
new file mode 100644
index 0000000..6ef19b2
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/objects/Switch_Quantity1_05.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b1/objects/Switch_Quantity2_02.xml b/examples/panel_assembly/w2_b1/objects/Switch_Quantity2_02.xml
new file mode 100644
index 0000000..8f4b99e
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/objects/Switch_Quantity2_02.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b1/objects/Switch_Quantity3_02.xml b/examples/panel_assembly/w2_b1/objects/Switch_Quantity3_02.xml
new file mode 100644
index 0000000..88d9769
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/objects/Switch_Quantity3_02.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b1/objects/Switch_Quantity3_03.xml b/examples/panel_assembly/w2_b1/objects/Switch_Quantity3_03.xml
new file mode 100644
index 0000000..20d2ed7
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/objects/Switch_Quantity3_03.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b1/objects/Switch_Quantity4_01.xml b/examples/panel_assembly/w2_b1/objects/Switch_Quantity4_01.xml
new file mode 100644
index 0000000..df0fc14
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/objects/Switch_Quantity4_01.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b1/objects/Toogle_Quantity1_01.xml b/examples/panel_assembly/w2_b1/objects/Toogle_Quantity1_01.xml
new file mode 100644
index 0000000..f769cb0
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/objects/Toogle_Quantity1_01.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b1/objects/Toogle_Quantity1_02.xml b/examples/panel_assembly/w2_b1/objects/Toogle_Quantity1_02.xml
new file mode 100644
index 0000000..e629b66
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/objects/Toogle_Quantity1_02.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b1/objects/Toogle_Quantity1_03.xml b/examples/panel_assembly/w2_b1/objects/Toogle_Quantity1_03.xml
new file mode 100644
index 0000000..2ef5023
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/objects/Toogle_Quantity1_03.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b1/objects/Toogle_Quantity3_01.xml b/examples/panel_assembly/w2_b1/objects/Toogle_Quantity3_01.xml
new file mode 100644
index 0000000..0e8d5c8
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/objects/Toogle_Quantity3_01.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b1/view_panel.py b/examples/panel_assembly/w2_b1/view_panel.py
new file mode 100644
index 0000000..56667b8
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/view_panel.py
@@ -0,0 +1,11 @@
+"""View the w2_b1 panel assembly in MuJoCo interactive viewer."""
+
+from pathlib import Path
+
+import mujoco
+import mujoco.viewer
+
+XML_PATH = Path(__file__).parent / "generated" / "w2_b1_panel_assembly.xml"
+model = mujoco.MjModel.from_xml_path(str(XML_PATH))
+data = mujoco.MjData(model)
+mujoco.viewer.launch(model, data)
diff --git a/examples/panel_assembly/w2_b1/w2_b1_panel_base.xml b/examples/panel_assembly/w2_b1/w2_b1_panel_base.xml
new file mode 100644
index 0000000..32229e0
--- /dev/null
+++ b/examples/panel_assembly/w2_b1/w2_b1_panel_base.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/generate_layout_from_excel.py b/examples/panel_assembly/w2_b2/generate_layout_from_excel.py
new file mode 100644
index 0000000..17ec46a
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/generate_layout_from_excel.py
@@ -0,0 +1,278 @@
+"""Read the w2_b2 sheet from the panel-info Excel workbook and emit a
+panel-assembly YAML that uses the **flat-list** placements format.
+
+Usage:
+ python examples/panel_assembly/w2_b2/generate_layout_from_excel.py \
+ "C:/Users/10529/Downloads/点按工站面板信息记录.xlsx"
+
+ # With explicit output path:
+ python examples/panel_assembly/w2_b2/generate_layout_from_excel.py \
+ "C:/Users/10529/Downloads/点按工站面板信息记录.xlsx" \
+ --output examples/panel_assembly/w2_b2/layout_w2_b2.yaml
+"""
+
+from __future__ import annotations
+
+import argparse
+import math
+import re
+import warnings
+from pathlib import Path
+from typing import Any
+
+import openpyxl
+
+# ── Excel column indices (1-based) ──────────────────────────────────────
+COL_TYPE = 2 # B: Switch / Knob / Stop
+COL_SEQ = 3 # C: sequence number (1-37)
+COL_QTY = 4 # D: quantity
+COL_CLASS = 5 # E: class_name e.g. Switch_Quantity1_06
+COL_BTN_CLS = 6 # F: button_class
+COL_COORD = 8 # H: distance-from-centre coordinate
+COL_POS = 9 # I: (wall, row, col) e.g. (2,4,1)
+COL_NOTES = 11 # K: notes (size, diameter, …)
+
+# ── Known y-values per wall-row (mm) ───────────────────────────────────
+ROW_Y_MAP: dict[int, float] = {
+ 4: 120.0,
+ 5: 0.0,
+ 6: -120.0,
+}
+
+SHEET_NAME = "w2_b2"
+DATA_START_ROW = 3 # first row with actual placement data
+
+
+# ── coordinate parsing ──────────────────────────────────────────────────
+
+
+def _parse_coord(raw: Any, *, fallback_y: float | None) -> tuple[float, float]:
+ """Parse the H-column value into ``(x, y)`` in millimetres.
+
+ Three representations exist in the workbook:
+ * A string with a comma – ``"350,120"`` / ``"-70,0"`` / ``"350,-120"``
+ * A bare integer whose comma was eaten by Excel's thousands separator –
+ ``350120`` (should be ``350,120``).
+ * ``None`` – caller must handle.
+ """
+ if raw is None:
+ raise ValueError("Coordinate cell is empty.")
+
+ if isinstance(raw, str):
+ parts = [p.strip() for p in raw.split(",")]
+ if len(parts) == 2:
+ return float(parts[0]), float(parts[1])
+ raise ValueError(f"Cannot parse coordinate string: {raw!r}")
+
+ # Numeric – likely "x,yyy" eaten by thousands-separator formatting.
+ value = float(raw)
+ if fallback_y is not None:
+ # Recover x by treating the number as string-concatenation of x and y.
+ y_str = str(int(abs(fallback_y))) # e.g. "120"
+ val_str = str(int(value))
+ if val_str.endswith(y_str) and len(val_str) > len(y_str):
+ x_str = val_str[: -len(y_str)]
+ return float(x_str), fallback_y
+
+ # Fallback: truncation-based recovery (works for y = ±120).
+ x = int(value / 1000)
+ y = value - x * 1000
+ if fallback_y is not None and not math.isclose(y, fallback_y, abs_tol=1.0):
+ warnings.warn(
+ f"Recovered y={y} differs from expected y={fallback_y} for raw={raw!r}; "
+ f"using fallback y.",
+ stacklevel=2,
+ )
+ y = fallback_y
+ return float(x), float(y)
+
+
+def _parse_wall_pos(raw: Any) -> tuple[int, int, int] | None:
+ """Parse ``(wall, row, col)`` from the I-column, e.g. ``'(2,4,1)'``."""
+ if raw is None:
+ return None
+ text = str(raw).strip().strip("()")
+ parts = [p.strip() for p in text.split(",")]
+ if len(parts) != 3:
+ return None
+ return int(parts[0]), int(parts[1]), int(parts[2])
+
+
+# ── main extraction ─────────────────────────────────────────────────────
+
+
+def _extract_placements(
+ excel_path: str | Path,
+) -> list[dict[str, Any]]:
+ wb = openpyxl.load_workbook(str(excel_path), data_only=True)
+ ws = wb[SHEET_NAME]
+
+ placements: list[dict[str, Any]] = []
+ seen_cols: dict[tuple[int, int], int] = {} # (row, col) → seq for dup detection
+
+ for excel_row in range(DATA_START_ROW, ws.max_row + 1):
+ seq_raw = ws.cell(row=excel_row, column=COL_SEQ).value
+ if seq_raw is None:
+ continue
+ seq = int(seq_raw)
+
+ # ── type ──
+ type_raw = ws.cell(row=excel_row, column=COL_TYPE).value
+ if type_raw is None:
+ # Auto-correct: rows 37-39 are missing type; assume Switch.
+ type_name = "Switch"
+ warnings.warn(
+ f"seq {seq}: type is empty — auto-corrected to 'Switch'.",
+ stacklevel=2,
+ )
+ else:
+ type_name = str(type_raw).strip()
+
+ class_name = ws.cell(row=excel_row, column=COL_CLASS).value
+ if class_name is None:
+ # Build a fallback class_name from type + seq.
+ class_name = f"{type_name}_{seq}"
+ warnings.warn(
+ f"seq {seq}: class_name is empty — auto-generated '{class_name}'.",
+ stacklevel=2,
+ )
+ else:
+ class_name = str(class_name).strip()
+
+ # ── wall position (row, col) ──
+ wall_pos = _parse_wall_pos(ws.cell(row=excel_row, column=COL_POS).value)
+ if wall_pos is None:
+ warnings.warn(
+ f"seq {seq}: wall position is empty — skipping.",
+ stacklevel=2,
+ )
+ continue
+ _, w_row, w_col = wall_pos
+ fallback_y = ROW_Y_MAP.get(w_row)
+
+ # ── duplicate (row, col) detection ──
+ rc_key = (w_row, w_col)
+ if rc_key in seen_cols:
+ old_seq = seen_cols[rc_key]
+ # Auto-correct: assign next available col.
+ max_col_in_row = max(c for (r, c) in seen_cols if r == w_row)
+ w_col = max_col_in_row + 1
+ warnings.warn(
+ f"seq {seq}: position ({w_row},{rc_key[1]}) already used by seq {old_seq} "
+ f"— auto-corrected to ({w_row},{w_col}).",
+ stacklevel=2,
+ )
+ seen_cols[(w_row, w_col)] = seq
+
+ # ── coordinate ──
+ coord_raw = ws.cell(row=excel_row, column=COL_COORD).value
+ if coord_raw is None:
+ warnings.warn(
+ f"seq {seq}: coordinate is empty — skipping.",
+ stacklevel=2,
+ )
+ continue
+ x, y = _parse_coord(coord_raw, fallback_y=fallback_y)
+
+ # ── notes ──
+ notes = ws.cell(row=excel_row, column=COL_NOTES).value
+ notes_str = str(notes).strip() if notes else None
+
+ entry: dict[str, Any] = {
+ "seq": seq,
+ "type": type_name,
+ "class_name": class_name,
+ "xml": f"objects/{type_name}_{seq}.xml",
+ "pos": [x, y],
+ "row": w_row,
+ "col": w_col,
+ }
+ if notes_str:
+ entry["notes"] = notes_str
+
+ placements.append(entry)
+
+ return placements
+
+
+# ── YAML generation ─────────────────────────────────────────────────────
+
+
+def _format_yaml(placements: list[dict[str, Any]]) -> str:
+ lines: list[str] = []
+ lines.append("# Auto-generated from Excel sheet 'w2_b2'")
+ lines.append(
+ "# Re-generate: python examples/panel_assembly/w2_b2/"
+ "generate_layout_from_excel.py "
+ )
+ lines.append("")
+ lines.append("panel:")
+ lines.append(" xml: w2_b2_panel_base.xml")
+ lines.append(" attach_to_body: panel_mount")
+ lines.append("")
+ lines.append("output_xml: generated/w2_b2_panel_assembly.xml")
+ lines.append("")
+ lines.append("layout:")
+ lines.append(" coordinate_scale: 0.001 # mm -> m")
+ lines.append(" face_axes: [x, z]")
+ lines.append(" base_pos: [0.0, 30.0, 0.0]")
+ lines.append(" default_quat: [0.7071068, -0.7071068, 0.0, 0.0]")
+ lines.append(" remove_root_joints: true")
+ lines.append("")
+ lines.append("placements:")
+
+ for p in placements:
+ seq = p["seq"]
+ typ = p["type"]
+ row = p["row"]
+ col = p["col"]
+ notes = p.get("notes", "")
+ comment = f"seq {seq} | {typ} | wall ({row},{col})"
+ if notes:
+ comment += f" | {notes}"
+ lines.append(f" # {comment}")
+ lines.append(f" - xml: {p['xml']}")
+ lines.append(f" pos: [{p['pos'][0]}, {p['pos'][1]}]")
+ lines.append(f" row: {row}")
+ lines.append(f" col: {col}")
+
+ lines.append("")
+ return "\n".join(lines)
+
+
+# ── CLI ─────────────────────────────────────────────────────────────────
+
+
+def _build_parser() -> argparse.ArgumentParser:
+ parser = argparse.ArgumentParser(
+ description="Generate w2_b2 panel layout YAML from Excel workbook."
+ )
+ parser.add_argument("excel", type=Path, help="Path to the Excel workbook.")
+ parser.add_argument(
+ "--output",
+ type=Path,
+ default=None,
+ help="Output YAML path. Defaults to layout_w2_b2.yaml next to this script.",
+ )
+ return parser
+
+
+def main() -> None:
+ args = _build_parser().parse_args()
+ placements = _extract_placements(args.excel)
+
+ output = args.output or (Path(__file__).parent / "layout_w2_b2.yaml")
+ yaml_text = _format_yaml(placements)
+ output.write_text(yaml_text, encoding="utf-8")
+
+ print(f"Generated {output} with {len(placements)} placements.")
+ for p in placements:
+ print(
+ f" seq={p['seq']:>2} type={p['type']:<6} "
+ f"pos=({p['pos'][0]:>7.1f}, {p['pos'][1]:>6.1f}) "
+ f"row={p['row']} col={p['col']} xml={p['xml']}"
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/panel_assembly/w2_b2/generated/w2_b2_panel_assembly.xml b/examples/panel_assembly/w2_b2/generated/w2_b2_panel_assembly.xml
new file mode 100644
index 0000000..660b5b5
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/generated/w2_b2_panel_assembly.xml
@@ -0,0 +1,304 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/layout_w2_b2.yaml b/examples/panel_assembly/w2_b2/layout_w2_b2.yaml
new file mode 100644
index 0000000..e25f3d0
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/layout_w2_b2.yaml
@@ -0,0 +1,202 @@
+# Auto-generated from Excel sheet 'w2_b2'
+# Re-generate: python examples/panel_assembly/w2_b2/generate_layout_from_excel.py
+
+panel:
+ xml: w2_b2_panel_base.xml
+ attach_to_body: panel_mount
+
+output_xml: generated/w2_b2_panel_assembly.xml
+
+layout:
+ coordinate_scale: 0.001 # mm -> m
+ face_axes: [x, z]
+ base_pos: [0.0, 30.0, 0.0]
+ default_quat: [0.7071068, -0.7071068, 0.0, 0.0]
+ remove_root_joints: true
+
+placements:
+ # seq 1 | Switch | wall (4,1)
+ - xml: objects/Switch_1.xml
+ pos: [350.0, 120.0]
+ row: 4
+ col: 1
+ # seq 2 | Knob | wall (4,2)
+ - xml: objects/Knob_2.xml
+ pos: [294.0, 120.0]
+ row: 4
+ col: 2
+ # seq 3 | Switch | wall (4,3)
+ - xml: objects/Switch_3.xml
+ pos: [238.0, 120.0]
+ row: 4
+ col: 3
+ # seq 4 | Switch | wall (4,4) | 2.4*1.9
+ - xml: objects/Switch_4.xml
+ pos: [182.0, 120.0]
+ row: 4
+ col: 4
+ # seq 5 | Switch | wall (4,5)
+ - xml: objects/Switch_5.xml
+ pos: [126.0, 120.0]
+ row: 4
+ col: 5
+ # seq 6 | Switch | wall (4,6)
+ - xml: objects/Switch_6.xml
+ pos: [70.0, 120.0]
+ row: 4
+ col: 6
+ # seq 7 | Switch | wall (4,7)
+ - xml: objects/Switch_7.xml
+ pos: [10.0, 120.0]
+ row: 4
+ col: 7
+ # seq 8 | Switch | wall (4,8)
+ - xml: objects/Switch_8.xml
+ pos: [-50.0, 120.0]
+ row: 4
+ col: 8
+ # seq 9 | Switch | wall (4,9)
+ - xml: objects/Switch_9.xml
+ pos: [-110.0, 120.0]
+ row: 4
+ col: 9
+ # seq 10 | Switch | wall (4,10)
+ - xml: objects/Switch_10.xml
+ pos: [-170.0, 120.0]
+ row: 4
+ col: 10
+ # seq 11 | Knob | wall (4,11) | 厚30mm
+ - xml: objects/Knob_11.xml
+ pos: [-230.0, 120.0]
+ row: 4
+ col: 11
+ # seq 12 | Switch | wall (4,12) | 厚9mm
+ - xml: objects/Switch_12.xml
+ pos: [-290.0, 120.0]
+ row: 4
+ col: 12
+ # seq 13 | Switch | wall (4,13) | 厚15mm
+ - xml: objects/Switch_13.xml
+ pos: [-350.0, 120.0]
+ row: 4
+ col: 13
+ # seq 14 | Switch | wall (5,1)
+ - xml: objects/Switch_14.xml
+ pos: [350.0, 0.0]
+ row: 5
+ col: 1
+ # seq 15 | Switch | wall (5,2)
+ - xml: objects/Switch_15.xml
+ pos: [296.0, 0.0]
+ row: 5
+ col: 2
+ # seq 16 | Switch | wall (5,3)
+ - xml: objects/Switch_16.xml
+ pos: [242.0, 0.0]
+ row: 5
+ col: 3
+ # seq 17 | Switch | wall (5,4)
+ - xml: objects/Switch_17.xml
+ pos: [188.0, 0.0]
+ row: 5
+ col: 4
+ # seq 18 | Switch | wall (5,5)
+ - xml: objects/Switch_18.xml
+ pos: [134.0, 0.0]
+ row: 5
+ col: 5
+ # seq 19 | Switch | wall (5,6)
+ - xml: objects/Switch_19.xml
+ pos: [70.0, 0.0]
+ row: 5
+ col: 6
+ # seq 20 | Stop | wall (5,8)
+ - xml: objects/Stop_20.xml
+ pos: [-70.0, 0.0]
+ row: 5
+ col: 8
+ # seq 21 | Switch | wall (5,9) | 直径2.1
+ - xml: objects/Switch_21.xml
+ pos: [-140.0, 0.0]
+ row: 5
+ col: 9
+ # seq 22 | Switch | wall (5,10)
+ - xml: objects/Switch_22.xml
+ pos: [-210.0, 0.0]
+ row: 5
+ col: 10
+ # seq 23 | Switch | wall (5,11)
+ - xml: objects/Switch_23.xml
+ pos: [-280.0, 0.0]
+ row: 5
+ col: 11
+ # seq 24 | Stop | wall (5,12) | 厚20mm
+ - xml: objects/Stop_24.xml
+ pos: [-350.0, 0.0]
+ row: 5
+ col: 12
+ # seq 25 | Switch | wall (6,1) | 3*2.5
+ - xml: objects/Switch_25.xml
+ pos: [350.0, -120.0]
+ row: 6
+ col: 1
+ # seq 26 | Switch | wall (6,2)
+ - xml: objects/Switch_26.xml
+ pos: [285.0, -120.0]
+ row: 6
+ col: 2
+ # seq 27 | Switch | wall (6,3)
+ - xml: objects/Switch_27.xml
+ pos: [220.0, -120.0]
+ row: 6
+ col: 3
+ # seq 28 | Switch | wall (6,4)
+ - xml: objects/Switch_28.xml
+ pos: [155.0, -120.0]
+ row: 6
+ col: 4
+ # seq 29 | Switch | wall (6,5)
+ - xml: objects/Switch_29.xml
+ pos: [90.0, -120.0]
+ row: 6
+ col: 5
+ # seq 30 | Switch | wall (6,6)
+ - xml: objects/Switch_30.xml
+ pos: [25.0, -120.0]
+ row: 6
+ col: 6
+ # seq 31 | Switch | wall (6,7) | 2.2*1.7
+ - xml: objects/Switch_31.xml
+ pos: [-43.2, -120.0]
+ row: 6
+ col: 7
+ # seq 32 | Switch | wall (6,8)
+ - xml: objects/Switch_32.xml
+ pos: [-96.2, -120.0]
+ row: 6
+ col: 8
+ # seq 33 | Switch | wall (6,9) | 2*1.5
+ - xml: objects/Switch_33.xml
+ pos: [-149.2, -120.0]
+ row: 6
+ col: 9
+ # seq 34 | Switch | wall (6,10) | 厚6mm
+ - xml: objects/Switch_34.xml
+ pos: [-202.2, -120.0]
+ row: 6
+ col: 10
+ # seq 35 | Switch | wall (6,11)
+ - xml: objects/Switch_35.xml
+ pos: [-255.2, -120.0]
+ row: 6
+ col: 11
+ # seq 36 | Switch | wall (6,12)
+ - xml: objects/Switch_36.xml
+ pos: [-308.2, -120.0]
+ row: 6
+ col: 12
+ # seq 37 | Switch | wall (6,13)
+ - xml: objects/Switch_37.xml
+ pos: [-361.2, -120.0]
+ row: 6
+ col: 13
diff --git a/examples/panel_assembly/w2_b2/objects/Knob_11.xml b/examples/panel_assembly/w2_b2/objects/Knob_11.xml
new file mode 100644
index 0000000..c359312
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Knob_11.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Knob_2.xml b/examples/panel_assembly/w2_b2/objects/Knob_2.xml
new file mode 100644
index 0000000..fb47d66
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Knob_2.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Stop_20.xml b/examples/panel_assembly/w2_b2/objects/Stop_20.xml
new file mode 100644
index 0000000..bfbabff
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Stop_20.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Stop_24.xml b/examples/panel_assembly/w2_b2/objects/Stop_24.xml
new file mode 100644
index 0000000..9bed257
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Stop_24.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_1.xml b/examples/panel_assembly/w2_b2/objects/Switch_1.xml
new file mode 100644
index 0000000..e4bdf02
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_1.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_10.xml b/examples/panel_assembly/w2_b2/objects/Switch_10.xml
new file mode 100644
index 0000000..d1dcf12
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_10.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_12.xml b/examples/panel_assembly/w2_b2/objects/Switch_12.xml
new file mode 100644
index 0000000..4fc4aac
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_12.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_13.xml b/examples/panel_assembly/w2_b2/objects/Switch_13.xml
new file mode 100644
index 0000000..e7e1fdc
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_13.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_14.xml b/examples/panel_assembly/w2_b2/objects/Switch_14.xml
new file mode 100644
index 0000000..0b80a20
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_14.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_15.xml b/examples/panel_assembly/w2_b2/objects/Switch_15.xml
new file mode 100644
index 0000000..373bcf0
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_15.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_16.xml b/examples/panel_assembly/w2_b2/objects/Switch_16.xml
new file mode 100644
index 0000000..1ddda96
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_16.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_17.xml b/examples/panel_assembly/w2_b2/objects/Switch_17.xml
new file mode 100644
index 0000000..958d92f
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_17.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_18.xml b/examples/panel_assembly/w2_b2/objects/Switch_18.xml
new file mode 100644
index 0000000..3a61053
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_18.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_19.xml b/examples/panel_assembly/w2_b2/objects/Switch_19.xml
new file mode 100644
index 0000000..d147034
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_19.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_2.xml b/examples/panel_assembly/w2_b2/objects/Switch_2.xml
new file mode 100644
index 0000000..be1d79e
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_2.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_21.xml b/examples/panel_assembly/w2_b2/objects/Switch_21.xml
new file mode 100644
index 0000000..a16340e
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_21.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_22.xml b/examples/panel_assembly/w2_b2/objects/Switch_22.xml
new file mode 100644
index 0000000..26377df
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_22.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_23.xml b/examples/panel_assembly/w2_b2/objects/Switch_23.xml
new file mode 100644
index 0000000..b437327
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_23.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_25.xml b/examples/panel_assembly/w2_b2/objects/Switch_25.xml
new file mode 100644
index 0000000..ebb576c
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_25.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_26.xml b/examples/panel_assembly/w2_b2/objects/Switch_26.xml
new file mode 100644
index 0000000..524c9f8
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_26.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_27.xml b/examples/panel_assembly/w2_b2/objects/Switch_27.xml
new file mode 100644
index 0000000..c693bb8
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_27.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_28.xml b/examples/panel_assembly/w2_b2/objects/Switch_28.xml
new file mode 100644
index 0000000..abddf25
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_28.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_29.xml b/examples/panel_assembly/w2_b2/objects/Switch_29.xml
new file mode 100644
index 0000000..b151eb5
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_29.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_3.xml b/examples/panel_assembly/w2_b2/objects/Switch_3.xml
new file mode 100644
index 0000000..e130304
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_3.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_30.xml b/examples/panel_assembly/w2_b2/objects/Switch_30.xml
new file mode 100644
index 0000000..bdd0f12
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_30.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_31.xml b/examples/panel_assembly/w2_b2/objects/Switch_31.xml
new file mode 100644
index 0000000..cda7ad9
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_31.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_32.xml b/examples/panel_assembly/w2_b2/objects/Switch_32.xml
new file mode 100644
index 0000000..2ef0a63
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_32.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_33.xml b/examples/panel_assembly/w2_b2/objects/Switch_33.xml
new file mode 100644
index 0000000..f7b9bdb
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_33.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_34.xml b/examples/panel_assembly/w2_b2/objects/Switch_34.xml
new file mode 100644
index 0000000..d7cddf6
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_34.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_35.xml b/examples/panel_assembly/w2_b2/objects/Switch_35.xml
new file mode 100644
index 0000000..b3fe314
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_35.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_36.xml b/examples/panel_assembly/w2_b2/objects/Switch_36.xml
new file mode 100644
index 0000000..33fca5d
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_36.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_37.xml b/examples/panel_assembly/w2_b2/objects/Switch_37.xml
new file mode 100644
index 0000000..25cb3cd
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_37.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_4.xml b/examples/panel_assembly/w2_b2/objects/Switch_4.xml
new file mode 100644
index 0000000..7e0cfb5
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_4.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_5.xml b/examples/panel_assembly/w2_b2/objects/Switch_5.xml
new file mode 100644
index 0000000..fed79be
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_5.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_6.xml b/examples/panel_assembly/w2_b2/objects/Switch_6.xml
new file mode 100644
index 0000000..a9c1ed1
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_6.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_7.xml b/examples/panel_assembly/w2_b2/objects/Switch_7.xml
new file mode 100644
index 0000000..c392964
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_7.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_8.xml b/examples/panel_assembly/w2_b2/objects/Switch_8.xml
new file mode 100644
index 0000000..2adc27e
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_8.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/objects/Switch_9.xml b/examples/panel_assembly/w2_b2/objects/Switch_9.xml
new file mode 100644
index 0000000..dfe1157
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/objects/Switch_9.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w2_b2/w2_b2_panel_base.xml b/examples/panel_assembly/w2_b2/w2_b2_panel_base.xml
new file mode 100644
index 0000000..f8e45a7
--- /dev/null
+++ b/examples/panel_assembly/w2_b2/w2_b2_panel_base.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/create_initial_objects.py b/examples/panel_assembly/w3_b1/create_initial_objects.py
new file mode 100644
index 0000000..057f84d
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/create_initial_objects.py
@@ -0,0 +1,88 @@
+"""One-time script: create the initial switch XMLs for w3_b1 in objects/.
+
+Each switch is 8.6cm x 8.6cm square, 7mm thick, default black metallic.
+"""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+OBJECTS_DIR = Path(__file__).parent / "objects"
+
+# Geometry (metres)
+BTN_HALF = 0.043
+BTN_FACE_HALF = 0.040
+BEZEL_HALF_Z = 0.001
+BTN_HALF_Z = 0.0035
+
+# Default black metallic
+BEZEL_RGBA = "0.05 0.05 0.07 1"
+BTN_RGBA = "0.10 0.10 0.13 1"
+SPECULAR = "0.7"
+SHININESS = "0.8"
+
+# 26 switches from Excel w3_b1
+# (class_name, x_mm, y_mm)
+# Row 1 (y=120): 13 switches; positions 1-5,10-13 have no class_name in Excel
+SWITCHES = [
+ ("w3b1_Switch_01", 349.8, 120.0), # 1 (3,1,1)
+ ("w3b1_Switch_02", 293.8, 120.0), # 2 (3,1,2)
+ ("w3b1_Switch_03", 237.8, 120.0), # 3 (3,1,3)
+ ("w3b1_Switch_04", 181.8, 120.0), # 4 (3,1,4)
+ ("w3b1_Switch_05", 125.8, 120.0), # 5 (3,1,5)
+ ("Switch_Quantity1_36", 69.8, 120.0), # 6 (3,1,6)
+ ("Switch_Quantity1_37", 9.8, 120.0), # 7 (3,1,7)
+ ("Switch_Quantity1_38", -50.2, 120.0), # 8 (3,1,8)
+ ("Switch_Quantity1_39", -110.2, 120.0), # 9 (3,1,9)
+ ("w3b1_Switch_10", -170.2, 120.0), # 10 (3,1,10)
+ ("w3b1_Switch_11", -230.2, 120.0), # 11 (3,1,11)
+ ("w3b1_Switch_12", -290.2, 120.0), # 12 (3,1,12)
+ ("w3b1_Switch_13", -349.8, 120.0), # 13 (3,1,13)
+ # Row 2 (y=0): 6 switches (no position at col 4)
+ ("Switch_Quantity3_04", 349.8, 0.0), # 14 (3,2,1)
+ ("Switch_Quantity3_05", 233.2, 0.0), # 15 (3,2,2)
+ ("Switch_Quantity2_03", 116.6, 0.0), # 16 (3,2,3)
+ ("Switch_Quantity1_40", -116.6, 0.0), # 17 (3,2,5)
+ ("Switch_Quantity1_41", -233.2, 0.0), # 18 (3,2,6)
+ ("Switch_Quantity1_42", -349.8, 0.0), # 19 (3,2,7)
+ # Row 3 (y=-120): 7 switches
+ ("Switch_Quantity2_04", 349.8, -120.0), # 20 (3,3,1)
+ ("Switch_Quantity2_05", 233.2, -120.0), # 21 (3,3,2)
+ ("Toogle_Quantity4_01", 116.6, -120.0), # 22 (3,3,3)
+ ("Switch_Quantity2_06", 0.0, -120.0), # 23 (3,3,4)
+ ("Switch_Quantity1_43", -116.6, -120.0), # 24 (3,3,5)
+ ("Switch_Quantity1_44", -233.2, -120.0), # 25 (3,3,6)
+ ("Toogle_Quantity1_04", -349.8, -120.0), # 26 (3,3,7)
+]
+
+
+def generate_switch_xml(class_name: str) -> str:
+ return f"""
+
+
+
+
+
+
+
+
+
+
+"""
+
+
+def main() -> None:
+ OBJECTS_DIR.mkdir(exist_ok=True)
+ for class_name, x, y in SWITCHES:
+ path = OBJECTS_DIR / f"{class_name}.xml"
+ path.write_text(generate_switch_xml(class_name), encoding="utf-8")
+ print(f" {path.name}")
+ print(f"\nCreated {len(SWITCHES)} switch XMLs in {OBJECTS_DIR}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/panel_assembly/w3_b1/generate_w3_b1.py b/examples/panel_assembly/w3_b1/generate_w3_b1.py
new file mode 100644
index 0000000..54e4b5d
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/generate_w3_b1.py
@@ -0,0 +1,176 @@
+"""Generate w3_b1 panel assembly from objects/ XML files.
+
+Reads switch XMLs from objects/ and assembles them into the final panel.
+Edit objects/*.xml to customise, then re-run this script.
+"""
+
+from __future__ import annotations
+
+from pathlib import Path
+from xml.etree import ElementTree as ET
+
+BASE_DIR = Path(__file__).parent
+
+# 17 switches from Excel w3_b1 (positions 1-5, 10-13 in row 1 have no switch yet)
+# (class_name, x_mm, y_mm, wall_row, wall_col)
+SWITCHES = [
+ # Row 1 (y=120): 4 switches (cols 6-9 only)
+ ("Switch_Quantity1_36", 69.8, 120.0, 1, 6),
+ ("Switch_Quantity1_37", 9.8, 120.0, 1, 7),
+ ("Switch_Quantity1_38", -50.2, 120.0, 1, 8),
+ ("Switch_Quantity1_39", -110.2, 120.0, 1, 9),
+ # Row 2 (y=0): 6 switches (no col 4)
+ ("Switch_Quantity3_04", 349.8, 0.0, 2, 1),
+ ("Switch_Quantity3_05", 233.2, 0.0, 2, 2),
+ ("Switch_Quantity2_03", 116.6, 0.0, 2, 3),
+ ("Switch_Quantity1_40", -116.6, 0.0, 2, 5),
+ ("Switch_Quantity1_41", -233.2, 0.0, 2, 6),
+ ("Switch_Quantity1_42", -349.8, 0.0, 2, 7),
+ # Row 3 (y=-120): 7 switches
+ ("Switch_Quantity2_04", 349.8, -120.0, 3, 1),
+ ("Switch_Quantity2_05", 233.2, -120.0, 3, 2),
+ ("Toogle_Quantity4_01", 116.6, -120.0, 3, 3),
+ ("Switch_Quantity2_06", 0.0, -120.0, 3, 4),
+ ("Switch_Quantity1_43", -116.6, -120.0, 3, 5),
+ ("Switch_Quantity1_44", -233.2, -120.0, 3, 6),
+ ("Toogle_Quantity1_04", -349.8, -120.0, 3, 7),
+]
+
+PANEL_HALF_X = 0.81
+PANEL_HALF_Y = 0.025
+PANEL_HALF_Z = 0.28
+BASE_POS_Y = 30.0
+
+
+def parse_switch_xml(xml_path: Path) -> tuple[list[ET.Element], list[ET.Element], str]:
+ tree = ET.parse(xml_path)
+ root = tree.getroot()
+ materials = list(root.iter("material"))
+ body = root.find(".//worldbody/body")
+ geoms = list(body.iter("geom")) if body is not None else []
+ body_name = body.get("name", "") if body is not None else ""
+ return materials, geoms, body_name
+
+
+def generate_layout_yaml() -> str:
+ lines = [
+ "# w3_b1 switch wall layout",
+ f"# {len(SWITCHES)} switches (row 1 positions 1-5,10-13 reserved)",
+ "",
+ "panel:",
+ " xml: w3_b1_panel_base.xml",
+ " attach_to_body: panel_mount",
+ "",
+ "output_xml: generated/w3_b1_panel_assembly.xml",
+ "",
+ "layout:",
+ " coordinate_scale: 0.001",
+ " face_axes: [x, z]",
+ f" base_pos: [0.0, {BASE_POS_Y}, 0.0]",
+ " default_quat: [0.7071068, -0.7071068, 0.0, 0.0]",
+ " remove_root_joints: true",
+ "",
+ "placements:",
+ ]
+ for idx, (cname, x, y, r, c) in enumerate(SWITCHES, 1):
+ lines.append(f" # {idx}. {cname} | wall ({r},{c})")
+ lines.append(f" - xml: objects/{cname}.xml")
+ lines.append(f" pos: [{x}, {y}]")
+ lines.append(f" row: {r}")
+ lines.append(f" col: {c}")
+ return "\n".join(lines) + "\n"
+
+
+def generate_panel_base_xml() -> str:
+ return f"""
+
+
+
+
+
+
+
+"""
+
+
+def generate_assembly_xml() -> str:
+ coord_scale = 0.001
+ base_y = BASE_POS_Y * coord_scale
+ quat = "0.7071068 -0.7071068 0 0"
+ objects_dir = BASE_DIR / "objects"
+
+ asset_lines: list[str] = []
+ slot_lines: list[str] = []
+
+ for cname, x_mm, y_mm, r, c in SWITCHES:
+ xml_path = objects_dir / f"{cname}.xml"
+ if not xml_path.exists():
+ print(f" WARNING: {xml_path.name} not found, skipping")
+ continue
+ materials, geoms, body_name = parse_switch_xml(xml_path)
+ x_m = x_mm * coord_scale
+ z_m = y_mm * coord_scale
+ slot_name = f"panel_slot_r{r}_c{c}"
+ assembled_body = f"{body_name}_r{r}_c{c}" if body_name else f"{cname}_r{r}_c{c}"
+
+ for mat in materials:
+ asset_lines.append(f" {ET.tostring(mat, encoding='unicode').strip()}")
+
+ slot_lines.append(
+ f' '
+ )
+ slot_lines.append(f' ')
+ for geom in geoms:
+ orig = geom.get("name", "")
+ if orig:
+ geom.set("name", f"{orig}__{slot_name}")
+ slot_lines.append(
+ f" {ET.tostring(geom, encoding='unicode').strip()}"
+ )
+ slot_lines.append(" ")
+ slot_lines.append(" ")
+
+ return f"""
+
+
+{chr(10).join(asset_lines)}
+
+
+
+
+
+{chr(10).join(slot_lines)}
+
+
+"""
+
+
+def main() -> None:
+ objects_dir = BASE_DIR / "objects"
+ if not objects_dir.exists():
+ print(f"ERROR: {objects_dir} does not exist.")
+ return
+ (BASE_DIR / "generated").mkdir(exist_ok=True)
+
+ (BASE_DIR / "layout_w3_b1.yaml").write_text(
+ generate_layout_yaml(), encoding="utf-8"
+ )
+ print(" Created layout_w3_b1.yaml")
+
+ (BASE_DIR / "w3_b1_panel_base.xml").write_text(
+ generate_panel_base_xml(), encoding="utf-8"
+ )
+ print(" Created w3_b1_panel_base.xml")
+
+ (BASE_DIR / "generated" / "w3_b1_panel_assembly.xml").write_text(
+ generate_assembly_xml(), encoding="utf-8"
+ )
+ print(" Created generated/w3_b1_panel_assembly.xml")
+
+ print(f"\nDone. Assembly built from {len(SWITCHES)} switches.")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/panel_assembly/w3_b1/generated/w3_b1_panel_assembly.xml b/examples/panel_assembly/w3_b1/generated/w3_b1_panel_assembly.xml
new file mode 100644
index 0000000..77e7951
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/generated/w3_b1_panel_assembly.xml
@@ -0,0 +1,163 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/layout_w3_b1.yaml b/examples/panel_assembly/w3_b1/layout_w3_b1.yaml
new file mode 100644
index 0000000..a4f5053
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/layout_w3_b1.yaml
@@ -0,0 +1,102 @@
+# w3_b1 switch wall layout
+# 17 switches (row 1 positions 1-5,10-13 reserved)
+
+panel:
+ xml: w3_b1_panel_base.xml
+ attach_to_body: panel_mount
+
+output_xml: generated/w3_b1_panel_assembly.xml
+
+layout:
+ coordinate_scale: 0.001
+ face_axes: [x, z]
+ base_pos: [0.0, 30.0, 0.0]
+ default_quat: [0.7071068, -0.7071068, 0.0, 0.0]
+ remove_root_joints: true
+
+placements:
+ # 1. Switch_Quantity1_36 | wall (1,6)
+ - xml: objects/Switch_Quantity1_36.xml
+ pos: [69.8, 120.0]
+ row: 1
+ col: 6
+ # 2. Switch_Quantity1_37 | wall (1,7)
+ - xml: objects/Switch_Quantity1_37.xml
+ pos: [9.8, 120.0]
+ row: 1
+ col: 7
+ # 3. Switch_Quantity1_38 | wall (1,8)
+ - xml: objects/Switch_Quantity1_38.xml
+ pos: [-50.2, 120.0]
+ row: 1
+ col: 8
+ # 4. Switch_Quantity1_39 | wall (1,9)
+ - xml: objects/Switch_Quantity1_39.xml
+ pos: [-110.2, 120.0]
+ row: 1
+ col: 9
+ # 5. Switch_Quantity3_04 | wall (2,1)
+ - xml: objects/Switch_Quantity3_04.xml
+ pos: [349.8, 0.0]
+ row: 2
+ col: 1
+ # 6. Switch_Quantity3_05 | wall (2,2)
+ - xml: objects/Switch_Quantity3_05.xml
+ pos: [233.2, 0.0]
+ row: 2
+ col: 2
+ # 7. Switch_Quantity2_03 | wall (2,3)
+ - xml: objects/Switch_Quantity2_03.xml
+ pos: [116.6, 0.0]
+ row: 2
+ col: 3
+ # 8. Switch_Quantity1_40 | wall (2,5)
+ - xml: objects/Switch_Quantity1_40.xml
+ pos: [-116.6, 0.0]
+ row: 2
+ col: 5
+ # 9. Switch_Quantity1_41 | wall (2,6)
+ - xml: objects/Switch_Quantity1_41.xml
+ pos: [-233.2, 0.0]
+ row: 2
+ col: 6
+ # 10. Switch_Quantity1_42 | wall (2,7)
+ - xml: objects/Switch_Quantity1_42.xml
+ pos: [-349.8, 0.0]
+ row: 2
+ col: 7
+ # 11. Switch_Quantity2_04 | wall (3,1)
+ - xml: objects/Switch_Quantity2_04.xml
+ pos: [349.8, -120.0]
+ row: 3
+ col: 1
+ # 12. Switch_Quantity2_05 | wall (3,2)
+ - xml: objects/Switch_Quantity2_05.xml
+ pos: [233.2, -120.0]
+ row: 3
+ col: 2
+ # 13. Toogle_Quantity4_01 | wall (3,3)
+ - xml: objects/Toogle_Quantity4_01.xml
+ pos: [116.6, -120.0]
+ row: 3
+ col: 3
+ # 14. Switch_Quantity2_06 | wall (3,4)
+ - xml: objects/Switch_Quantity2_06.xml
+ pos: [0.0, -120.0]
+ row: 3
+ col: 4
+ # 15. Switch_Quantity1_43 | wall (3,5)
+ - xml: objects/Switch_Quantity1_43.xml
+ pos: [-116.6, -120.0]
+ row: 3
+ col: 5
+ # 16. Switch_Quantity1_44 | wall (3,6)
+ - xml: objects/Switch_Quantity1_44.xml
+ pos: [-233.2, -120.0]
+ row: 3
+ col: 6
+ # 17. Toogle_Quantity1_04 | wall (3,7)
+ - xml: objects/Toogle_Quantity1_04.xml
+ pos: [-349.8, -120.0]
+ row: 3
+ col: 7
diff --git a/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_36.xml b/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_36.xml
new file mode 100644
index 0000000..406a3a1
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_36.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_37.xml b/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_37.xml
new file mode 100644
index 0000000..19e2320
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_37.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_38.xml b/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_38.xml
new file mode 100644
index 0000000..a9be5c0
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_38.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_39.xml b/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_39.xml
new file mode 100644
index 0000000..c938366
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_39.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_40.xml b/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_40.xml
new file mode 100644
index 0000000..e6ec776
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_40.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_41.xml b/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_41.xml
new file mode 100644
index 0000000..abaac5f
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_41.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_42.xml b/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_42.xml
new file mode 100644
index 0000000..a0321dc
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_42.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_43.xml b/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_43.xml
new file mode 100644
index 0000000..67208ae
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_43.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_44.xml b/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_44.xml
new file mode 100644
index 0000000..f2ce7ec
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/Switch_Quantity1_44.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/Switch_Quantity2_03.xml b/examples/panel_assembly/w3_b1/objects/Switch_Quantity2_03.xml
new file mode 100644
index 0000000..9df7890
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/Switch_Quantity2_03.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/Switch_Quantity2_04.xml b/examples/panel_assembly/w3_b1/objects/Switch_Quantity2_04.xml
new file mode 100644
index 0000000..572e38f
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/Switch_Quantity2_04.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/Switch_Quantity2_05.xml b/examples/panel_assembly/w3_b1/objects/Switch_Quantity2_05.xml
new file mode 100644
index 0000000..50abbd2
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/Switch_Quantity2_05.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/Switch_Quantity2_06.xml b/examples/panel_assembly/w3_b1/objects/Switch_Quantity2_06.xml
new file mode 100644
index 0000000..95b319f
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/Switch_Quantity2_06.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/Switch_Quantity3_04.xml b/examples/panel_assembly/w3_b1/objects/Switch_Quantity3_04.xml
new file mode 100644
index 0000000..4160b29
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/Switch_Quantity3_04.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/Switch_Quantity3_05.xml b/examples/panel_assembly/w3_b1/objects/Switch_Quantity3_05.xml
new file mode 100644
index 0000000..522805b
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/Switch_Quantity3_05.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/Toogle_Quantity1_04.xml b/examples/panel_assembly/w3_b1/objects/Toogle_Quantity1_04.xml
new file mode 100644
index 0000000..380184c
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/Toogle_Quantity1_04.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/Toogle_Quantity4_01.xml b/examples/panel_assembly/w3_b1/objects/Toogle_Quantity4_01.xml
new file mode 100644
index 0000000..901b938
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/Toogle_Quantity4_01.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/w3b1_Switch_01.xml b/examples/panel_assembly/w3_b1/objects/w3b1_Switch_01.xml
new file mode 100644
index 0000000..ebecec1
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/w3b1_Switch_01.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/w3b1_Switch_02.xml b/examples/panel_assembly/w3_b1/objects/w3b1_Switch_02.xml
new file mode 100644
index 0000000..440bc7a
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/w3b1_Switch_02.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/w3b1_Switch_03.xml b/examples/panel_assembly/w3_b1/objects/w3b1_Switch_03.xml
new file mode 100644
index 0000000..cfbf32e
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/w3b1_Switch_03.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/w3b1_Switch_04.xml b/examples/panel_assembly/w3_b1/objects/w3b1_Switch_04.xml
new file mode 100644
index 0000000..10b60e8
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/w3b1_Switch_04.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/w3b1_Switch_05.xml b/examples/panel_assembly/w3_b1/objects/w3b1_Switch_05.xml
new file mode 100644
index 0000000..54537aa
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/w3b1_Switch_05.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/w3b1_Switch_10.xml b/examples/panel_assembly/w3_b1/objects/w3b1_Switch_10.xml
new file mode 100644
index 0000000..0aab535
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/w3b1_Switch_10.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/w3b1_Switch_11.xml b/examples/panel_assembly/w3_b1/objects/w3b1_Switch_11.xml
new file mode 100644
index 0000000..67bfa22
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/w3b1_Switch_11.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/w3b1_Switch_12.xml b/examples/panel_assembly/w3_b1/objects/w3b1_Switch_12.xml
new file mode 100644
index 0000000..1bd6ea9
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/w3b1_Switch_12.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/objects/w3b1_Switch_13.xml b/examples/panel_assembly/w3_b1/objects/w3b1_Switch_13.xml
new file mode 100644
index 0000000..624656d
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/objects/w3b1_Switch_13.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly/w3_b1/view_panel.py b/examples/panel_assembly/w3_b1/view_panel.py
new file mode 100644
index 0000000..b29e1f7
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/view_panel.py
@@ -0,0 +1,11 @@
+"""View the w3_b1 panel assembly in MuJoCo interactive viewer."""
+
+from pathlib import Path
+
+import mujoco
+import mujoco.viewer
+
+XML_PATH = Path(__file__).parent / "generated" / "w3_b1_panel_assembly.xml"
+model = mujoco.MjModel.from_xml_path(str(XML_PATH))
+data = mujoco.MjData(model)
+mujoco.viewer.launch(model, data)
diff --git a/examples/panel_assembly/w3_b1/w3_b1_panel_base.xml b/examples/panel_assembly/w3_b1/w3_b1_panel_base.xml
new file mode 100644
index 0000000..e3b96dc
--- /dev/null
+++ b/examples/panel_assembly/w3_b1/w3_b1_panel_base.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/generated/w2_b2_with_objects.xml b/examples/panel_assembly_excel/w2_b2/generated/w2_b2_with_objects.xml
new file mode 100644
index 0000000..a1f8d4c
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/generated/w2_b2_with_objects.xml
@@ -0,0 +1,231 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/layout.yaml b/examples/panel_assembly_excel/w2_b2/layout.yaml
new file mode 100644
index 0000000..3d81883
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/layout.yaml
@@ -0,0 +1,712 @@
+panel:
+ xml: panel_base.xml
+ attach_to_body: panel_mount
+output_xml: generated/w2_b2_with_objects.xml
+layout:
+ coordinate_scale: 0.001
+ face_axes:
+ - x
+ - z
+ x_coords:
+ - -361.2
+ - -350.0
+ - -308.2
+ - -290.0
+ - -280.0
+ - -255.2
+ - -230.0
+ - -210.0
+ - -202.2
+ - -170.0
+ - -149.2
+ - -140.0
+ - -110.0
+ - -96.2
+ - -70.0
+ - -50.0
+ - -43.2
+ - 10.0
+ - 25.0
+ - 70.0
+ - 90.0
+ - 126.0
+ - 134.0
+ - 155.0
+ - 182.0
+ - 188.0
+ - 220.0
+ - 238.0
+ - 242.0
+ - 285.0
+ - 294.0
+ - 296.0
+ - 350.0
+ y_coords:
+ - 120.0
+ - 0.0
+ - -120.0
+ base_pos:
+ - 0.0
+ - 15.0
+ - 0.0
+ default_quat:
+ - 0.7071068
+ - -0.7071068
+ - 0.0
+ - 0.0
+ remove_root_joints: true
+placements:
+- - null
+ - xml: objects/seq_13_switch_quantity1_16.xml
+ slot_name: w2_b2_slot_seq_13
+ mounted_body_name: w2_b2_seq_13
+ sequence: 13
+ category: Switch
+ class_name: Switch_Quantity1_16
+ button_class: '1_1'
+ wire_number: '47'
+ center_xy_mm:
+ - -350.0
+ - 120.0
+ source_location:
+ - 2
+ - 4
+ - 12
+ note: 厚15mm
+ - null
+ - xml: objects/seq_12_switch_quantity1_15.xml
+ slot_name: w2_b2_slot_seq_12
+ mounted_body_name: w2_b2_seq_12
+ sequence: 12
+ category: Switch
+ class_name: Switch_Quantity1_15
+ button_class: '1_1'
+ wire_number: '46'
+ center_xy_mm:
+ - -290.0
+ - 120.0
+ source_location:
+ - 2
+ - 4
+ - 11
+ note: 厚9mm
+ - null
+ - null
+ - xml: objects/seq_11_knob_quantity1_04.xml
+ slot_name: w2_b2_slot_seq_11
+ mounted_body_name: w2_b2_seq_11
+ sequence: 11
+ category: Knob
+ class_name: Knob_Quantity1_04
+ button_class: '1_1'
+ wire_number: '45'
+ center_xy_mm:
+ - -230.0
+ - 120.0
+ source_location:
+ - 2
+ - 4
+ - 10
+ note: 厚30mm
+ - null
+ - null
+ - xml: objects/seq_10_switch_quantity1_14.xml
+ slot_name: w2_b2_slot_seq_10
+ mounted_body_name: w2_b2_seq_10
+ sequence: 10
+ category: Switch
+ class_name: Switch_Quantity1_14
+ button_class: '1_1'
+ wire_number: '44'
+ center_xy_mm:
+ - -170.0
+ - 120.0
+ source_location:
+ - 2
+ - 4
+ - 9
+ note: null
+ - null
+ - null
+ - xml: objects/seq_09_switch_quantity1_13.xml
+ slot_name: w2_b2_slot_seq_09
+ mounted_body_name: w2_b2_seq_09
+ sequence: 9
+ category: Switch
+ class_name: Switch_Quantity1_13
+ button_class: '1_1'
+ wire_number: '43'
+ center_xy_mm:
+ - -110.0
+ - 120.0
+ source_location:
+ - 2
+ - 4
+ - 9
+ note: null
+ - null
+ - null
+ - xml: objects/seq_08_switch_quantity1_12.xml
+ slot_name: w2_b2_slot_seq_08
+ mounted_body_name: w2_b2_seq_08
+ sequence: 8
+ category: Switch
+ class_name: Switch_Quantity1_12
+ button_class: '1_1'
+ wire_number: '42'
+ center_xy_mm:
+ - -50.0
+ - 120.0
+ source_location:
+ - 2
+ - 4
+ - 8
+ note: null
+ - null
+ - xml: objects/seq_07_switch_quantity1_11.xml
+ slot_name: w2_b2_slot_seq_07
+ mounted_body_name: w2_b2_seq_07
+ sequence: 7
+ category: Switch
+ class_name: Switch_Quantity1_11
+ button_class: '1_1'
+ wire_number: '41'
+ center_xy_mm:
+ - 10.0
+ - 120.0
+ source_location:
+ - 2
+ - 4
+ - 7
+ note: null
+ - null
+ - xml: objects/seq_06_switch_quantity1_10.xml
+ slot_name: w2_b2_slot_seq_06
+ mounted_body_name: w2_b2_seq_06
+ sequence: 6
+ category: Switch
+ class_name: Switch_Quantity1_10
+ button_class: '1_1'
+ wire_number: '40'
+ center_xy_mm:
+ - 70.0
+ - 120.0
+ source_location:
+ - 2
+ - 4
+ - 6
+ note: null
+ - null
+ - xml: objects/seq_05_switch_quantity1_09.xml
+ slot_name: w2_b2_slot_seq_05
+ mounted_body_name: w2_b2_seq_05
+ sequence: 5
+ category: Switch
+ class_name: Switch_Quantity1_09
+ button_class: '1_1'
+ wire_number: '39'
+ center_xy_mm:
+ - 126.0
+ - 120.0
+ source_location:
+ - 2
+ - 4
+ - 5
+ note: null
+ - null
+ - null
+ - xml: objects/seq_04_switch_quantity1_08.xml
+ slot_name: w2_b2_slot_seq_04
+ mounted_body_name: w2_b2_seq_04
+ sequence: 4
+ category: Switch
+ class_name: Switch_Quantity1_08
+ button_class: '1_1'
+ wire_number: '38'
+ center_xy_mm:
+ - 182.0
+ - 120.0
+ source_location:
+ - 2
+ - 4
+ - 4
+ note: 2.4*1.9
+ - null
+ - null
+ - xml: objects/seq_03_switch_quantity1_07.xml
+ slot_name: w2_b2_slot_seq_03
+ mounted_body_name: w2_b2_seq_03
+ sequence: 3
+ category: Switch
+ class_name: Switch_Quantity1_07
+ button_class: '1_1'
+ wire_number: '37'
+ center_xy_mm:
+ - 238.0
+ - 120.0
+ source_location:
+ - 2
+ - 4
+ - 3
+ note: null
+ - null
+ - null
+ - xml: objects/seq_02_knob_quantity1_03.xml
+ slot_name: w2_b2_slot_seq_02
+ mounted_body_name: w2_b2_seq_02
+ sequence: 2
+ category: Knob
+ class_name: Knob_Quantity1_03
+ button_class: '1_1'
+ wire_number: '36'
+ center_xy_mm:
+ - 294.0
+ - 120.0
+ source_location:
+ - 2
+ - 4
+ - 2
+ note: null
+ - null
+ - xml: objects/seq_01_switch_quantity1_06.xml
+ slot_name: w2_b2_slot_seq_01
+ mounted_body_name: w2_b2_seq_01
+ sequence: 1
+ category: Switch
+ class_name: Switch_Quantity1_06
+ button_class: '1_1'
+ wire_number: '35'
+ center_xy_mm:
+ - 350.0
+ - 120.0
+ source_location:
+ - 2
+ - 4
+ - 1
+ note: null
+- - null
+ - xml: objects/seq_24_stop_quantity1_2.xml
+ slot_name: w2_b2_slot_seq_24
+ mounted_body_name: w2_b2_seq_24
+ sequence: 24
+ category: Stop
+ class_name: Stop_Quantity1_2
+ button_class: '1_1'
+ wire_number: '58'
+ center_xy_mm:
+ - -350.0
+ - 0.0
+ source_location:
+ - 2
+ - 5
+ - 12
+ note: 厚20mm
+ - null
+ - null
+ - xml: objects/seq_23_switch_quantity1_25.xml
+ slot_name: w2_b2_slot_seq_23
+ mounted_body_name: w2_b2_seq_23
+ sequence: 23
+ category: Switch
+ class_name: Switch_Quantity1_25
+ button_class: '1_1'
+ wire_number: '57'
+ center_xy_mm:
+ - -280.0
+ - 0.0
+ source_location:
+ - 2
+ - 5
+ - 11
+ note: null
+ - null
+ - null
+ - xml: objects/seq_22_switch_quantity1_24.xml
+ slot_name: w2_b2_slot_seq_22
+ mounted_body_name: w2_b2_seq_22
+ sequence: 22
+ category: Switch
+ class_name: Switch_Quantity1_24
+ button_class: '1_1'
+ wire_number: '56'
+ center_xy_mm:
+ - -210.0
+ - 0.0
+ source_location:
+ - 2
+ - 5
+ - 10
+ note: null
+ - null
+ - null
+ - null
+ - xml: objects/seq_21_switch_quantity1_23.xml
+ slot_name: w2_b2_slot_seq_21
+ mounted_body_name: w2_b2_seq_21
+ sequence: 21
+ category: Switch
+ class_name: Switch_Quantity1_23
+ button_class: '1_1'
+ wire_number: '55'
+ center_xy_mm:
+ - -140.0
+ - 0.0
+ source_location:
+ - 2
+ - 5
+ - 9
+ note: 直径2.1
+ - null
+ - null
+ - xml: objects/seq_20_stop_quantity1_1.xml
+ slot_name: w2_b2_slot_seq_20
+ mounted_body_name: w2_b2_seq_20
+ sequence: 20
+ category: Stop
+ class_name: Stop_Quantity1_1
+ button_class: '1_1'
+ wire_number: '54'
+ center_xy_mm:
+ - -70.0
+ - 0.0
+ source_location:
+ - 2
+ - 5
+ - 8
+ note: null
+ - null
+ - null
+ - null
+ - null
+ - xml: objects/seq_19_switch_quantity1_22.xml
+ slot_name: w2_b2_slot_seq_19
+ mounted_body_name: w2_b2_seq_19
+ sequence: 19
+ category: Switch
+ class_name: Switch_Quantity1_22
+ button_class: '1_1'
+ wire_number: '53'
+ center_xy_mm:
+ - 70.0
+ - 0.0
+ source_location:
+ - 2
+ - 5
+ - 6
+ note: null
+ - null
+ - null
+ - xml: objects/seq_18_switch_quantity1_21.xml
+ slot_name: w2_b2_slot_seq_18
+ mounted_body_name: w2_b2_seq_18
+ sequence: 18
+ category: Switch
+ class_name: Switch_Quantity1_21
+ button_class: '1_1'
+ wire_number: '52'
+ center_xy_mm:
+ - 134.0
+ - 0.0
+ source_location:
+ - 2
+ - 5
+ - 5
+ note: null
+ - null
+ - null
+ - xml: objects/seq_17_switch_quantity1_20.xml
+ slot_name: w2_b2_slot_seq_17
+ mounted_body_name: w2_b2_seq_17
+ sequence: 17
+ category: Switch
+ class_name: Switch_Quantity1_20
+ button_class: '1_1'
+ wire_number: '51'
+ center_xy_mm:
+ - 188.0
+ - 0.0
+ source_location:
+ - 2
+ - 5
+ - 4
+ note: null
+ - null
+ - null
+ - xml: objects/seq_16_switch_quantity1_19.xml
+ slot_name: w2_b2_slot_seq_16
+ mounted_body_name: w2_b2_seq_16
+ sequence: 16
+ category: Switch
+ class_name: Switch_Quantity1_19
+ button_class: '1_1'
+ wire_number: '50'
+ center_xy_mm:
+ - 242.0
+ - 0.0
+ source_location:
+ - 2
+ - 5
+ - 3
+ note: null
+ - null
+ - null
+ - xml: objects/seq_15_switch_quantity1_18.xml
+ slot_name: w2_b2_slot_seq_15
+ mounted_body_name: w2_b2_seq_15
+ sequence: 15
+ category: Switch
+ class_name: Switch_Quantity1_18
+ button_class: '1_1'
+ wire_number: '49'
+ center_xy_mm:
+ - 296.0
+ - 0.0
+ source_location:
+ - 2
+ - 5
+ - 2
+ note: null
+ - xml: objects/seq_14_switch_quantity1_17.xml
+ slot_name: w2_b2_slot_seq_14
+ mounted_body_name: w2_b2_seq_14
+ sequence: 14
+ category: Switch
+ class_name: Switch_Quantity1_17
+ button_class: '1_1'
+ wire_number: '48'
+ center_xy_mm:
+ - 350.0
+ - 0.0
+ source_location:
+ - 2
+ - 5
+ - 1
+ note: null
+- - xml: objects/seq_37_unknown.xml
+ slot_name: w2_b2_slot_seq_37
+ mounted_body_name: w2_b2_seq_37
+ sequence: 37
+ category: null
+ class_name: null
+ button_class: null
+ wire_number: null
+ center_xy_mm:
+ - -361.2
+ - -120.0
+ source_location:
+ - 2
+ - 6
+ - 13
+ note: null
+ - null
+ - xml: objects/seq_36_unknown.xml
+ slot_name: w2_b2_slot_seq_36
+ mounted_body_name: w2_b2_seq_36
+ sequence: 36
+ category: null
+ class_name: null
+ button_class: null
+ wire_number: null
+ center_xy_mm:
+ - -308.2
+ - -120.0
+ source_location:
+ - 2
+ - 6
+ - 12
+ note: null
+ - null
+ - null
+ - xml: objects/seq_35_unknown.xml
+ slot_name: w2_b2_slot_seq_35
+ mounted_body_name: w2_b2_seq_35
+ sequence: 35
+ category: null
+ class_name: null
+ button_class: null
+ wire_number: null
+ center_xy_mm:
+ - -255.2
+ - -120.0
+ source_location:
+ - 2
+ - 6
+ - 11
+ note: null
+ - null
+ - null
+ - xml: objects/seq_34_switch_quantity1_35.xml
+ slot_name: w2_b2_slot_seq_34
+ mounted_body_name: w2_b2_seq_34
+ sequence: 34
+ category: Switch
+ class_name: Switch_Quantity1_35
+ button_class: '1_1'
+ wire_number: '68'
+ center_xy_mm:
+ - -202.2
+ - -120.0
+ source_location:
+ - 2
+ - 6
+ - 10
+ note: 厚6mm
+ - null
+ - xml: objects/seq_33_switch_quantity1_34.xml
+ slot_name: w2_b2_slot_seq_33
+ mounted_body_name: w2_b2_seq_33
+ sequence: 33
+ category: Switch
+ class_name: Switch_Quantity1_34
+ button_class: '1_1'
+ wire_number: '67'
+ center_xy_mm:
+ - -149.2
+ - -120.0
+ source_location:
+ - 2
+ - 6
+ - 9
+ note: 2*1.5
+ - null
+ - null
+ - xml: objects/seq_32_switch_quantity1_33.xml
+ slot_name: w2_b2_slot_seq_32
+ mounted_body_name: w2_b2_seq_32
+ sequence: 32
+ category: Switch
+ class_name: Switch_Quantity1_33
+ button_class: '1_1'
+ wire_number: '66'
+ center_xy_mm:
+ - -96.2
+ - -120.0
+ source_location:
+ - 2
+ - 6
+ - 8
+ note: null
+ - null
+ - null
+ - xml: objects/seq_31_switch_quantity1_32.xml
+ slot_name: w2_b2_slot_seq_31
+ mounted_body_name: w2_b2_seq_31
+ sequence: 31
+ category: Switch
+ class_name: Switch_Quantity1_32
+ button_class: '1_1'
+ wire_number: '65'
+ center_xy_mm:
+ - -43.2
+ - -120.0
+ source_location:
+ - 2
+ - 6
+ - 7
+ note: 2.2*1.7
+ - null
+ - xml: objects/seq_30_switch_quantity1_31.xml
+ slot_name: w2_b2_slot_seq_30
+ mounted_body_name: w2_b2_seq_30
+ sequence: 30
+ category: Switch
+ class_name: Switch_Quantity1_31
+ button_class: '1_1'
+ wire_number: '64'
+ center_xy_mm:
+ - 25.0
+ - -120.0
+ source_location:
+ - 2
+ - 6
+ - 6
+ note: null
+ - null
+ - xml: objects/seq_29_switch_quantity1_30.xml
+ slot_name: w2_b2_slot_seq_29
+ mounted_body_name: w2_b2_seq_29
+ sequence: 29
+ category: Switch
+ class_name: Switch_Quantity1_30
+ button_class: '1_1'
+ wire_number: '63'
+ center_xy_mm:
+ - 90.0
+ - -120.0
+ source_location:
+ - 2
+ - 6
+ - 5
+ note: null
+ - null
+ - null
+ - xml: objects/seq_28_switch_quantity1_29.xml
+ slot_name: w2_b2_slot_seq_28
+ mounted_body_name: w2_b2_seq_28
+ sequence: 28
+ category: Switch
+ class_name: Switch_Quantity1_29
+ button_class: '1_1'
+ wire_number: '62'
+ center_xy_mm:
+ - 155.0
+ - -120.0
+ source_location:
+ - 2
+ - 6
+ - 4
+ note: null
+ - null
+ - null
+ - xml: objects/seq_27_switch_quantity1_28.xml
+ slot_name: w2_b2_slot_seq_27
+ mounted_body_name: w2_b2_seq_27
+ sequence: 27
+ category: Switch
+ class_name: Switch_Quantity1_28
+ button_class: '1_1'
+ wire_number: '61'
+ center_xy_mm:
+ - 220.0
+ - -120.0
+ source_location:
+ - 2
+ - 6
+ - 3
+ note: null
+ - null
+ - null
+ - xml: objects/seq_26_switch_quantity1_27.xml
+ slot_name: w2_b2_slot_seq_26
+ mounted_body_name: w2_b2_seq_26
+ sequence: 26
+ category: Switch
+ class_name: Switch_Quantity1_27
+ button_class: '1_1'
+ wire_number: '60'
+ center_xy_mm:
+ - 285.0
+ - -120.0
+ source_location:
+ - 2
+ - 6
+ - 2
+ note: null
+ - null
+ - null
+ - xml: objects/seq_25_switch_quantity1_26.xml
+ slot_name: w2_b2_slot_seq_25
+ mounted_body_name: w2_b2_seq_25
+ sequence: 25
+ category: Switch
+ class_name: Switch_Quantity1_26
+ button_class: '1_1'
+ wire_number: '59'
+ center_xy_mm:
+ - 350.0
+ - -120.0
+ source_location:
+ - 2
+ - 6
+ - 1
+ note: 3*2.5
diff --git a/examples/panel_assembly_excel/w2_b2/objects/README.md b/examples/panel_assembly_excel/w2_b2/objects/README.md
new file mode 100644
index 0000000..6043a0f
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/README.md
@@ -0,0 +1,44 @@
+# w2_b2 Objects
+
+这些 XML 文件还没有创建好。后续你可以按下面的文件名把每个序号对应的按钮资产补进来,
+然后直接运行面板装配脚本生成最终 XML。
+
+## 预期文件
+
+- `seq_01_switch_quantity1_06.xml` 序号 1 | Switch | Switch_Quantity1_06 | center=(350, 120) mm
+- `seq_02_knob_quantity1_03.xml` 序号 2 | Knob | Knob_Quantity1_03 | center=(294, 120) mm
+- `seq_03_switch_quantity1_07.xml` 序号 3 | Switch | Switch_Quantity1_07 | center=(238, 120) mm
+- `seq_04_switch_quantity1_08.xml` 序号 4 | Switch | Switch_Quantity1_08 | center=(182, 120) mm
+- `seq_05_switch_quantity1_09.xml` 序号 5 | Switch | Switch_Quantity1_09 | center=(126, 120) mm
+- `seq_06_switch_quantity1_10.xml` 序号 6 | Switch | Switch_Quantity1_10 | center=(70, 120) mm
+- `seq_07_switch_quantity1_11.xml` 序号 7 | Switch | Switch_Quantity1_11 | center=(10, 120) mm
+- `seq_08_switch_quantity1_12.xml` 序号 8 | Switch | Switch_Quantity1_12 | center=(-50, 120) mm
+- `seq_09_switch_quantity1_13.xml` 序号 9 | Switch | Switch_Quantity1_13 | center=(-110, 120) mm
+- `seq_10_switch_quantity1_14.xml` 序号 10 | Switch | Switch_Quantity1_14 | center=(-170, 120) mm
+- `seq_11_knob_quantity1_04.xml` 序号 11 | Knob | Knob_Quantity1_04 | center=(-230, 120) mm
+- `seq_12_switch_quantity1_15.xml` 序号 12 | Switch | Switch_Quantity1_15 | center=(-290, 120) mm
+- `seq_13_switch_quantity1_16.xml` 序号 13 | Switch | Switch_Quantity1_16 | center=(-350, 120) mm
+- `seq_14_switch_quantity1_17.xml` 序号 14 | Switch | Switch_Quantity1_17 | center=(350, 0) mm
+- `seq_15_switch_quantity1_18.xml` 序号 15 | Switch | Switch_Quantity1_18 | center=(296, 0) mm
+- `seq_16_switch_quantity1_19.xml` 序号 16 | Switch | Switch_Quantity1_19 | center=(242, 0) mm
+- `seq_17_switch_quantity1_20.xml` 序号 17 | Switch | Switch_Quantity1_20 | center=(188, 0) mm
+- `seq_18_switch_quantity1_21.xml` 序号 18 | Switch | Switch_Quantity1_21 | center=(134, 0) mm
+- `seq_19_switch_quantity1_22.xml` 序号 19 | Switch | Switch_Quantity1_22 | center=(70, 0) mm
+- `seq_20_stop_quantity1_1.xml` 序号 20 | Stop | Stop_Quantity1_1 | center=(-70, 0) mm
+- `seq_21_switch_quantity1_23.xml` 序号 21 | Switch | Switch_Quantity1_23 | center=(-140, 0) mm
+- `seq_22_switch_quantity1_24.xml` 序号 22 | Switch | Switch_Quantity1_24 | center=(-210, 0) mm
+- `seq_23_switch_quantity1_25.xml` 序号 23 | Switch | Switch_Quantity1_25 | center=(-280, 0) mm
+- `seq_24_stop_quantity1_2.xml` 序号 24 | Stop | Stop_Quantity1_2 | center=(-350, 0) mm
+- `seq_25_switch_quantity1_26.xml` 序号 25 | Switch | Switch_Quantity1_26 | center=(350, -120) mm
+- `seq_26_switch_quantity1_27.xml` 序号 26 | Switch | Switch_Quantity1_27 | center=(285, -120) mm
+- `seq_27_switch_quantity1_28.xml` 序号 27 | Switch | Switch_Quantity1_28 | center=(220, -120) mm
+- `seq_28_switch_quantity1_29.xml` 序号 28 | Switch | Switch_Quantity1_29 | center=(155, -120) mm
+- `seq_29_switch_quantity1_30.xml` 序号 29 | Switch | Switch_Quantity1_30 | center=(90, -120) mm
+- `seq_30_switch_quantity1_31.xml` 序号 30 | Switch | Switch_Quantity1_31 | center=(25, -120) mm
+- `seq_31_switch_quantity1_32.xml` 序号 31 | Switch | Switch_Quantity1_32 | center=(-43.2, -120) mm
+- `seq_32_switch_quantity1_33.xml` 序号 32 | Switch | Switch_Quantity1_33 | center=(-96.2, -120) mm
+- `seq_33_switch_quantity1_34.xml` 序号 33 | Switch | Switch_Quantity1_34 | center=(-149.2, -120) mm
+- `seq_34_switch_quantity1_35.xml` 序号 34 | Switch | Switch_Quantity1_35 | center=(-202.2, -120) mm
+- `seq_35_unknown.xml` 序号 35 | center=(-255.2, -120) mm
+- `seq_36_unknown.xml` 序号 36 | center=(-308.2, -120) mm
+- `seq_37_unknown.xml` 序号 37 | center=(-361.2, -120) mm
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_01_switch_quantity1_06.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_01_switch_quantity1_06.xml
new file mode 100644
index 0000000..98c9b8f
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_01_switch_quantity1_06.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_02_knob_quantity1_03.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_02_knob_quantity1_03.xml
new file mode 100644
index 0000000..13d0b86
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_02_knob_quantity1_03.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_03_switch_quantity1_07.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_03_switch_quantity1_07.xml
new file mode 100644
index 0000000..673d679
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_03_switch_quantity1_07.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_04_switch_quantity1_08.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_04_switch_quantity1_08.xml
new file mode 100644
index 0000000..7805c5a
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_04_switch_quantity1_08.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_05_switch_quantity1_09.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_05_switch_quantity1_09.xml
new file mode 100644
index 0000000..7a54c4d
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_05_switch_quantity1_09.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_06_switch_quantity1_10.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_06_switch_quantity1_10.xml
new file mode 100644
index 0000000..cafae46
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_06_switch_quantity1_10.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_07_switch_quantity1_11.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_07_switch_quantity1_11.xml
new file mode 100644
index 0000000..0e5855a
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_07_switch_quantity1_11.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_08_switch_quantity1_12.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_08_switch_quantity1_12.xml
new file mode 100644
index 0000000..029f2ec
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_08_switch_quantity1_12.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_09_switch_quantity1_13.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_09_switch_quantity1_13.xml
new file mode 100644
index 0000000..6ac9d5a
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_09_switch_quantity1_13.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_10_switch_quantity1_14.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_10_switch_quantity1_14.xml
new file mode 100644
index 0000000..7733fdc
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_10_switch_quantity1_14.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_11_knob_quantity1_04.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_11_knob_quantity1_04.xml
new file mode 100644
index 0000000..49870a4
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_11_knob_quantity1_04.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_12_switch_quantity1_15.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_12_switch_quantity1_15.xml
new file mode 100644
index 0000000..252ce39
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_12_switch_quantity1_15.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_13_switch_quantity1_16.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_13_switch_quantity1_16.xml
new file mode 100644
index 0000000..c84a329
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_13_switch_quantity1_16.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_14_switch_quantity1_17.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_14_switch_quantity1_17.xml
new file mode 100644
index 0000000..1535139
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_14_switch_quantity1_17.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_15_switch_quantity1_18.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_15_switch_quantity1_18.xml
new file mode 100644
index 0000000..cbf11d8
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_15_switch_quantity1_18.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_16_switch_quantity1_19.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_16_switch_quantity1_19.xml
new file mode 100644
index 0000000..97d2867
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_16_switch_quantity1_19.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_17_switch_quantity1_20.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_17_switch_quantity1_20.xml
new file mode 100644
index 0000000..feedf9c
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_17_switch_quantity1_20.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_18_switch_quantity1_21.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_18_switch_quantity1_21.xml
new file mode 100644
index 0000000..6540966
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_18_switch_quantity1_21.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_19_switch_quantity1_22.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_19_switch_quantity1_22.xml
new file mode 100644
index 0000000..80cb064
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_19_switch_quantity1_22.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_20_stop_quantity1_1.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_20_stop_quantity1_1.xml
new file mode 100644
index 0000000..b038b47
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_20_stop_quantity1_1.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_21_switch_quantity1_23.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_21_switch_quantity1_23.xml
new file mode 100644
index 0000000..36b3e6e
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_21_switch_quantity1_23.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_22_switch_quantity1_24.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_22_switch_quantity1_24.xml
new file mode 100644
index 0000000..ddb2602
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_22_switch_quantity1_24.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_23_switch_quantity1_25.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_23_switch_quantity1_25.xml
new file mode 100644
index 0000000..87b2ad6
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_23_switch_quantity1_25.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_24_stop_quantity1_2.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_24_stop_quantity1_2.xml
new file mode 100644
index 0000000..8387ab9
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_24_stop_quantity1_2.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_25_switch_quantity1_26.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_25_switch_quantity1_26.xml
new file mode 100644
index 0000000..d4fbb43
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_25_switch_quantity1_26.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_26_switch_quantity1_27.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_26_switch_quantity1_27.xml
new file mode 100644
index 0000000..6ccbf69
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_26_switch_quantity1_27.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_27_switch_quantity1_28.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_27_switch_quantity1_28.xml
new file mode 100644
index 0000000..618d0e1
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_27_switch_quantity1_28.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_28_switch_quantity1_29.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_28_switch_quantity1_29.xml
new file mode 100644
index 0000000..d4a0c1b
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_28_switch_quantity1_29.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_29_switch_quantity1_30.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_29_switch_quantity1_30.xml
new file mode 100644
index 0000000..cb58822
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_29_switch_quantity1_30.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_30_switch_quantity1_31.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_30_switch_quantity1_31.xml
new file mode 100644
index 0000000..1bfd353
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_30_switch_quantity1_31.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_31_switch_quantity1_32.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_31_switch_quantity1_32.xml
new file mode 100644
index 0000000..026777a
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_31_switch_quantity1_32.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_32_switch_quantity1_33.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_32_switch_quantity1_33.xml
new file mode 100644
index 0000000..9f209b5
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_32_switch_quantity1_33.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_33_switch_quantity1_34.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_33_switch_quantity1_34.xml
new file mode 100644
index 0000000..848eb4a
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_33_switch_quantity1_34.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_34_switch_quantity1_35.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_34_switch_quantity1_35.xml
new file mode 100644
index 0000000..ac6b135
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_34_switch_quantity1_35.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_35_unknown.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_35_unknown.xml
new file mode 100644
index 0000000..9f2e307
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_35_unknown.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_36_unknown.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_36_unknown.xml
new file mode 100644
index 0000000..1672a40
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_36_unknown.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/objects/seq_37_unknown.xml b/examples/panel_assembly_excel/w2_b2/objects/seq_37_unknown.xml
new file mode 100644
index 0000000..6e93708
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/objects/seq_37_unknown.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/panel_base.xml b/examples/panel_assembly_excel/w2_b2/panel_base.xml
new file mode 100644
index 0000000..a2b691f
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/panel_base.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/panel_assembly_excel/w2_b2/switch_inventory.csv b/examples/panel_assembly_excel/w2_b2/switch_inventory.csv
new file mode 100644
index 0000000..9b38492
--- /dev/null
+++ b/examples/panel_assembly_excel/w2_b2/switch_inventory.csv
@@ -0,0 +1,38 @@
+wall_board_name,sequence,category,button_count,class_name,button_class,center_x_mm,center_y_mm,location,wire_number,note,expected_xml
+w2_b2,1,Switch,1,Switch_Quantity1_06,1_1,350,120,"(2, 4, 1)",35,,objects/seq_01_switch_quantity1_06.xml
+w2_b2,2,Knob,1,Knob_Quantity1_03,1_1,294,120,"(2, 4, 2)",36,,objects/seq_02_knob_quantity1_03.xml
+w2_b2,3,Switch,1,Switch_Quantity1_07,1_1,238,120,"(2, 4, 3)",37,,objects/seq_03_switch_quantity1_07.xml
+w2_b2,4,Switch,1,Switch_Quantity1_08,1_1,182,120,"(2, 4, 4)",38,2.4*1.9,objects/seq_04_switch_quantity1_08.xml
+w2_b2,5,Switch,1,Switch_Quantity1_09,1_1,126,120,"(2, 4, 5)",39,,objects/seq_05_switch_quantity1_09.xml
+w2_b2,6,Switch,1,Switch_Quantity1_10,1_1,70,120,"(2, 4, 6)",40,,objects/seq_06_switch_quantity1_10.xml
+w2_b2,7,Switch,1,Switch_Quantity1_11,1_1,10,120,"(2, 4, 7)",41,,objects/seq_07_switch_quantity1_11.xml
+w2_b2,8,Switch,1,Switch_Quantity1_12,1_1,-50,120,"(2, 4, 8)",42,,objects/seq_08_switch_quantity1_12.xml
+w2_b2,9,Switch,1,Switch_Quantity1_13,1_1,-110,120,"(2, 4, 9)",43,,objects/seq_09_switch_quantity1_13.xml
+w2_b2,10,Switch,1,Switch_Quantity1_14,1_1,-170,120,"(2, 4, 9)",44,,objects/seq_10_switch_quantity1_14.xml
+w2_b2,11,Knob,1,Knob_Quantity1_04,1_1,-230,120,"(2, 4, 10)",45,厚30mm,objects/seq_11_knob_quantity1_04.xml
+w2_b2,12,Switch,1,Switch_Quantity1_15,1_1,-290,120,"(2, 4, 11)",46,厚9mm,objects/seq_12_switch_quantity1_15.xml
+w2_b2,13,Switch,1,Switch_Quantity1_16,1_1,-350,120,"(2, 4, 12)",47,厚15mm,objects/seq_13_switch_quantity1_16.xml
+w2_b2,14,Switch,1,Switch_Quantity1_17,1_1,350,0,"(2, 5, 1)",48,,objects/seq_14_switch_quantity1_17.xml
+w2_b2,15,Switch,1,Switch_Quantity1_18,1_1,296,0,"(2, 5, 2)",49,,objects/seq_15_switch_quantity1_18.xml
+w2_b2,16,Switch,1,Switch_Quantity1_19,1_1,242,0,"(2, 5, 3)",50,,objects/seq_16_switch_quantity1_19.xml
+w2_b2,17,Switch,1,Switch_Quantity1_20,1_1,188,0,"(2, 5, 4)",51,,objects/seq_17_switch_quantity1_20.xml
+w2_b2,18,Switch,1,Switch_Quantity1_21,1_1,134,0,"(2, 5, 5)",52,,objects/seq_18_switch_quantity1_21.xml
+w2_b2,19,Switch,1,Switch_Quantity1_22,1_1,70,0,"(2, 5, 6)",53,,objects/seq_19_switch_quantity1_22.xml
+w2_b2,20,Stop,1,Stop_Quantity1_1,1_1,-70,0,"(2, 5, 8)",54,,objects/seq_20_stop_quantity1_1.xml
+w2_b2,21,Switch,1,Switch_Quantity1_23,1_1,-140,0,"(2, 5, 9)",55,直径2.1,objects/seq_21_switch_quantity1_23.xml
+w2_b2,22,Switch,1,Switch_Quantity1_24,1_1,-210,0,"(2, 5, 10)",56,,objects/seq_22_switch_quantity1_24.xml
+w2_b2,23,Switch,1,Switch_Quantity1_25,1_1,-280,0,"(2, 5, 11)",57,,objects/seq_23_switch_quantity1_25.xml
+w2_b2,24,Stop,1,Stop_Quantity1_2,1_1,-350,0,"(2, 5, 12)",58,厚20mm,objects/seq_24_stop_quantity1_2.xml
+w2_b2,25,Switch,1,Switch_Quantity1_26,1_1,350,-120,"(2, 6, 1)",59,3*2.5,objects/seq_25_switch_quantity1_26.xml
+w2_b2,26,Switch,1,Switch_Quantity1_27,1_1,285,-120,"(2, 6, 2)",60,,objects/seq_26_switch_quantity1_27.xml
+w2_b2,27,Switch,1,Switch_Quantity1_28,1_1,220,-120,"(2, 6, 3)",61,,objects/seq_27_switch_quantity1_28.xml
+w2_b2,28,Switch,1,Switch_Quantity1_29,1_1,155,-120,"(2, 6, 4)",62,,objects/seq_28_switch_quantity1_29.xml
+w2_b2,29,Switch,1,Switch_Quantity1_30,1_1,90,-120,"(2, 6, 5)",63,,objects/seq_29_switch_quantity1_30.xml
+w2_b2,30,Switch,1,Switch_Quantity1_31,1_1,25,-120,"(2, 6, 6)",64,,objects/seq_30_switch_quantity1_31.xml
+w2_b2,31,Switch,1,Switch_Quantity1_32,1_1,-43.2,-120,"(2, 6, 7)",65,2.2*1.7,objects/seq_31_switch_quantity1_32.xml
+w2_b2,32,Switch,1,Switch_Quantity1_33,1_1,-96.2,-120,"(2, 6, 8)",66,,objects/seq_32_switch_quantity1_33.xml
+w2_b2,33,Switch,1,Switch_Quantity1_34,1_1,-149.2,-120,"(2, 6, 9)",67,2*1.5,objects/seq_33_switch_quantity1_34.xml
+w2_b2,34,Switch,1,Switch_Quantity1_35,1_1,-202.2,-120,"(2, 6, 10)",68,厚6mm,objects/seq_34_switch_quantity1_35.xml
+w2_b2,35,,,,,-255.2,-120,"(2, 6, 11)",,,objects/seq_35_unknown.xml
+w2_b2,36,,,,,-308.2,-120,"(2, 6, 12)",,,objects/seq_36_unknown.xml
+w2_b2,37,,,,,-361.2,-120,"(2, 6, 13)",,,objects/seq_37_unknown.xml
diff --git a/examples/tune_randomization_extremes.py b/examples/tune_randomization_extremes.py
index 949a8e1..cdca066 100644
--- a/examples/tune_randomization_extremes.py
+++ b/examples/tune_randomization_extremes.py
@@ -471,13 +471,24 @@ def _apply_case(self, case: ExtremeCase) -> None:
)
target.apply_pose(compose_pose(base_world, sampled_in_base))
else:
+ base_pose = self._resolve_base_pose(target)
target.apply_pose(
_with_offsets(
- target.get_default_pose(),
+ base_pose,
offsets,
target.rand_range,
)
)
+
+ def _resolve_base_pose(self, target: RandomizationTarget) -> PoseState:
+ reference = target.rand_range.reference
+ if isinstance(reference, RandomizationReference):
+ return target.get_default_pose()
+ if reference in self.backend.object_handlers:
+ return self.backend.object_handlers[reference].get_pose()
+ if reference in self.backend.operator_handlers:
+ return self.backend.operator_handlers[reference].get_end_effector_pose()
+ return target.get_default_pose()
self.env.refresh_viewer()
self.case_var.set(case.name)
self.desc_var.set(case.description)