feat: add panel assembly model asset
parent
954b4f84ab
commit
3f6b07eb37
@ -0,0 +1,43 @@
|
||||
# QET Panel Assembly Model
|
||||
|
||||
This directory contains a reusable electrical panel assembly model based on the referenced video.
|
||||
|
||||
## Files
|
||||
|
||||
- `qet_panel_assembly.FCStd`: FreeCAD native panel assembly asset.
|
||||
- `qet_panel_assembly.step`: geometry-only exchange export.
|
||||
- `qet_panel_assembly_report.json`: generated metadata for verification.
|
||||
- `create_qet_panel_assembly.py`: FreeCAD Python generator used to recreate the asset.
|
||||
|
||||
## Geometry
|
||||
|
||||
The model is a medium-detail approximation of the full assembly:
|
||||
|
||||
- Pale gray cabinet / door body.
|
||||
- Thick side edge and recessed door panel.
|
||||
- Two circular hinge / screw markers.
|
||||
- Dark right-side mounting plate.
|
||||
- Two vertical white perforated connector banks.
|
||||
- Small lower accessory connector modules.
|
||||
- Yellow wire-frame style guide geometry matching the highlighted wiring envelope in the reference.
|
||||
|
||||
Approximate dimensions:
|
||||
|
||||
- Overall height: `180 mm`
|
||||
- Cabinet width: `110 mm`
|
||||
- Cabinet depth: `55 mm`
|
||||
- Right connector area height: about `120 mm`
|
||||
|
||||
## Semantics
|
||||
|
||||
This is a visual and placement asset. It intentionally does not contain terminal LCS objects or engineering binding fields such as `QetProjectUuid`, `QetElementUuid`, `QetTerminalUuid`, or `QetInstanceId`.
|
||||
|
||||
## Regenerate
|
||||
|
||||
On this Windows workstation, use the registered FreeCAD runtime:
|
||||
|
||||
```powershell
|
||||
$runtime = Get-Content -LiteralPath 'C:\Users\ng123\AppData\Local\QETDeps\runtime.json' -Raw | ConvertFrom-Json
|
||||
$env:QET_FREECAD_RUNTIME_JSON = 'C:\Users\ng123\AppData\Local\QETDeps\runtime.json'
|
||||
& $runtime.freecad_python 'D:\LightWork3D\data\examples\qet_panel_assembly\create_qet_panel_assembly.py'
|
||||
```
|
||||
@ -0,0 +1,246 @@
|
||||
# Generate a reusable QET electrical panel assembly asset.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _bootstrap_windows_freecad_runtime():
|
||||
if os.name != "nt":
|
||||
return
|
||||
|
||||
runtime_json = os.environ.get("QET_FREECAD_RUNTIME_JSON")
|
||||
if not runtime_json:
|
||||
runtime_json = os.path.join(os.environ.get("LOCALAPPDATA", ""), "QETDeps", "runtime.json")
|
||||
if not runtime_json or not os.path.exists(runtime_json):
|
||||
return
|
||||
|
||||
with open(runtime_json, "r", encoding="utf-8-sig") as handle:
|
||||
runtime = json.load(handle)
|
||||
|
||||
roots = [str(item) for item in runtime.get("path_prefix", []) if item]
|
||||
freecad_root = runtime.get("freecad_root", "")
|
||||
if freecad_root:
|
||||
roots.extend(
|
||||
[
|
||||
os.path.join(freecad_root, "build", "Mod", "Material"),
|
||||
os.path.join(freecad_root, "build", "Mod", "Part"),
|
||||
os.path.join(freecad_root, "build", "Mod", "Import"),
|
||||
os.path.join(freecad_root, "build", "Mod"),
|
||||
]
|
||||
)
|
||||
roots.append(os.path.join(os.environ.get("SystemRoot", r"C:\Windows"), "System32", "downlevel"))
|
||||
|
||||
for root in roots:
|
||||
if root and os.path.isdir(root):
|
||||
try:
|
||||
os.add_dll_directory(root)
|
||||
except (AttributeError, OSError):
|
||||
pass
|
||||
if root not in sys.path:
|
||||
sys.path.append(root)
|
||||
|
||||
|
||||
_bootstrap_windows_freecad_runtime()
|
||||
|
||||
import FreeCAD as App
|
||||
import Part
|
||||
|
||||
|
||||
OUT_DIR = Path(__file__).resolve().parent
|
||||
FCSTD_PATH = OUT_DIR / "qet_panel_assembly.FCStd"
|
||||
STEP_PATH = OUT_DIR / "qet_panel_assembly.step"
|
||||
REPORT_PATH = OUT_DIR / "qet_panel_assembly_report.json"
|
||||
|
||||
ENGINEERING_BINDING_PROPERTIES = {
|
||||
"QetProjectUuid",
|
||||
"QetElementUuid",
|
||||
"QetTerminalUuid",
|
||||
"QetInstanceId",
|
||||
}
|
||||
|
||||
|
||||
def _style(obj, color, transparency=0):
|
||||
if not hasattr(obj, "ViewObject") or obj.ViewObject is None:
|
||||
return
|
||||
obj.ViewObject.ShapeColor = color
|
||||
obj.ViewObject.Transparency = transparency
|
||||
|
||||
|
||||
def _box(doc, name, dx, dy, dz, x, y, z, color, transparency=0):
|
||||
obj = doc.addObject("Part::Feature", name)
|
||||
obj.Shape = Part.makeBox(dx, dy, dz, App.Vector(x, y, z))
|
||||
_style(obj, color, transparency)
|
||||
return obj
|
||||
|
||||
|
||||
def _cylinder_y(doc, name, radius, length, x, y, z, color):
|
||||
obj = doc.addObject("Part::Feature", name)
|
||||
obj.Shape = Part.makeCylinder(radius, length, App.Vector(x, y, z), App.Vector(0, -1, 0))
|
||||
_style(obj, color, 0)
|
||||
return obj
|
||||
|
||||
|
||||
def _wire_segment(doc, name, start, end, thickness, color):
|
||||
sx, sy, sz = start
|
||||
ex, ey, ez = end
|
||||
dx = abs(ex - sx) or thickness
|
||||
dy = abs(ey - sy) or thickness
|
||||
dz = abs(ez - sz) or thickness
|
||||
x = min(sx, ex) - (thickness / 2.0 if ex == sx else 0.0)
|
||||
y = min(sy, ey) - (thickness / 2.0 if ey == sy else 0.0)
|
||||
z = min(sz, ez) - (thickness / 2.0 if ez == sz else 0.0)
|
||||
return _box(doc, name, dx, dy, dz, x, y, z, color, 0)
|
||||
|
||||
|
||||
def _wire_rect(doc, prefix, x0, y0, z0, width, height, depth_offset=0.0):
|
||||
color = (1.0, 0.86, 0.05)
|
||||
y = y0 + depth_offset
|
||||
thickness = 0.55
|
||||
return [
|
||||
_wire_segment(doc, prefix + "_Left", (x0, y, z0), (x0, y, z0 + height), thickness, color),
|
||||
_wire_segment(doc, prefix + "_Right", (x0 + width, y, z0), (x0 + width, y, z0 + height), thickness, color),
|
||||
_wire_segment(doc, prefix + "_Top", (x0, y, z0 + height), (x0 + width, y, z0 + height), thickness, color),
|
||||
_wire_segment(doc, prefix + "_Bottom", (x0, y, z0), (x0 + width, y, z0), thickness, color),
|
||||
]
|
||||
|
||||
|
||||
def _export_step(objects):
|
||||
try:
|
||||
import Import
|
||||
|
||||
Import.export(objects, str(STEP_PATH))
|
||||
except Exception:
|
||||
import ImportGui
|
||||
|
||||
ImportGui.export(objects, str(STEP_PATH))
|
||||
|
||||
|
||||
def _create_connector_bank(doc, prefix, x, y, z, rows, cols, plate_height, plate_width):
|
||||
white = (0.93, 0.9, 0.95)
|
||||
dark = (0.04, 0.04, 0.05)
|
||||
metal = (0.58, 0.58, 0.56)
|
||||
objects = [
|
||||
_box(doc, prefix + "_Body", plate_width, 4.0, plate_height, x, y, z, white, 0),
|
||||
_box(doc, prefix + "_SideRailLeft", 1.2, 4.4, plate_height, x - 1.2, y - 0.2, z, metal, 0),
|
||||
_box(doc, prefix + "_SideRailRight", 1.2, 4.4, plate_height, x + plate_width, y - 0.2, z, metal, 0),
|
||||
]
|
||||
x_pitch = plate_width / (cols + 1)
|
||||
z_pitch = plate_height / (rows + 1)
|
||||
for row in range(rows):
|
||||
for col in range(cols):
|
||||
px = x + x_pitch * (col + 1)
|
||||
pz = z + z_pitch * (row + 1)
|
||||
objects.append(
|
||||
_cylinder_y(
|
||||
doc,
|
||||
"{0}_Hole_R{1:02d}_C{2:02d}".format(prefix, row + 1, col + 1),
|
||||
1.0,
|
||||
0.7,
|
||||
px,
|
||||
y - 0.15,
|
||||
pz,
|
||||
dark,
|
||||
)
|
||||
)
|
||||
if col == 0:
|
||||
objects.append(
|
||||
_cylinder_y(
|
||||
doc,
|
||||
"{0}_Screw_R{1:02d}".format(prefix, row + 1),
|
||||
0.55,
|
||||
0.8,
|
||||
x + plate_width + 2.6,
|
||||
y - 0.15,
|
||||
pz,
|
||||
metal,
|
||||
)
|
||||
)
|
||||
return objects
|
||||
|
||||
|
||||
def main():
|
||||
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
doc = App.newDocument("QETPanelAssembly")
|
||||
objects = []
|
||||
|
||||
light = (0.82, 0.84, 0.88)
|
||||
edge = (0.46, 0.43, 0.5)
|
||||
dark_panel = (0.22, 0.2, 0.26)
|
||||
white = (0.95, 0.94, 0.96)
|
||||
black = (0.02, 0.02, 0.02)
|
||||
|
||||
# Cabinet / door, roughly matching the large pale box in the reference video.
|
||||
objects.extend(
|
||||
[
|
||||
_box(doc, "Panel_BackBox", 110.0, 55.0, 180.0, -80.0, -25.0, 0.0, light, 0),
|
||||
_box(doc, "Panel_LeftDoorFace", 6.0, 60.0, 178.0, -88.0, -28.0, 1.0, edge, 0),
|
||||
_box(doc, "Panel_InnerRecess", 72.0, 1.4, 132.0, -68.0, -29.0, 24.0, (0.9, 0.92, 0.96), 18),
|
||||
_box(doc, "Panel_RecessLeftLine", 1.2, 1.8, 132.0, -68.0, -30.0, 24.0, edge, 0),
|
||||
_box(doc, "Panel_RecessRightLine", 1.2, 1.8, 132.0, 2.8, -30.0, 24.0, edge, 0),
|
||||
_box(doc, "Panel_RecessTopLine", 72.0, 1.8, 1.2, -68.0, -30.0, 155.0, edge, 0),
|
||||
_box(doc, "Panel_RecessBottomLine", 72.0, 1.8, 1.2, -68.0, -30.0, 24.0, edge, 0),
|
||||
_cylinder_y(doc, "Panel_HingeTop", 2.3, 1.0, -85.0, -31.0, 134.0, (0.18, 0.18, 0.2)),
|
||||
_cylinder_y(doc, "Panel_HingeBottom", 2.3, 1.0, -85.0, -31.0, 44.0, (0.18, 0.18, 0.2)),
|
||||
_box(doc, "Panel_RightMountPlate", 45.0, 6.0, 168.0, 30.0, -31.0, 6.0, dark_panel, 0),
|
||||
]
|
||||
)
|
||||
|
||||
# Connector banks and small accessory blocks on the right side.
|
||||
objects.extend(_create_connector_bank(doc, "ConnectorBank_Left", 35.0, -36.0, 44.0, 10, 2, 110.0, 18.0))
|
||||
objects.extend(_create_connector_bank(doc, "ConnectorBank_Right", 60.0, -37.0, 50.0, 12, 3, 102.0, 20.0))
|
||||
objects.extend(
|
||||
[
|
||||
_box(doc, "ConnectorBank_LeftTopCap", 18.0, 4.2, 12.0, 35.0, -36.2, 154.0, white, 0),
|
||||
_box(doc, "ConnectorBank_RightTopCap", 20.0, 4.2, 11.0, 60.0, -37.2, 152.0, white, 0),
|
||||
_box(doc, "AccessoryConnector_LowerLeft", 16.0, 4.0, 34.0, 26.0, -37.0, 18.0, white, 0),
|
||||
_box(doc, "AccessoryConnector_LowerRight", 14.0, 4.0, 28.0, 70.0, -38.0, 22.0, white, 0),
|
||||
_cylinder_y(doc, "AccessoryConnector_LowerLeftScrew1", 0.8, 0.7, 34.0, -38.1, 28.0, black),
|
||||
_cylinder_y(doc, "AccessoryConnector_LowerLeftScrew2", 0.8, 0.7, 34.0, -38.1, 42.0, black),
|
||||
_cylinder_y(doc, "AccessoryConnector_LowerRightScrew1", 0.8, 0.7, 77.0, -39.1, 30.0, black),
|
||||
_cylinder_y(doc, "AccessoryConnector_LowerRightScrew2", 0.8, 0.7, 77.0, -39.1, 44.0, black),
|
||||
]
|
||||
)
|
||||
|
||||
# Yellow annotation-like wire frames from the source video.
|
||||
objects.extend(_wire_rect(doc, "WireFrame_LeftBank", 32.0, -41.0, 34.0, 27.0, 130.0))
|
||||
objects.extend(_wire_rect(doc, "WireFrame_RightBank", 56.0, -42.0, 38.0, 33.0, 126.0, -1.2))
|
||||
objects.extend(
|
||||
[
|
||||
_wire_segment(doc, "WireFrame_TopBridge", (45.0, -42.0, 158.0), (88.0, -42.0, 158.0), 0.55, (1.0, 0.86, 0.05)),
|
||||
_wire_segment(doc, "WireFrame_BottomBridge", (42.0, -42.0, 34.0), (88.0, -42.0, 34.0), 0.55, (1.0, 0.86, 0.05)),
|
||||
]
|
||||
)
|
||||
|
||||
doc.recompute()
|
||||
doc.saveAs(str(FCSTD_PATH))
|
||||
_export_step(objects)
|
||||
|
||||
report = {
|
||||
"dimensions_mm": {
|
||||
"overall_height": 180.0,
|
||||
"cabinet_width": 110.0,
|
||||
"cabinet_depth": 55.0,
|
||||
"connector_area_height": 120.0,
|
||||
},
|
||||
"outputs": {"fcstd": str(FCSTD_PATH), "step": str(STEP_PATH)},
|
||||
"objects": [obj.Name for obj in objects],
|
||||
"engineering_binding_properties_present": {
|
||||
obj.Name: [prop for prop in sorted(ENGINEERING_BINDING_PROPERTIES) if prop in obj.PropertiesList]
|
||||
for obj in objects
|
||||
if any(prop in obj.PropertiesList for prop in ENGINEERING_BINDING_PROPERTIES)
|
||||
},
|
||||
"terminal_role_objects": [obj.Name for obj in objects if getattr(obj, "Role", "") == "Terminal"],
|
||||
}
|
||||
REPORT_PATH.write_text(json.dumps(report, indent=2), encoding="utf-8")
|
||||
|
||||
print("Generated FCStd: {0}".format(FCSTD_PATH))
|
||||
print("Generated STEP: {0}".format(STEP_PATH))
|
||||
print("Generated report: {0}".format(REPORT_PATH))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,128 @@
|
||||
{
|
||||
"dimensions_mm": {
|
||||
"overall_height": 180.0,
|
||||
"cabinet_width": 110.0,
|
||||
"cabinet_depth": 55.0,
|
||||
"connector_area_height": 120.0
|
||||
},
|
||||
"outputs": {
|
||||
"fcstd": "D:\\LightWork3D\\data\\examples\\qet_panel_assembly\\qet_panel_assembly.FCStd",
|
||||
"step": "D:\\LightWork3D\\data\\examples\\qet_panel_assembly\\qet_panel_assembly.step"
|
||||
},
|
||||
"objects": [
|
||||
"Panel_BackBox",
|
||||
"Panel_LeftDoorFace",
|
||||
"Panel_InnerRecess",
|
||||
"Panel_RecessLeftLine",
|
||||
"Panel_RecessRightLine",
|
||||
"Panel_RecessTopLine",
|
||||
"Panel_RecessBottomLine",
|
||||
"Panel_HingeTop",
|
||||
"Panel_HingeBottom",
|
||||
"Panel_RightMountPlate",
|
||||
"ConnectorBank_Left_Body",
|
||||
"ConnectorBank_Left_SideRailLeft",
|
||||
"ConnectorBank_Left_SideRailRight",
|
||||
"ConnectorBank_Left_Hole_R01_C01",
|
||||
"ConnectorBank_Left_Screw_R01",
|
||||
"ConnectorBank_Left_Hole_R01_C02",
|
||||
"ConnectorBank_Left_Hole_R02_C01",
|
||||
"ConnectorBank_Left_Screw_R02",
|
||||
"ConnectorBank_Left_Hole_R02_C02",
|
||||
"ConnectorBank_Left_Hole_R03_C01",
|
||||
"ConnectorBank_Left_Screw_R03",
|
||||
"ConnectorBank_Left_Hole_R03_C02",
|
||||
"ConnectorBank_Left_Hole_R04_C01",
|
||||
"ConnectorBank_Left_Screw_R04",
|
||||
"ConnectorBank_Left_Hole_R04_C02",
|
||||
"ConnectorBank_Left_Hole_R05_C01",
|
||||
"ConnectorBank_Left_Screw_R05",
|
||||
"ConnectorBank_Left_Hole_R05_C02",
|
||||
"ConnectorBank_Left_Hole_R06_C01",
|
||||
"ConnectorBank_Left_Screw_R06",
|
||||
"ConnectorBank_Left_Hole_R06_C02",
|
||||
"ConnectorBank_Left_Hole_R07_C01",
|
||||
"ConnectorBank_Left_Screw_R07",
|
||||
"ConnectorBank_Left_Hole_R07_C02",
|
||||
"ConnectorBank_Left_Hole_R08_C01",
|
||||
"ConnectorBank_Left_Screw_R08",
|
||||
"ConnectorBank_Left_Hole_R08_C02",
|
||||
"ConnectorBank_Left_Hole_R09_C01",
|
||||
"ConnectorBank_Left_Screw_R09",
|
||||
"ConnectorBank_Left_Hole_R09_C02",
|
||||
"ConnectorBank_Left_Hole_R10_C01",
|
||||
"ConnectorBank_Left_Screw_R10",
|
||||
"ConnectorBank_Left_Hole_R10_C02",
|
||||
"ConnectorBank_Right_Body",
|
||||
"ConnectorBank_Right_SideRailLeft",
|
||||
"ConnectorBank_Right_SideRailRight",
|
||||
"ConnectorBank_Right_Hole_R01_C01",
|
||||
"ConnectorBank_Right_Screw_R01",
|
||||
"ConnectorBank_Right_Hole_R01_C02",
|
||||
"ConnectorBank_Right_Hole_R01_C03",
|
||||
"ConnectorBank_Right_Hole_R02_C01",
|
||||
"ConnectorBank_Right_Screw_R02",
|
||||
"ConnectorBank_Right_Hole_R02_C02",
|
||||
"ConnectorBank_Right_Hole_R02_C03",
|
||||
"ConnectorBank_Right_Hole_R03_C01",
|
||||
"ConnectorBank_Right_Screw_R03",
|
||||
"ConnectorBank_Right_Hole_R03_C02",
|
||||
"ConnectorBank_Right_Hole_R03_C03",
|
||||
"ConnectorBank_Right_Hole_R04_C01",
|
||||
"ConnectorBank_Right_Screw_R04",
|
||||
"ConnectorBank_Right_Hole_R04_C02",
|
||||
"ConnectorBank_Right_Hole_R04_C03",
|
||||
"ConnectorBank_Right_Hole_R05_C01",
|
||||
"ConnectorBank_Right_Screw_R05",
|
||||
"ConnectorBank_Right_Hole_R05_C02",
|
||||
"ConnectorBank_Right_Hole_R05_C03",
|
||||
"ConnectorBank_Right_Hole_R06_C01",
|
||||
"ConnectorBank_Right_Screw_R06",
|
||||
"ConnectorBank_Right_Hole_R06_C02",
|
||||
"ConnectorBank_Right_Hole_R06_C03",
|
||||
"ConnectorBank_Right_Hole_R07_C01",
|
||||
"ConnectorBank_Right_Screw_R07",
|
||||
"ConnectorBank_Right_Hole_R07_C02",
|
||||
"ConnectorBank_Right_Hole_R07_C03",
|
||||
"ConnectorBank_Right_Hole_R08_C01",
|
||||
"ConnectorBank_Right_Screw_R08",
|
||||
"ConnectorBank_Right_Hole_R08_C02",
|
||||
"ConnectorBank_Right_Hole_R08_C03",
|
||||
"ConnectorBank_Right_Hole_R09_C01",
|
||||
"ConnectorBank_Right_Screw_R09",
|
||||
"ConnectorBank_Right_Hole_R09_C02",
|
||||
"ConnectorBank_Right_Hole_R09_C03",
|
||||
"ConnectorBank_Right_Hole_R10_C01",
|
||||
"ConnectorBank_Right_Screw_R10",
|
||||
"ConnectorBank_Right_Hole_R10_C02",
|
||||
"ConnectorBank_Right_Hole_R10_C03",
|
||||
"ConnectorBank_Right_Hole_R11_C01",
|
||||
"ConnectorBank_Right_Screw_R11",
|
||||
"ConnectorBank_Right_Hole_R11_C02",
|
||||
"ConnectorBank_Right_Hole_R11_C03",
|
||||
"ConnectorBank_Right_Hole_R12_C01",
|
||||
"ConnectorBank_Right_Screw_R12",
|
||||
"ConnectorBank_Right_Hole_R12_C02",
|
||||
"ConnectorBank_Right_Hole_R12_C03",
|
||||
"ConnectorBank_LeftTopCap",
|
||||
"ConnectorBank_RightTopCap",
|
||||
"AccessoryConnector_LowerLeft",
|
||||
"AccessoryConnector_LowerRight",
|
||||
"AccessoryConnector_LowerLeftScrew1",
|
||||
"AccessoryConnector_LowerLeftScrew2",
|
||||
"AccessoryConnector_LowerRightScrew1",
|
||||
"AccessoryConnector_LowerRightScrew2",
|
||||
"WireFrame_LeftBank_Left",
|
||||
"WireFrame_LeftBank_Right",
|
||||
"WireFrame_LeftBank_Top",
|
||||
"WireFrame_LeftBank_Bottom",
|
||||
"WireFrame_RightBank_Left",
|
||||
"WireFrame_RightBank_Right",
|
||||
"WireFrame_RightBank_Top",
|
||||
"WireFrame_RightBank_Bottom",
|
||||
"WireFrame_TopBridge",
|
||||
"WireFrame_BottomBridge"
|
||||
],
|
||||
"engineering_binding_properties_present": {},
|
||||
"terminal_role_objects": []
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
# Panel Assembly Model Asset Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Generate a reusable electrical panel assembly asset as `.FCStd` and `.step`.
|
||||
|
||||
**Architecture:** Add one FreeCAD Python generator in `data/examples/qet_panel_assembly/`. The script bootstraps the registered Windows FreeCAD runtime, creates the panel assembly geometry, saves the FCStd, exports STEP, and writes a report used by verification.
|
||||
|
||||
**Tech Stack:** FreeCAD Python API, Part workbench primitives, registered QETDeps FreeCAD Python runtime, Markdown documentation.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Generator and README
|
||||
|
||||
**Files:**
|
||||
- Create: `data/examples/qet_panel_assembly/create_qet_panel_assembly.py`
|
||||
- Create: `data/examples/qet_panel_assembly/README.md`
|
||||
|
||||
- [ ] **Step 1: Create generator**
|
||||
|
||||
Create `create_qet_panel_assembly.py`. It must:
|
||||
|
||||
- Bootstrap Windows FreeCAD DLL paths from `QET_FREECAD_RUNTIME_JSON` or `%LOCALAPPDATA%\QETDeps\runtime.json`.
|
||||
- Generate cabinet/door geometry, dark mounting plate, two white perforated connector banks, small white connector modules, and yellow wire frame geometry.
|
||||
- Save `qet_panel_assembly.FCStd`.
|
||||
- Export `qet_panel_assembly.step`.
|
||||
- Write `qet_panel_assembly_report.json`.
|
||||
- Avoid terminal LCS objects and engineering binding properties.
|
||||
|
||||
- [ ] **Step 2: Create README**
|
||||
|
||||
Create `README.md` describing dimensions, visual parts, output file roles, regeneration command, and the absence of terminal semantics.
|
||||
|
||||
### Task 2: Generate and Verify
|
||||
|
||||
**Files:**
|
||||
- Generate: `data/examples/qet_panel_assembly/qet_panel_assembly.FCStd`
|
||||
- Generate: `data/examples/qet_panel_assembly/qet_panel_assembly.step`
|
||||
- Generate: `data/examples/qet_panel_assembly/qet_panel_assembly_report.json`
|
||||
|
||||
- [ ] **Step 1: Run generator**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
$runtime = Get-Content -LiteralPath 'C:\Users\ng123\AppData\Local\QETDeps\runtime.json' -Raw | ConvertFrom-Json
|
||||
$env:QET_FREECAD_RUNTIME_JSON = 'C:\Users\ng123\AppData\Local\QETDeps\runtime.json'
|
||||
& $runtime.freecad_python 'D:\LightWork3D\data\examples\qet_panel_assembly\create_qet_panel_assembly.py'
|
||||
```
|
||||
|
||||
Expected: command exits `0` and prints generated output paths.
|
||||
|
||||
- [ ] **Step 2: Verify FCStd and STEP**
|
||||
|
||||
Open the FCStd with the registered FreeCAD Python runtime and assert:
|
||||
|
||||
- The document has objects.
|
||||
- At least one object name starts with `Panel_`.
|
||||
- At least one object name starts with `ConnectorBank_`.
|
||||
- At least one object name starts with `WireFrame_`.
|
||||
- No object has `Role="Terminal"`.
|
||||
- No object has `QetProjectUuid`, `QetElementUuid`, `QetTerminalUuid`, or `QetInstanceId`.
|
||||
|
||||
Read the first line of the STEP file and assert it is `ISO-10303-21;`.
|
||||
|
||||
### Task 3: Commit
|
||||
|
||||
**Files:**
|
||||
- Add: `docs/superpowers/plans/2026-05-27-panel-assembly-model-implementation.md`
|
||||
- Add: `data/examples/qet_panel_assembly/`
|
||||
|
||||
- [ ] **Step 1: Stage intended files only**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
git add -- docs/superpowers/plans/2026-05-27-panel-assembly-model-implementation.md data/examples/qet_panel_assembly
|
||||
git diff --cached --name-only
|
||||
```
|
||||
|
||||
Expected: only the plan and `qet_panel_assembly` files are staged.
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
git commit -m "feat: add panel assembly model asset"
|
||||
```
|
||||
|
||||
Expected: a commit containing only the generated panel assembly asset files and plan.
|
||||
|
||||
## Self-Review
|
||||
|
||||
- Spec coverage: implements FCStd, STEP, generator, README, report, and semantic verification.
|
||||
- Placeholder scan: no TBD/TODO/fill-later language is present.
|
||||
- Type consistency: output file names match the approved spec.
|
||||
Loading…
Reference in New Issue