This commit is contained in:
Jason Stedwell
2026-06-17 16:03:26 -05:00
parent fa1e9b68c7
commit c1abe36822
99 changed files with 1562887 additions and 0 deletions
Vendored
BIN
View File
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
{"sessionId":"1165102c-0750-4e7c-9c0c-9e7d88851166","pid":2450,"procStart":"Wed Jun 17 20:05:33 2026","acquiredAt":1781728917587}
+39
View File
@@ -0,0 +1,39 @@
{
"permissions": {
"allow": [
"Bash(echo \"=== bootstrap marker ===\")",
"Bash(curl -s -H 'Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab' https://echoapi.alwisp.com/vault/_agent/echo-vault.md)",
"Bash(echo \"=== operator-preferences ===\")",
"Bash(curl -s -H 'Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab' https://echoapi.alwisp.com/vault/_agent/memory/semantic/operator-preferences.md)",
"Bash(echo \"=== current-context ===\")",
"Bash(curl -s -X POST -H 'Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab' https://echoapi.alwisp.com/search/simple/?query=deployment+environment)",
"Bash(curl -s -X POST -H 'Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab' https://echoapi.alwisp.com/search/simple/?query=preferred+deploy+stack+hosting)",
"Bash(curl -s -H 'Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab' https://echoapi.alwisp.com/vault/_agent/memory/semantic/tech-stack.md)",
"Bash(curl -s -X POST -H 'Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab' https://echoapi.alwisp.com/search/simple/?query=step+parse)",
"Bash(curl -s -X POST -H 'Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab' https://echoapi.alwisp.com/search/simple/?query=STEP+processor+CAD)",
"Bash(cat)",
"Bash(curl -s -X PUT -H 'Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab' -H 'Content-Type: text/markdown' --data-binary @/tmp/step_parser.md https://echoapi.alwisp.com/vault/projects/incubating/step-parser.md)",
"Bash(curl -s -o /dev/null -w 'HTTP %{http_code}\\\\n' -H 'Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab' https://echoapi.alwisp.com/vault/projects/incubating/step-parser.md)",
"Bash(docker version *)",
"Bash(docker buildx *)",
"Bash(awk '{print $2}')",
"Bash(chmod +x build.sh smoke-test.sh)",
"Bash(bash -n build.sh)",
"Bash(bash -n smoke-test.sh)",
"Bash(docker info *)",
"Bash(/Applications/Docker.app/Contents/Resources/bin/docker version *)",
"Bash(open -a Docker)",
"Bash(./build.sh)",
"Bash(echo \"BUILD_EXIT=$?\")",
"Bash(docker image *)",
"Bash(docker run *)",
"Bash(tee -a _phase0_build.log)",
"Bash(rm -rf _phase0_out)",
"Bash(./smoke-test.sh)",
"Bash(echo \"SMOKE_EXIT=$?\")",
"Bash(tee -a _phase0_smoke.log)",
"Bash(awk '/Traceback \\\\\\(most recent call last\\\\\\)/{f=1} f{print; n++} n>35{exit}' _phase0_smoke.log)",
"Bash(echo \"BUILD2_EXIT=$?\")"
]
}
}
+14
View File
@@ -0,0 +1,14 @@
# Keep the build context lean. The .step samples ARE needed (smoke test runs
# them), but their generated outputs and caches are not.
**/__pycache__/
**/*.pyc
.DS_Store
**/.DS_Store
.claude/
.git/
_phase0_out/
# Pre-generated sample outputs — regenerated by the smoke test, no need to bake in.
skill.src/*.png
skill.src/*.csv
skill.src/*_bom.xlsx
+3
View File
@@ -0,0 +1,3 @@
# Copy to .env and fill in. Only needed for the translation path (--translate);
# Phase 0 smoke test runs --no-translate and does not require this.
ANTHROPIC_API_KEY=sk-ant-...
+64
View File
@@ -0,0 +1,64 @@
# STEP Parser — Phase 0 CLI container
#
# Goal: prove the heavy native stack runs on linux/amd64 (Unraid target):
# - CAD kernel build123d → cadquery-ocp (OpenCASCADE)
# - offscreen render pyrender via OSMesa (software GL, no GPU needed)
# - diagram export cairosvg (cairo / pango)
#
# Build for Unraid (x86_64) even from an arm64 Mac (uses QEMU emulation):
# docker buildx build --platform linux/amd64 -t step-parser:dev --load .
# or just: ./build.sh (build.sh passes --platform linux/amd64)
FROM python:3.11-slim-bookworm
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
# Force pyrender/PyOpenGL onto the software rasterizer — no GPU on Unraid.
PYOPENGL_PLATFORM=osmesa
# System libraries grouped by which dependency needs them:
# OpenCASCADE / OCP runtime : libgl1 libglu1-mesa X11 libs fontconfig libgomp1
# pyrender offscreen (OSMesa): libosmesa6
# cairosvg : libcairo2 libpango* libgdk-pixbuf libffi8
RUN apt-get update && apt-get install -y --no-install-recommends \
libgl1 \
libglu1-mesa \
libosmesa6 \
libxrender1 \
libxext6 \
libsm6 \
libx11-6 \
libxi6 \
libfontconfig1 \
libglib2.0-0 \
libgomp1 \
libcairo2 \
libpango-1.0-0 \
libpangocairo-1.0-0 \
libgdk-pixbuf-2.0-0 \
libffi8 \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
# pyrender hard-pins PyOpenGL==3.1.0, but the OSMesa offscreen backend needs
# OSMesaCreateContextAttribs, which only exists in PyOpenGL >=3.1.5. Install
# pyrender's pin first, then force-upgrade PyOpenGL (--no-deps leaves pyrender
# itself untouched, avoiding the resolver conflict).
RUN pip install --upgrade pip \
&& pip install -r requirements.txt \
&& pip install --no-deps --upgrade "PyOpenGL==3.1.7"
# The skill source lives in skill.src/ in the repo root; copy it into the image.
# step_processor.py adds its own dir to sys.path[0], so `import modules` resolves
# regardless of the working directory.
COPY skill.src ./skill.src
# Generated artifacts (xlsx, png, svg, _EN.step) are written next to the input
# STEP file. Mount a host directory here and process files inside it.
VOLUME ["/data"]
ENTRYPOINT ["python", "skill.src/step_processor.py"]
CMD ["--help"]
+85
View File
@@ -0,0 +1,85 @@
# Phase 0 — Containerize the CLI
> **Status: ✅ DONE (2026-06-17).** `step-parser:dev` builds for linux/amd64 and all
> three sample assemblies (MR16/MR27/MR28) pass the full pipeline — load, BOM .xlsx,
> 6-view OSMesa thumbnails, geometry query, and dimensional-diagram SVG. Validated on
> a Mac (arm64) via QEMU emulation; image arch confirmed x86_64. Deps frozen in
> `requirements.lock.txt`. Two fixes were required (see **Fixes applied** below).
**Goal:** prove the heavy native stack runs on **linux/amd64** (the Unraid target) before any web code is written. This is the highest-risk part of the [step-parser](projects/active/step-parser.md) roadmap; everything after it is conventional React-on-FastAPI.
What's being de-risked:
| Path | Library | Container concern |
|------|---------|-------------------|
| STEP load + geometry | build123d → cadquery-ocp (OpenCASCADE) | manylinux x86_64 wheels; OCC needs GL/X11/OpenMP runtime libs |
| 6-view thumbnails | trimesh + pyrender | **offscreen GL with no GPU** → forced onto OSMesa software rasterizer |
| Dimensional diagram | cairosvg | cairo / pango / gdk-pixbuf system libs |
| BOM | pandas + openpyxl | none |
| Translation | anthropic (Claude API) | network + `ANTHROPIC_API_KEY` (skipped in this phase) |
## Files (all in repo root, `skill.src/` untouched)
- `Dockerfile``python:3.11-slim-bookworm`, amd64-pinned, OSMesa + cairo system libs
- `requirements.txt` — Python deps (floor pins; freeze to a lockfile after first green build)
- `.dockerignore` — keeps `.step` samples, drops caches and pre-generated outputs
- `.env.example``ANTHROPIC_API_KEY` (translation only)
- `build.sh` — buildx build for linux/amd64 (+ registry push hints)
- `smoke-test.sh` — runs MR16/MR27/MR28 through BOM + query + diagram; checks artifacts
## Run it (on a Docker host)
> Docker is **not** installed on the dev Mac this was authored on, and it's arm64.
> The build runs anywhere with Docker + buildx: Unraid itself, or a Mac/PC with
> Docker Desktop. From arm64 the amd64 build uses QEMU emulation (slow first time;
> the OpenCASCADE wheel is large).
```bash
./build.sh # → step-parser:dev (linux/amd64)
./smoke-test.sh # → _phase0_out/ with xlsx, pngs, svg per sample
```
Ad-hoc single file:
```bash
docker run --rm -v "$PWD/_phase0_out:/data" step-parser:dev "/data/MyPart.step" --no-translate
```
## Pass criteria
For each of the three samples the smoke test must produce, with a clean exit:
- `*_bom.xlsx` (kernel loaded + assembly traversed + Excel written)
- at least one `*_<view>.png` (pyrender succeeded under OSMesa — the riskiest item)
- `*__external-diagram.svg` (cairosvg succeeded)
All three green ⇒ Phase 0 done. Freeze the lockfile and move to Phase 1 (FastAPI wrapper).
## Fixes applied during Phase 0
1. **PyOpenGL / OSMesa version clash (packaging).** `pyrender 0.1.45` hard-pins
`PyOpenGL==3.1.0`, but the OSMesa offscreen backend needs `OSMesaCreateContextAttribs`
(added in PyOpenGL ≥3.1.5). The Dockerfile installs pyrender's pin, then force-upgrades
with `pip install --no-deps --upgrade "PyOpenGL==3.1.7"` (leaves pyrender untouched,
avoids the resolver conflict). Without this, all 6 renders fail with an ImportError.
2. **`external_diagram.py` line 318 `KeyError: 'position'` (source bug).** `_arrange_views()`
returns view dicts keyed `ox`/`oy`, but the layout map read a non-existent `position` key —
crashed the diagram on any platform. Fixed to `{v["name"]: (v["ox"], v["oy"]) ...}`.
## Known follow-ups (deferred, out of Phase 0 scope)
- **Active-area detection is a no-op on Linux.** `external_diagram.py` imports `OCC.Core.*`
(pythonocc-core) for active-area detection, but the loader uses build123d's `OCP` kernel.
The import fails and is caught (logged as a warning) — the diagram still renders, just
without the auto-detected active-area dimension. Porting `OCC``OCP` is a Phase 1 task.
- **Pin to the lockfile for reproducible Unraid builds.** `requirements.lock.txt` is frozen;
switch the Dockerfile's `COPY requirements.txt` / install line to it when convenient.
- **FreeCAD fallback is macOS-only right now.** `skill.src/modules/loader.py` hardcodes
`/Applications/FreeCAD.app/...`, so on Linux the fallback no-ops and returns `None`.
build123d is the primary path and should handle the samples; wiring a Linux
`freecadcmd` fallback means editing `loader.py` and is left for later.
- **Lockfile.** Replace the floor pins with `requirements.lock.txt` once the build is green.
- **OSMesa is CPU rendering.** Fine for correctness validation; thumbnail speed on the
~25 MB sample assemblies is a Phase 1+ concern. The roadmap's recommended pivot —
export GLB and render client-side with three.js — sidesteps server GL for the live viewer.
+359
View File
@@ -0,0 +1,359 @@
Building step-parser:dev for linux/amd64 ...
#0 building with "desktop-linux" instance using docker driver
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 2.40kB done
#1 DONE 0.0s
#2 [internal] load metadata for docker.io/library/python:3.11-slim-bookworm
#2 DONE 2.1s
#3 [internal] load .dockerignore
#3 transferring context: 392B done
#3 DONE 0.0s
#4 [internal] load build context
#4 transferring context: 1.14kB done
#4 DONE 0.0s
#5 [1/6] FROM docker.io/library/python:3.11-slim-bookworm@sha256:e2d3af735aff6eeee600b1933bedd99da6645fedf572cc12ef4cc1331f2ceebe
#5 resolve docker.io/library/python:3.11-slim-bookworm@sha256:e2d3af735aff6eeee600b1933bedd99da6645fedf572cc12ef4cc1331f2ceebe 0.0s done
#5 DONE 0.0s
#6 [2/6] RUN apt-get update && apt-get install -y --no-install-recommends libgl1 libglu1-mesa libosmesa6 libxrender1 libxext6 libsm6 libx11-6 libxi6 libfontconfig1 libglib2.0-0 libgomp1 libcairo2 libpango-1.0-0 libpangocairo-1.0-0 libgdk-pixbuf-2.0-0 libffi8 ca-certificates && rm -rf /var/lib/apt/lists/*
#6 CACHED
#7 [3/6] WORKDIR /app
#7 CACHED
#8 [4/6] COPY requirements.txt .
#8 CACHED
#9 [5/6] RUN pip install --upgrade pip && pip install -r requirements.txt && pip install --no-deps --upgrade "PyOpenGL==3.1.7"
#9 1.203 Requirement already satisfied: pip in /usr/local/lib/python3.11/site-packages (24.0)
#9 1.922 Collecting pip
#9 2.393 Downloading pip-26.1.2-py3-none-any.whl.metadata (4.6 kB)
#9 2.510 Downloading pip-26.1.2-py3-none-any.whl (1.8 MB)
#9 3.374 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.8/1.8 MB 2.2 MB/s eta 0:00:00
#9 3.445 Installing collected packages: pip
#9 3.445 Attempting uninstall: pip
#9 3.448 Found existing installation: pip 24.0
#9 3.472 Uninstalling pip-24.0:
#9 3.542 Successfully uninstalled pip-24.0
#9 4.080 Successfully installed pip-26.1.2
#9 4.080 WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
#9 6.567 Collecting build123d>=0.7 (from -r requirements.txt (line 10))
#9 6.935 Downloading build123d-0.11.0-py3-none-any.whl.metadata (14 kB)
#9 7.563 Collecting numpy>=1.24 (from -r requirements.txt (line 13))
#9 7.656 Downloading numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (6.6 kB)
#9 7.996 Collecting pandas>=2.0 (from -r requirements.txt (line 14))
#9 8.093 Downloading pandas-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.metadata (79 kB)
#9 8.363 Collecting openpyxl>=3.1 (from -r requirements.txt (line 15))
#9 8.446 Downloading openpyxl-3.1.5-py2.py3-none-any.whl.metadata (2.5 kB)
#9 8.656 Collecting trimesh>=4.0 (from -r requirements.txt (line 18))
#9 8.746 Downloading trimesh-4.12.2-py3-none-any.whl.metadata (13 kB)
#9 8.856 Collecting pyrender>=0.1.45 (from -r requirements.txt (line 19))
#9 8.943 Downloading pyrender-0.1.45-py3-none-any.whl.metadata (1.5 kB)
#9 9.347 Collecting Pillow>=10.0 (from -r requirements.txt (line 20))
#9 9.443 Downloading pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (8.8 kB)
#9 9.502 Collecting cairosvg>=2.7 (from -r requirements.txt (line 23))
#9 9.537 Downloading cairosvg-2.9.0-py3-none-any.whl.metadata (2.7 kB)
#9 9.619 Collecting anthropic>=0.39 (from -r requirements.txt (line 26))
#9 9.653 Downloading anthropic-0.109.2-py3-none-any.whl.metadata (3.2 kB)
#9 9.707 Collecting cadquery-ocp-novtk<8.0,>=7.9 (from build123d>=0.7->-r requirements.txt (line 10))
#9 9.748 Downloading cadquery_ocp_novtk-7.9.3.1.1-cp311-cp311-manylinux_2_31_x86_64.whl.metadata (886 bytes)
#9 9.800 Collecting typing_extensions<5,>=4.6.0 (from build123d>=0.7->-r requirements.txt (line 10))
#9 9.836 Downloading typing_extensions-4.15.0-py3-none-any.whl.metadata (3.3 kB)
#9 9.892 Collecting svgpathtools<2,>=1.5.1 (from build123d>=0.7->-r requirements.txt (line 10))
#9 9.935 Downloading svgpathtools-1.7.2-py2.py3-none-any.whl.metadata (22 kB)
#9 9.995 Collecting anytree<3,>=2.8.0 (from build123d>=0.7->-r requirements.txt (line 10))
#9 10.04 Downloading anytree-2.13.0-py3-none-any.whl.metadata (8.0 kB)
#9 10.43 Collecting ezdxf<2,>=1.1.0 (from build123d>=0.7->-r requirements.txt (line 10))
#9 10.49 Downloading ezdxf-1.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (9.9 kB)
#9 10.59 Collecting ipython<10,>=8.0.0 (from build123d>=0.7->-r requirements.txt (line 10))
#9 10.63 Downloading ipython-9.14.1-py3-none-any.whl.metadata (4.7 kB)
#9 10.67 Collecting ocpsvg<0.7,>=0.6 (from build123d>=0.7->-r requirements.txt (line 10))
#9 10.72 Downloading ocpsvg-0.6.0-py3-none-any.whl.metadata (858 bytes)
#9 10.77 Collecting ocp_gordon<0.3,>=0.2 (from build123d>=0.7->-r requirements.txt (line 10))
#9 10.81 Downloading ocp_gordon-0.2.0-py3-none-any.whl.metadata (4.9 kB)
#9 10.85 Collecting trianglesolver (from build123d>=0.7->-r requirements.txt (line 10))
#9 10.89 Downloading trianglesolver-1.2-py3-none-any.whl.metadata (1.7 kB)
#9 10.96 Collecting sympy (from build123d>=0.7->-r requirements.txt (line 10))
#9 11.00 Downloading sympy-1.14.0-py3-none-any.whl.metadata (12 kB)
#9 11.20 Collecting scipy (from build123d>=0.7->-r requirements.txt (line 10))
#9 11.23 Downloading scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (62 kB)
#9 11.40 Collecting scikit-learn<2,>=1.5 (from build123d>=0.7->-r requirements.txt (line 10))
#9 11.48 Downloading scikit_learn-1.9.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (11 kB)
#9 11.64 Collecting webcolors~=24.8.0 (from build123d>=0.7->-r requirements.txt (line 10))
#9 11.79 Downloading webcolors-24.8.0-py3-none-any.whl.metadata (2.6 kB)
#9 11.96 Collecting requests<3,>=2.32 (from build123d>=0.7->-r requirements.txt (line 10))
#9 12.06 Downloading requests-2.34.2-py3-none-any.whl.metadata (4.8 kB)
#9 12.16 Collecting lib3mf>=2.4.1 (from build123d>=0.7->-r requirements.txt (line 10))
#9 12.22 Downloading lib3mf-2.5.0-py3-none-manylinux2014_x86_64.whl.metadata (6.2 kB)
#9 12.30 Collecting cadquery-ocp-proxy==7.9.3.1.1 (from cadquery-ocp-novtk<8.0,>=7.9->build123d>=0.7->-r requirements.txt (line 10))
#9 12.36 Downloading cadquery_ocp_proxy-7.9.3.1.1-py3-none-any.whl.metadata (5.2 kB)
#9 12.45 Collecting pyparsing>=3.0.0 (from ezdxf<2,>=1.1.0->build123d>=0.7->-r requirements.txt (line 10))
#9 12.51 Downloading pyparsing-3.3.2-py3-none-any.whl.metadata (5.8 kB)
#9 12.81 Collecting fonttools (from ezdxf<2,>=1.1.0->build123d>=0.7->-r requirements.txt (line 10))
#9 12.93 Downloading fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (118 kB)
#9 13.25 Collecting decorator>=5.1.0 (from ipython<10,>=8.0.0->build123d>=0.7->-r requirements.txt (line 10))
#9 13.36 Downloading decorator-5.3.1-py3-none-any.whl.metadata (3.9 kB)
#9 13.48 Collecting ipython-pygments-lexers>=1.0.0 (from ipython<10,>=8.0.0->build123d>=0.7->-r requirements.txt (line 10))
#9 13.56 Downloading ipython_pygments_lexers-1.1.1-py3-none-any.whl.metadata (1.1 kB)
#9 13.67 Collecting jedi>=0.18.2 (from ipython<10,>=8.0.0->build123d>=0.7->-r requirements.txt (line 10))
#9 13.77 Downloading jedi-0.20.0-py2.py3-none-any.whl.metadata (23 kB)
#9 13.90 Collecting matplotlib-inline>=0.1.6 (from ipython<10,>=8.0.0->build123d>=0.7->-r requirements.txt (line 10))
#9 13.99 Downloading matplotlib_inline-0.2.2-py3-none-any.whl.metadata (2.4 kB)
#9 14.09 Collecting pexpect>4.6 (from ipython<10,>=8.0.0->build123d>=0.7->-r requirements.txt (line 10))
#9 14.19 Downloading pexpect-4.9.0-py2.py3-none-any.whl.metadata (2.5 kB)
#9 14.34 Collecting prompt_toolkit<3.1.0,>=3.0.41 (from ipython<10,>=8.0.0->build123d>=0.7->-r requirements.txt (line 10))
#9 14.44 Downloading prompt_toolkit-3.0.52-py3-none-any.whl.metadata (6.4 kB)
#9 14.70 Collecting psutil>=7 (from ipython<10,>=8.0.0->build123d>=0.7->-r requirements.txt (line 10))
#9 14.89 Downloading psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl.metadata (22 kB)
#9 15.16 Collecting pygments>=2.14.0 (from ipython<10,>=8.0.0->build123d>=0.7->-r requirements.txt (line 10))
#9 15.28 Downloading pygments-2.20.0-py3-none-any.whl.metadata (2.5 kB)
#9 15.41 Collecting stack_data>=0.6.0 (from ipython<10,>=8.0.0->build123d>=0.7->-r requirements.txt (line 10))
#9 15.55 Downloading stack_data-0.6.3-py3-none-any.whl.metadata (18 kB)
#9 15.67 Collecting traitlets>=5.13.0 (from ipython<10,>=8.0.0->build123d>=0.7->-r requirements.txt (line 10))
#9 15.70 Downloading traitlets-5.15.1-py3-none-any.whl.metadata (10 kB)
#9 15.78 Collecting svgelements<2,>=1.9.1 (from ocpsvg<0.7,>=0.6->build123d>=0.7->-r requirements.txt (line 10))
#9 15.82 Downloading svgelements-1.9.6-py2.py3-none-any.whl.metadata (44 kB)
#9 15.91 Collecting wcwidth (from prompt_toolkit<3.1.0,>=3.0.41->ipython<10,>=8.0.0->build123d>=0.7->-r requirements.txt (line 10))
#9 15.96 Downloading wcwidth-0.8.1-py3-none-any.whl.metadata (43 kB)
#9 16.26 Collecting charset_normalizer<4,>=2 (from requests<3,>=2.32->build123d>=0.7->-r requirements.txt (line 10))
#9 16.51 Downloading charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (40 kB)
#9 16.81 Collecting idna<4,>=2.5 (from requests<3,>=2.32->build123d>=0.7->-r requirements.txt (line 10))
#9 16.99 Downloading idna-3.18-py3-none-any.whl.metadata (6.1 kB)
#9 17.14 Collecting urllib3<3,>=1.26 (from requests<3,>=2.32->build123d>=0.7->-r requirements.txt (line 10))
#9 17.21 Downloading urllib3-2.7.0-py3-none-any.whl.metadata (6.9 kB)
#9 17.29 Collecting certifi>=2023.5.7 (from requests<3,>=2.32->build123d>=0.7->-r requirements.txt (line 10))
#9 17.33 Downloading certifi-2026.6.17-py3-none-any.whl.metadata (2.5 kB)
#9 17.40 Collecting joblib>=1.4.0 (from scikit-learn<2,>=1.5->build123d>=0.7->-r requirements.txt (line 10))
#9 17.45 Downloading joblib-1.5.3-py3-none-any.whl.metadata (5.5 kB)
#9 17.54 Collecting narwhals>=2.0.1 (from scikit-learn<2,>=1.5->build123d>=0.7->-r requirements.txt (line 10))
#9 17.58 Downloading narwhals-2.22.1-py3-none-any.whl.metadata (15 kB)
#9 17.65 Collecting threadpoolctl>=3.5.0 (from scikit-learn<2,>=1.5->build123d>=0.7->-r requirements.txt (line 10))
#9 17.70 Downloading threadpoolctl-3.6.0-py3-none-any.whl.metadata (13 kB)
#9 17.79 Collecting svgwrite (from svgpathtools<2,>=1.5.1->build123d>=0.7->-r requirements.txt (line 10))
#9 17.85 Downloading svgwrite-1.4.3-py3-none-any.whl.metadata (8.8 kB)
#9 17.94 Collecting python-dateutil>=2.8.2 (from pandas>=2.0->-r requirements.txt (line 14))
#9 18.02 Downloading python_dateutil-2.9.0.post0-py2.py3-none-any.whl.metadata (8.4 kB)
#9 18.13 Collecting et-xmlfile (from openpyxl>=3.1->-r requirements.txt (line 15))
#9 18.23 Downloading et_xmlfile-2.0.0-py3-none-any.whl.metadata (2.7 kB)
#9 18.40 Collecting freetype-py (from pyrender>=0.1.45->-r requirements.txt (line 19))
#9 18.55 Downloading freetype_py-2.5.1-py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl.metadata (6.3 kB)
#9 18.72 Collecting imageio (from pyrender>=0.1.45->-r requirements.txt (line 19))
#9 18.84 Downloading imageio-2.37.3-py3-none-any.whl.metadata (9.7 kB)
#9 19.02 Collecting networkx (from pyrender>=0.1.45->-r requirements.txt (line 19))
#9 19.12 Downloading networkx-3.6.1-py3-none-any.whl.metadata (6.8 kB)
#9 19.28 Collecting pyglet>=1.4.10 (from pyrender>=0.1.45->-r requirements.txt (line 19))
#9 19.38 Downloading pyglet-2.1.14-py3-none-any.whl.metadata (7.7 kB)
#9 19.51 Collecting PyOpenGL==3.1.0 (from pyrender>=0.1.45->-r requirements.txt (line 19))
#9 19.61 Downloading PyOpenGL-3.1.0.zip (2.2 MB)
#9 21.38 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.2/2.2 MB 1.4 MB/s 0:00:01
#9 21.53 Installing build dependencies: started
#9 24.60 Installing build dependencies: finished with status 'done'
#9 24.60 Getting requirements to build wheel: started
#9 25.23 Getting requirements to build wheel: finished with status 'done'
#9 25.24 Preparing metadata (pyproject.toml): started
#9 25.85 Preparing metadata (pyproject.toml): finished with status 'done'
#9 26.04 Collecting six (from pyrender>=0.1.45->-r requirements.txt (line 19))
#9 26.14 Downloading six-1.17.0-py2.py3-none-any.whl.metadata (1.7 kB)
#9 26.25 Collecting cairocffi (from cairosvg>=2.7->-r requirements.txt (line 23))
#9 26.35 Downloading cairocffi-1.7.1-py3-none-any.whl.metadata (3.3 kB)
#9 26.46 Collecting cssselect2 (from cairosvg>=2.7->-r requirements.txt (line 23))
#9 26.56 Downloading cssselect2-0.9.0-py3-none-any.whl.metadata (2.9 kB)
#9 26.68 Collecting defusedxml (from cairosvg>=2.7->-r requirements.txt (line 23))
#9 26.79 Downloading defusedxml-0.7.1-py2.py3-none-any.whl.metadata (32 kB)
#9 26.94 Collecting tinycss2 (from cairosvg>=2.7->-r requirements.txt (line 23))
#9 27.05 Downloading tinycss2-1.5.1-py3-none-any.whl.metadata (3.0 kB)
#9 27.19 Collecting anyio<5,>=3.5.0 (from anthropic>=0.39->-r requirements.txt (line 26))
#9 27.30 Downloading anyio-4.14.0-py3-none-any.whl.metadata (4.6 kB)
#9 27.43 Collecting distro<2,>=1.7.0 (from anthropic>=0.39->-r requirements.txt (line 26))
#9 27.54 Downloading distro-1.9.0-py3-none-any.whl.metadata (6.8 kB)
#9 27.58 Collecting docstring-parser<1,>=0.15 (from anthropic>=0.39->-r requirements.txt (line 26))
#9 27.62 Downloading docstring_parser-0.18.0-py3-none-any.whl.metadata (3.5 kB)
#9 27.68 Collecting httpx<1,>=0.25.0 (from anthropic>=0.39->-r requirements.txt (line 26))
#9 27.71 Downloading httpx-0.28.1-py3-none-any.whl.metadata (7.1 kB)
#9 27.86 Collecting jiter<1,>=0.4.0 (from anthropic>=0.39->-r requirements.txt (line 26))
#9 27.91 Downloading jiter-0.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (5.2 kB)
#9 28.19 Collecting pydantic<3,>=1.9.0 (from anthropic>=0.39->-r requirements.txt (line 26))
#9 28.28 Downloading pydantic-2.13.4-py3-none-any.whl.metadata (109 kB)
#9 28.43 Collecting sniffio (from anthropic>=0.39->-r requirements.txt (line 26))
#9 28.53 Downloading sniffio-1.3.1-py3-none-any.whl.metadata (3.9 kB)
#9 28.79 Collecting httpcore==1.* (from httpx<1,>=0.25.0->anthropic>=0.39->-r requirements.txt (line 26))
#9 29.03 Downloading httpcore-1.0.9-py3-none-any.whl.metadata (21 kB)
#9 29.22 Collecting h11>=0.16 (from httpcore==1.*->httpx<1,>=0.25.0->anthropic>=0.39->-r requirements.txt (line 26))
#9 29.31 Downloading h11-0.16.0-py3-none-any.whl.metadata (8.3 kB)
#9 29.41 Collecting annotated-types>=0.6.0 (from pydantic<3,>=1.9.0->anthropic>=0.39->-r requirements.txt (line 26))
#9 29.49 Downloading annotated_types-0.7.0-py3-none-any.whl.metadata (15 kB)
#9 30.52 Collecting pydantic-core==2.46.4 (from pydantic<3,>=1.9.0->anthropic>=0.39->-r requirements.txt (line 26))
#9 30.55 Downloading pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.6 kB)
#9 30.60 Collecting typing-inspection>=0.4.2 (from pydantic<3,>=1.9.0->anthropic>=0.39->-r requirements.txt (line 26))
#9 30.64 Downloading typing_inspection-0.4.2-py3-none-any.whl.metadata (2.6 kB)
#9 30.69 Collecting parso<0.9.0,>=0.8.6 (from jedi>=0.18.2->ipython<10,>=8.0.0->build123d>=0.7->-r requirements.txt (line 10))
#9 30.72 Downloading parso-0.8.7-py2.py3-none-any.whl.metadata (8.2 kB)
#9 30.77 Collecting ptyprocess>=0.5 (from pexpect>4.6->ipython<10,>=8.0.0->build123d>=0.7->-r requirements.txt (line 10))
#9 30.82 Downloading ptyprocess-0.7.0-py2.py3-none-any.whl.metadata (1.3 kB)
#9 30.88 Collecting executing>=1.2.0 (from stack_data>=0.6.0->ipython<10,>=8.0.0->build123d>=0.7->-r requirements.txt (line 10))
#9 30.92 Downloading executing-2.2.1-py2.py3-none-any.whl.metadata (8.9 kB)
#9 30.98 Collecting asttokens>=2.1.0 (from stack_data>=0.6.0->ipython<10,>=8.0.0->build123d>=0.7->-r requirements.txt (line 10))
#9 31.01 Downloading asttokens-3.0.1-py3-none-any.whl.metadata (4.9 kB)
#9 31.05 Collecting pure-eval (from stack_data>=0.6.0->ipython<10,>=8.0.0->build123d>=0.7->-r requirements.txt (line 10))
#9 31.10 Downloading pure_eval-0.2.3-py3-none-any.whl.metadata (6.3 kB)
#9 31.27 Collecting cffi>=1.1.0 (from cairocffi->cairosvg>=2.7->-r requirements.txt (line 23))
#9 31.30 Downloading cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.6 kB)
#9 31.35 Collecting pycparser (from cffi>=1.1.0->cairocffi->cairosvg>=2.7->-r requirements.txt (line 23))
#9 31.40 Downloading pycparser-3.0-py3-none-any.whl.metadata (8.2 kB)
#9 31.47 Collecting webencodings (from cssselect2->cairosvg>=2.7->-r requirements.txt (line 23))
#9 31.55 Downloading webencodings-0.5.1-py2.py3-none-any.whl.metadata (2.1 kB)
#9 31.66 Collecting mpmath<1.4,>=1.1.0 (from sympy->build123d>=0.7->-r requirements.txt (line 10))
#9 31.73 Downloading mpmath-1.3.0-py3-none-any.whl.metadata (8.6 kB)
#9 31.81 Downloading build123d-0.11.0-py3-none-any.whl (361 kB)
#9 32.05 Downloading numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (16.9 MB)
#9 36.36 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16.9/16.9 MB 4.0 MB/s 0:00:04
#9 36.45 Downloading anytree-2.13.0-py3-none-any.whl (45 kB)
#9 36.50 Downloading cadquery_ocp_novtk-7.9.3.1.1-cp311-cp311-manylinux_2_31_x86_64.whl (67.6 MB)
#9 44.24 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 67.6/67.6 MB 8.8 MB/s 0:00:07
#9 44.30 Downloading cadquery_ocp_proxy-7.9.3.1.1-py3-none-any.whl (3.3 kB)
#9 44.34 Downloading ezdxf-1.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (5.8 MB)
#9 44.85 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5.8/5.8 MB 11.4 MB/s 0:00:00
#9 44.90 Downloading ipython-9.14.1-py3-none-any.whl (627 kB)
#9 44.95 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 627.8/627.8 kB 10.6 MB/s 0:00:00
#9 45.00 Downloading ocp_gordon-0.2.0-py3-none-any.whl (52 kB)
#9 45.05 Downloading ocpsvg-0.6.0-py3-none-any.whl (20 kB)
#9 45.09 Downloading prompt_toolkit-3.0.52-py3-none-any.whl (391 kB)
#9 45.17 Downloading requests-2.34.2-py3-none-any.whl (73 kB)
#9 45.21 Downloading charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (214 kB)
#9 45.27 Downloading idna-3.18-py3-none-any.whl (65 kB)
#9 45.32 Downloading scikit_learn-1.9.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (9.3 MB)
#9 46.13 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 9.3/9.3 MB 11.4 MB/s 0:00:00
#9 46.18 Downloading svgelements-1.9.6-py2.py3-none-any.whl (137 kB)
#9 46.23 Downloading svgpathtools-1.7.2-py2.py3-none-any.whl (68 kB)
#9 46.27 Downloading typing_extensions-4.15.0-py3-none-any.whl (44 kB)
#9 46.31 Downloading urllib3-2.7.0-py3-none-any.whl (131 kB)
#9 46.36 Downloading webcolors-24.8.0-py3-none-any.whl (15 kB)
#9 46.41 Downloading pandas-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (11.3 MB)
#9 47.42 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 11.3/11.3 MB 11.2 MB/s 0:00:01
#9 47.58 Downloading openpyxl-3.1.5-py2.py3-none-any.whl (250 kB)
#9 47.78 Downloading trimesh-4.12.2-py3-none-any.whl (741 kB)
#9 47.87 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 741.0/741.0 kB 9.6 MB/s 0:00:00
#9 47.91 Downloading pyrender-0.1.45-py3-none-any.whl (1.2 MB)
#9 48.01 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.2/1.2 MB 11.5 MB/s 0:00:00
#9 48.05 Downloading pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (7.1 MB)
#9 48.70 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 7.1/7.1 MB 11.0 MB/s 0:00:00
#9 48.74 Downloading cairosvg-2.9.0-py3-none-any.whl (45 kB)
#9 48.78 Downloading anthropic-0.109.2-py3-none-any.whl (923 kB)
#9 48.86 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 923.8/923.8 kB 11.4 MB/s 0:00:00
#9 48.90 Downloading anyio-4.14.0-py3-none-any.whl (123 kB)
#9 48.96 Downloading distro-1.9.0-py3-none-any.whl (20 kB)
#9 48.99 Downloading docstring_parser-0.18.0-py3-none-any.whl (22 kB)
#9 49.03 Downloading httpx-0.28.1-py3-none-any.whl (73 kB)
#9 49.07 Downloading httpcore-1.0.9-py3-none-any.whl (78 kB)
#9 49.11 Downloading jiter-0.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (349 kB)
#9 49.18 Downloading pydantic-2.13.4-py3-none-any.whl (472 kB)
#9 49.26 Downloading pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.1 MB)
#9 49.45 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.1/2.1 MB 11.3 MB/s 0:00:00
#9 49.49 Downloading annotated_types-0.7.0-py3-none-any.whl (13 kB)
#9 49.53 Downloading certifi-2026.6.17-py3-none-any.whl (133 kB)
#9 49.59 Downloading decorator-5.3.1-py3-none-any.whl (10 kB)
#9 49.63 Downloading h11-0.16.0-py3-none-any.whl (37 kB)
#9 49.67 Downloading ipython_pygments_lexers-1.1.1-py3-none-any.whl (8.1 kB)
#9 49.71 Downloading jedi-0.20.0-py2.py3-none-any.whl (4.9 MB)
#9 50.15 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.9/4.9 MB 11.0 MB/s 0:00:00
#9 50.20 Downloading parso-0.8.7-py2.py3-none-any.whl (107 kB)
#9 50.24 Downloading joblib-1.5.3-py3-none-any.whl (309 kB)
#9 50.32 Downloading lib3mf-2.5.0-py3-none-manylinux2014_x86_64.whl (1.6 MB)
#9 50.47 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.6/1.6 MB 10.9 MB/s 0:00:00
#9 50.51 Downloading matplotlib_inline-0.2.2-py3-none-any.whl (9.5 kB)
#9 50.54 Downloading narwhals-2.22.1-py3-none-any.whl (454 kB)
#9 50.63 Downloading pexpect-4.9.0-py2.py3-none-any.whl (63 kB)
#9 50.67 Downloading psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl (155 kB)
#9 50.73 Downloading ptyprocess-0.7.0-py2.py3-none-any.whl (13 kB)
#9 50.77 Downloading pyglet-2.1.14-py3-none-any.whl (1.0 MB)
#9 50.86 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.0/1.0 MB 11.3 MB/s 0:00:00
#9 50.91 Downloading pygments-2.20.0-py3-none-any.whl (1.2 MB)
#9 51.03 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.2/1.2 MB 10.3 MB/s 0:00:00
#9 51.08 Downloading pyparsing-3.3.2-py3-none-any.whl (122 kB)
#9 51.12 Downloading python_dateutil-2.9.0.post0-py2.py3-none-any.whl (229 kB)
#9 51.18 Downloading scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (35.3 MB)
#9 54.59 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 35.3/35.3 MB 10.4 MB/s 0:00:03
#9 54.63 Downloading six-1.17.0-py2.py3-none-any.whl (11 kB)
#9 54.67 Downloading stack_data-0.6.3-py3-none-any.whl (24 kB)
#9 54.71 Downloading asttokens-3.0.1-py3-none-any.whl (27 kB)
#9 54.75 Downloading executing-2.2.1-py2.py3-none-any.whl (28 kB)
#9 54.79 Downloading threadpoolctl-3.6.0-py3-none-any.whl (18 kB)
#9 54.83 Downloading traitlets-5.15.1-py3-none-any.whl (85 kB)
#9 54.88 Downloading typing_inspection-0.4.2-py3-none-any.whl (14 kB)
#9 54.92 Downloading cairocffi-1.7.1-py3-none-any.whl (75 kB)
#9 54.95 Downloading cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (215 kB)
#9 55.01 Downloading cssselect2-0.9.0-py3-none-any.whl (15 kB)
#9 55.05 Downloading defusedxml-0.7.1-py2.py3-none-any.whl (25 kB)
#9 55.09 Downloading et_xmlfile-2.0.0-py3-none-any.whl (18 kB)
#9 55.13 Downloading fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (5.1 MB)
#9 55.57 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5.1/5.1 MB 11.6 MB/s 0:00:00
#9 55.61 Downloading freetype_py-2.5.1-py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (1.0 MB)
#9 55.72 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.0/1.0 MB 8.6 MB/s 0:00:00
#9 55.75 Downloading imageio-2.37.3-py3-none-any.whl (317 kB)
#9 55.82 Downloading networkx-3.6.1-py3-none-any.whl (2.1 MB)
#9 56.00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.1/2.1 MB 11.6 MB/s 0:00:00
#9 56.04 Downloading pure_eval-0.2.3-py3-none-any.whl (11 kB)
#9 56.08 Downloading pycparser-3.0-py3-none-any.whl (48 kB)
#9 56.12 Downloading sniffio-1.3.1-py3-none-any.whl (10 kB)
#9 56.16 Downloading svgwrite-1.4.3-py3-none-any.whl (67 kB)
#9 56.20 Downloading sympy-1.14.0-py3-none-any.whl (6.3 MB)
#9 56.75 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.3/6.3 MB 11.5 MB/s 0:00:00
#9 56.80 Downloading mpmath-1.3.0-py3-none-any.whl (536 kB)
#9 56.84 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 536.2/536.2 kB 12.0 MB/s 0:00:00
#9 56.88 Downloading tinycss2-1.5.1-py3-none-any.whl (28 kB)
#9 56.92 Downloading webencodings-0.5.1-py2.py3-none-any.whl (11 kB)
#9 56.96 Downloading trianglesolver-1.2-py3-none-any.whl (5.4 kB)
#9 57.00 Downloading wcwidth-0.8.1-py3-none-any.whl (323 kB)
#9 57.63 Building wheels for collected packages: PyOpenGL
#9 57.63 Building wheel for PyOpenGL (pyproject.toml): started
#9 58.92 Building wheel for PyOpenGL (pyproject.toml): finished with status 'done'
#9 58.92 Created wheel for PyOpenGL: filename=pyopengl-3.1.0-py3-none-any.whl size=1745238 sha256=4de35aa9a3fd450fc041959cba7abb6b5d4864aa3332fcc3c1acceea007b6ea8
#9 58.92 Stored in directory: /tmp/pip-ephem-wheel-cache-mfbjsrx3/wheels/2f/37/f5/f88cd3dddf75bc3ce608e44bf8a79078c408bf1f351a50818e
#9 58.93 Successfully built PyOpenGL
#9 59.12 Installing collected packages: webencodings, trianglesolver, svgelements, PyOpenGL, pure-eval, ptyprocess, mpmath, lib3mf, webcolors, wcwidth, urllib3, typing_extensions, traitlets, tinycss2, threadpoolctl, sympy, svgwrite, sniffio, six, pyparsing, pygments, pyglet, pycparser, psutil, Pillow, pexpect, parso, numpy, networkx, narwhals, joblib, jiter, idna, h11, freetype-py, fonttools, executing, et-xmlfile, docstring-parser, distro, defusedxml, decorator, charset_normalizer, certifi, cadquery-ocp-proxy, asttokens, anytree, annotated-types, typing-inspection, trimesh, stack_data, scipy, requests, python-dateutil, pydantic-core, prompt_toolkit, openpyxl, ocpsvg, matplotlib-inline, jedi, ipython-pygments-lexers, imageio, httpcore, ezdxf, cssselect2, cffi, cadquery-ocp-novtk, anyio, svgpathtools, scikit-learn, pyrender, pydantic, pandas, ocp_gordon, ipython, httpx, cairocffi, cairosvg, build123d, anthropic
#9 79.09
#9 79.10 Successfully installed Pillow-12.2.0 PyOpenGL-3.1.0 annotated-types-0.7.0 anthropic-0.109.2 anyio-4.14.0 anytree-2.13.0 asttokens-3.0.1 build123d-0.11.0 cadquery-ocp-novtk-7.9.3.1.1 cadquery-ocp-proxy-7.9.3.1.1 cairocffi-1.7.1 cairosvg-2.9.0 certifi-2026.6.17 cffi-2.0.0 charset_normalizer-3.4.7 cssselect2-0.9.0 decorator-5.3.1 defusedxml-0.7.1 distro-1.9.0 docstring-parser-0.18.0 et-xmlfile-2.0.0 executing-2.2.1 ezdxf-1.4.4 fonttools-4.63.0 freetype-py-2.5.1 h11-0.16.0 httpcore-1.0.9 httpx-0.28.1 idna-3.18 imageio-2.37.3 ipython-9.14.1 ipython-pygments-lexers-1.1.1 jedi-0.20.0 jiter-0.15.0 joblib-1.5.3 lib3mf-2.5.0 matplotlib-inline-0.2.2 mpmath-1.3.0 narwhals-2.22.1 networkx-3.6.1 numpy-2.4.6 ocp_gordon-0.2.0 ocpsvg-0.6.0 openpyxl-3.1.5 pandas-3.0.3 parso-0.8.7 pexpect-4.9.0 prompt_toolkit-3.0.52 psutil-7.2.2 ptyprocess-0.7.0 pure-eval-0.2.3 pycparser-3.0 pydantic-2.13.4 pydantic-core-2.46.4 pyglet-2.1.14 pygments-2.20.0 pyparsing-3.3.2 pyrender-0.1.45 python-dateutil-2.9.0.post0 requests-2.34.2 scikit-learn-1.9.0 scipy-1.17.1 six-1.17.0 sniffio-1.3.1 stack_data-0.6.3 svgelements-1.9.6 svgpathtools-1.7.2 svgwrite-1.4.3 sympy-1.14.0 threadpoolctl-3.6.0 tinycss2-1.5.1 traitlets-5.15.1 trianglesolver-1.2 trimesh-4.12.2 typing-inspection-0.4.2 typing_extensions-4.15.0 urllib3-2.7.0 wcwidth-0.8.1 webcolors-24.8.0 webencodings-0.5.1
#9 79.10 WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager, possibly rendering your system unusable. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv. Use the --root-user-action option if you know what you are doing and want to suppress this warning.
#9 80.37 Collecting PyOpenGL==3.1.7
#9 80.55 Downloading PyOpenGL-3.1.7-py3-none-any.whl.metadata (3.2 kB)
#9 80.58 Downloading PyOpenGL-3.1.7-py3-none-any.whl (2.4 MB)
#9 80.91 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.4/2.4 MB 9.7 MB/s 0:00:00
#9 80.97 Installing collected packages: PyOpenGL
#9 80.97 Attempting uninstall: PyOpenGL
#9 80.97 Found existing installation: PyOpenGL 3.1.0
#9 81.07 Uninstalling PyOpenGL-3.1.0:
#9 81.09 Successfully uninstalled PyOpenGL-3.1.0
#9 81.95 Successfully installed PyOpenGL-3.1.7
#9 81.95 WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager, possibly rendering your system unusable. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv. Use the --root-user-action option if you know what you are doing and want to suppress this warning.
#9 DONE 82.3s
#10 [6/6] COPY skill.src ./skill.src
#10 DONE 0.2s
#11 exporting to image
#11 exporting layers
#11 exporting layers 18.6s done
#11 exporting manifest sha256:e3162acb62db45ed994ae29e83a1bfaa07d54ab2c9f70a42034978865f0e5bfd done
#11 exporting config sha256:eccea0b0f80670e258e0b4119d7cf39046b3415cdae7029a2c4f948bb9aa56f1 done
#11 exporting attestation manifest sha256:51cc65728f76ec08f88fb725987649fbcfffe036c0965aec68b6652cd11dd7e0 done
#11 exporting manifest list sha256:8017361c440d502568dfa60547eee8769800ae2dbddf682f0f909ee2ed5091a2 done
#11 naming to docker.io/library/step-parser:dev done
#11 DONE 18.7s
View build details: docker-desktop://dashboard/build/desktop-linux/desktop-linux/b8ggdw1yff1bk16im8g9of8bf
Built step-parser:dev.
Next:
./smoke-test.sh # validate the kernel + render + diagram
docker run --rm --entrypoint pip step-parser:dev freeze > requirements.lock.txt
Push to the Unraid registry once green:
docker tag step-parser:dev registry.alwisp.com/jason/step-parser:latest
docker push registry.alwisp.com/jason/step-parser:latest
BUILD_EXIT=0
+59
View File
@@ -0,0 +1,59 @@
Building step-parser:dev for linux/amd64 ...
#0 building with "desktop-linux" instance using docker driver
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 2.40kB done
#1 DONE 0.0s
#2 [internal] load metadata for docker.io/library/python:3.11-slim-bookworm
#2 DONE 0.5s
#3 [internal] load .dockerignore
#3 transferring context: 392B done
#3 DONE 0.0s
#4 [internal] load build context
#4 transferring context: 47.02kB done
#4 DONE 0.0s
#5 [1/6] FROM docker.io/library/python:3.11-slim-bookworm@sha256:e2d3af735aff6eeee600b1933bedd99da6645fedf572cc12ef4cc1331f2ceebe
#5 resolve docker.io/library/python:3.11-slim-bookworm@sha256:e2d3af735aff6eeee600b1933bedd99da6645fedf572cc12ef4cc1331f2ceebe 0.0s done
#5 DONE 0.0s
#6 [3/6] WORKDIR /app
#6 CACHED
#7 [2/6] RUN apt-get update && apt-get install -y --no-install-recommends libgl1 libglu1-mesa libosmesa6 libxrender1 libxext6 libsm6 libx11-6 libxi6 libfontconfig1 libglib2.0-0 libgomp1 libcairo2 libpango-1.0-0 libpangocairo-1.0-0 libgdk-pixbuf-2.0-0 libffi8 ca-certificates && rm -rf /var/lib/apt/lists/*
#7 CACHED
#8 [4/6] COPY requirements.txt .
#8 CACHED
#9 [5/6] RUN pip install --upgrade pip && pip install -r requirements.txt && pip install --no-deps --upgrade "PyOpenGL==3.1.7"
#9 CACHED
#10 [6/6] COPY skill.src ./skill.src
#10 DONE 0.1s
#11 exporting to image
#11 exporting layers
#11 exporting layers 1.2s done
#11 exporting manifest sha256:cba7070f2b3cc5e1ba51390ccc4322831ed554a26276888351bc05318e81405b
#11 exporting manifest sha256:cba7070f2b3cc5e1ba51390ccc4322831ed554a26276888351bc05318e81405b done
#11 exporting config sha256:9ce25a4f4e87289d9203f1c1d0f455918bbb3f193382a6e92ea4045724cff5b1 done
#11 exporting attestation manifest sha256:dba17b13ea58c9f108c63444ddf4d6b38f9e584087e84bfe19264282b86e3380 done
#11 exporting manifest list sha256:234461c1c298541efaeca2bf5592a339fb02652ca309d969f3fa758857056b83 done
#11 naming to docker.io/library/step-parser:dev done
#11 DONE 1.3s
View build details: docker-desktop://dashboard/build/desktop-linux/desktop-linux/o16801doyym83zu3xu9tgbq84
Built step-parser:dev.
Next:
./smoke-test.sh # validate the kernel + render + diagram
docker run --rm --entrypoint pip step-parser:dev freeze > requirements.lock.txt
Push to the Unraid registry once green:
docker tag step-parser:dev registry.alwisp.com/jason/step-parser:latest
docker push registry.alwisp.com/jason/step-parser:latest
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

@@ -0,0 +1,77 @@
<svg xmlns="http://www.w3.org/2000/svg" width="420mm" height="297mm" viewBox="0 0 420 297" font-family="Arial, Helvetica, sans-serif">
<defs>
<marker id="arrow" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto">
<path d="M0,0 L6,3 L0,6 Z" fill="#1565C0"/>
</marker>
<marker id="arrowR" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto-start-reverse">
<path d="M0,0 L6,3 L0,6 Z" fill="#1565C0"/>
</marker>
</defs>
<rect width="420" height="297" fill="white" stroke="none"/>
<rect x="7.5" y="7.5" width="412.5" height="289.5" fill="none" stroke="#1a1a1a" stroke-width="0.5"/>
<g id="view-front">
<rect x="328.75" y="35.00" width="148.20" height="24.56" fill="none" stroke="#1a1a1a" stroke-width="0.7"/>
<text x="328.75" y="32.00" font-size="3.0" fill="#1a1a1a" font-weight="bold">FRONT</text>
<line x1="328.75" y1="59.56" x2="328.75" y2="77.56" stroke="#1565C0" stroke-width="0.3"/>
<line x1="476.95" y1="59.56" x2="476.95" y2="77.56" stroke="#1565C0" stroke-width="0.3"/>
<line x1="328.75" y1="77.56" x2="476.95" y2="77.56" stroke="#1565C0" stroke-width="0.4" marker-start="url(#arrowR)" marker-end="url(#arrow)"/>
<text x="402.85" y="76.06" font-size="3.5" fill="#1565C0" text-anchor="middle">248.6</text>
<text x="402.85" y="80.56" font-size="2.5" fill="#1565C0" font-style="italic" text-anchor="middle">(9.787")</text>
<line x1="328.75" y1="35.00" x2="310.75" y2="35.00" stroke="#1565C0" stroke-width="0.3"/>
<line x1="328.75" y1="59.56" x2="310.75" y2="59.56" stroke="#1565C0" stroke-width="0.3"/>
<line x1="310.75" y1="35.00" x2="310.75" y2="59.56" stroke="#1565C0" stroke-width="0.4" marker-start="url(#arrowR)" marker-end="url(#arrow)"/>
<text x="309.25" y="47.28" font-size="3.5" fill="#1565C0" text-anchor="middle" transform="rotate(-90 309.25 47.28)">41.2</text>
<text x="313.25" y="47.28" font-size="2.5" fill="#1565C0" font-style="italic" text-anchor="middle" transform="rotate(-90 313.25 47.28)">(1.622")</text>
</g>
<g id="view-bottom">
<rect x="328.75" y="99.56" width="148.20" height="273.75" fill="none" stroke="#1a1a1a" stroke-width="0.7"/>
<text x="328.75" y="96.56" font-size="3.0" fill="#1a1a1a" font-weight="bold">BOTTOM</text>
<line x1="328.75" y1="373.31" x2="328.75" y2="391.31" stroke="#1565C0" stroke-width="0.3"/>
<line x1="476.95" y1="373.31" x2="476.95" y2="391.31" stroke="#1565C0" stroke-width="0.3"/>
<line x1="328.75" y1="391.31" x2="476.95" y2="391.31" stroke="#1565C0" stroke-width="0.4" marker-start="url(#arrowR)" marker-end="url(#arrow)"/>
<text x="402.85" y="389.81" font-size="3.5" fill="#1565C0" text-anchor="middle">248.6</text>
<text x="402.85" y="394.31" font-size="2.5" fill="#1565C0" font-style="italic" text-anchor="middle">(9.787")</text>
</g>
<g id="view-left">
<rect x="35.00" y="35.00" width="273.75" height="24.56" fill="none" stroke="#1a1a1a" stroke-width="0.7"/>
<text x="35.00" y="32.00" font-size="3.0" fill="#1a1a1a" font-weight="bold">SIDE</text>
<line x1="35.00" y1="59.56" x2="35.00" y2="77.56" stroke="#1565C0" stroke-width="0.3"/>
<line x1="308.75" y1="59.56" x2="308.75" y2="77.56" stroke="#1565C0" stroke-width="0.3"/>
<line x1="35.00" y1="77.56" x2="308.75" y2="77.56" stroke="#1565C0" stroke-width="0.4" marker-start="url(#arrowR)" marker-end="url(#arrow)"/>
<text x="171.87" y="76.06" font-size="3.5" fill="#1565C0" text-anchor="middle">459.2</text>
<text x="171.87" y="80.56" font-size="2.5" fill="#1565C0" font-style="italic" text-anchor="middle">(18.079")</text>
<line x1="308.75" y1="35.00" x2="326.75" y2="35.00" stroke="#1565C0" stroke-width="0.3"/>
<line x1="308.75" y1="59.56" x2="326.75" y2="59.56" stroke="#1565C0" stroke-width="0.3"/>
<line x1="326.75" y1="35.00" x2="326.75" y2="59.56" stroke="#1565C0" stroke-width="0.4" marker-start="url(#arrowR)" marker-end="url(#arrow)"/>
<text x="325.25" y="47.28" font-size="3.5" fill="#1565C0" text-anchor="middle" transform="rotate(-90 325.25 47.28)">41.2</text>
<text x="329.25" y="47.28" font-size="2.5" fill="#1565C0" font-style="italic" text-anchor="middle" transform="rotate(-90 329.25 47.28)">(1.622")</text>
</g>
<g id="view-rear">
<rect x="790.69" y="35.00" width="148.20" height="24.56" fill="none" stroke="#1a1a1a" stroke-width="0.7"/>
<text x="790.69" y="32.00" font-size="3.0" fill="#1a1a1a" font-weight="bold">REAR</text>
<line x1="790.69" y1="59.56" x2="790.69" y2="77.56" stroke="#1565C0" stroke-width="0.3"/>
<line x1="938.89" y1="59.56" x2="938.89" y2="77.56" stroke="#1565C0" stroke-width="0.3"/>
<line x1="790.69" y1="77.56" x2="938.89" y2="77.56" stroke="#1565C0" stroke-width="0.4" marker-start="url(#arrowR)" marker-end="url(#arrow)"/>
<text x="864.79" y="76.06" font-size="3.5" fill="#1565C0" text-anchor="middle">248.6</text>
<text x="864.79" y="80.56" font-size="2.5" fill="#1565C0" font-style="italic" text-anchor="middle">(9.787")</text>
</g>
<g id="view-isometric_front">
<polygon points="394.41,227.73 483.33,227.73 483.33,212.99 394.41,212.99" fill="#f0f0f0" stroke="#1a1a1a" stroke-width="0.6"/>
<polygon points="394.41,212.99 483.33,212.99 578.16,158.24 489.24,158.24" fill="#e0e0e0" stroke="#1a1a1a" stroke-width="0.6"/>
<polygon points="483.33,227.73 483.33,212.99 578.16,158.24" fill="#d0d0d0" stroke="#1a1a1a" stroke-width="0.6"/>
</g>
<rect x="15.0" y="267.5" width="390.0" height="22.0" fill="#f5f5f5" stroke="#1a1a1a" stroke-width="0.4"/>
<text x="18.0" y="274.5" font-size="5.0" fill="#1a1a1a" font-weight="bold">MR16s Gen1_EN — MR16s Gen1_EN</text>
<text x="18.0" y="280.5" font-size="3.0" fill="#1a1a1a"><tspan font-weight="bold">Units: </tspan>Dimensions in mm (in)</text>
<text x="63.0" y="280.5" font-size="3.0" fill="#1a1a1a"><tspan font-weight="bold">Scale: </tspan>NTS</text>
<text x="108.0" y="280.5" font-size="3.0" fill="#1a1a1a"><tspan font-weight="bold">Rev: </tspan>A</text>
<text x="153.0" y="280.5" font-size="3.0" fill="#1a1a1a"><tspan font-weight="bold">Date: </tspan>2026-06-17</text>
<text x="198.0" y="280.5" font-size="3.0" fill="#1a1a1a"><tspan font-weight="bold">By: </tspan></text>
<text x="402.0" y="280.5" font-size="3.5" fill="#1a1a1a" text-anchor="end" font-weight="bold">MPMedia</text>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

+102
View File
@@ -0,0 +1,102 @@
{
"schema_version": "1.0",
"generated_at": "2026-06-17T20:38:16.305002+00:00",
"source_path": "/data/MR16s Gen1_EN.step",
"model_name": "MR16s Gen1_EN",
"mode": "enclosure_only",
"style": "rendered",
"layout_mode": "single_sheet",
"engine_used": "build123d",
"fallback_invoked": false,
"units": {
"primary": "mm",
"secondary": "in"
},
"overall_width": 248.6,
"overall_height": 41.2,
"overall_depth": 459.2,
"bounding_box": {
"x_min": -135.8,
"x_max": 112.8,
"y_min": -215.75,
"y_max": 243.45,
"z_min": 366.0,
"z_max": 407.2
},
"active_area": null,
"mounting_dimensions": null,
"mounting_variants": [],
"selected_parts": [
"15.6\" Rear Cover",
"Remote Control + Light Sensor Board",
"J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step",
"15.6\" Tempered Glass 3+3 Laminated",
"M3x6 Countersunk Screw",
"DZ-LP0632 Light Sensor Control Board",
"J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc.step",
"3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07",
"TOSN-DY398P-EMC Sub-board",
"TOSN-AD120P12V10A-120W",
"2P Phoenix Terminal",
"HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step",
"G156HAN02.0--20221031_G156HAN02.0_PSpec_DBEST",
"3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07.step",
"Small Glass Lens",
"Board~a9jc.step",
"J7~XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step",
"15.6\" Terminal Board",
"XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step",
"Lower Aluminum Plate",
"Upper Aluminum Plate",
"BSCZ-TX3361",
"BNC-1~BNC-TH_BNC-50KWYE~a9jc.step",
"15.6\" Mounting Plate",
"15.6\" Light Sensor Bracket",
"15.6\" High-Speed Rail Display Assembly Drawing",
"15.6\" Aluminum Frame",
"8Ω 5W Speaker (Model 3070)"
],
"excluded_parts": [],
"mapping_file_used": null,
"datablock": {
"model_number": "MR16s Gen1_EN",
"display_name": "MR16s Gen1_EN",
"revision": "A",
"drawing_date": "2026-06-17",
"drawn_by": "",
"company": "MPMedia",
"units_note": "Dimensions in mm (in)",
"scale": "NTS",
"custom_fields": {}
},
"layout": {
"views_included": [
"front",
"isometric_front",
"left",
"rear",
"bottom"
],
"sheet_size": "A3_landscape",
"scale_ratio": "NTS",
"dimension_style": "baseline",
"iso_style": "shaded_render"
},
"outputs": {
"diagram_png": "/data/MR16s Gen1_EN__external-diagram.png",
"diagram_pdf": null,
"diagram_svg": "/data/MR16s Gen1_EN__external-diagram.svg",
"iso_png": null,
"front_png": "front",
"side_png": "left",
"rear_png": null,
"meta_json": "/data/MR16s Gen1_EN__meta.json",
"variant_outputs": null
},
"warnings": [
"Active area (screen aperture) not detected — omitted from diagram."
],
"notes": [
"SVG written: MR16s Gen1_EN__external-diagram.svg"
]
}
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

@@ -0,0 +1,77 @@
<svg xmlns="http://www.w3.org/2000/svg" width="420mm" height="297mm" viewBox="0 0 420 297" font-family="Arial, Helvetica, sans-serif">
<defs>
<marker id="arrow" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto">
<path d="M0,0 L6,3 L0,6 Z" fill="#1565C0"/>
</marker>
<marker id="arrowR" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto-start-reverse">
<path d="M0,0 L6,3 L0,6 Z" fill="#1565C0"/>
</marker>
</defs>
<rect width="420" height="297" fill="white" stroke="none"/>
<rect x="7.5" y="7.5" width="412.5" height="289.5" fill="none" stroke="#1a1a1a" stroke-width="0.5"/>
<g id="view-front">
<rect x="323.12" y="35.00" width="148.20" height="15.37" fill="none" stroke="#1a1a1a" stroke-width="0.7"/>
<text x="323.12" y="32.00" font-size="3.0" fill="#1a1a1a" font-weight="bold">FRONT</text>
<line x1="323.12" y1="50.37" x2="323.12" y2="68.37" stroke="#1565C0" stroke-width="0.3"/>
<line x1="471.32" y1="50.37" x2="471.32" y2="68.37" stroke="#1565C0" stroke-width="0.3"/>
<line x1="323.12" y1="68.37" x2="471.32" y2="68.37" stroke="#1565C0" stroke-width="0.4" marker-start="url(#arrowR)" marker-end="url(#arrow)"/>
<text x="397.22" y="66.87" font-size="3.5" fill="#1565C0" text-anchor="middle">397.2</text>
<text x="397.22" y="71.37" font-size="2.5" fill="#1565C0" font-style="italic" text-anchor="middle">(15.638")</text>
<line x1="323.12" y1="35.00" x2="305.12" y2="35.00" stroke="#1565C0" stroke-width="0.3"/>
<line x1="323.12" y1="50.37" x2="305.12" y2="50.37" stroke="#1565C0" stroke-width="0.3"/>
<line x1="305.12" y1="35.00" x2="305.12" y2="50.37" stroke="#1565C0" stroke-width="0.4" marker-start="url(#arrowR)" marker-end="url(#arrow)"/>
<text x="303.62" y="42.69" font-size="3.5" fill="#1565C0" text-anchor="middle" transform="rotate(-90 303.62 42.69)">41.2</text>
<text x="307.62" y="42.69" font-size="2.5" fill="#1565C0" font-style="italic" text-anchor="middle" transform="rotate(-90 307.62 42.69)">(1.622")</text>
</g>
<g id="view-bottom">
<rect x="323.12" y="90.37" width="148.20" height="268.12" fill="none" stroke="#1a1a1a" stroke-width="0.7"/>
<text x="323.12" y="87.37" font-size="3.0" fill="#1a1a1a" font-weight="bold">BOTTOM</text>
<line x1="323.12" y1="358.49" x2="323.12" y2="376.49" stroke="#1565C0" stroke-width="0.3"/>
<line x1="471.32" y1="358.49" x2="471.32" y2="376.49" stroke="#1565C0" stroke-width="0.3"/>
<line x1="323.12" y1="376.49" x2="471.32" y2="376.49" stroke="#1565C0" stroke-width="0.4" marker-start="url(#arrowR)" marker-end="url(#arrow)"/>
<text x="397.22" y="374.99" font-size="3.5" fill="#1565C0" text-anchor="middle">397.2</text>
<text x="397.22" y="379.49" font-size="2.5" fill="#1565C0" font-style="italic" text-anchor="middle">(15.638")</text>
</g>
<g id="view-left">
<rect x="35.00" y="35.00" width="268.12" height="15.37" fill="none" stroke="#1a1a1a" stroke-width="0.7"/>
<text x="35.00" y="32.00" font-size="3.0" fill="#1a1a1a" font-weight="bold">SIDE</text>
<line x1="35.00" y1="50.37" x2="35.00" y2="68.37" stroke="#1565C0" stroke-width="0.3"/>
<line x1="303.12" y1="50.37" x2="303.12" y2="68.37" stroke="#1565C0" stroke-width="0.3"/>
<line x1="35.00" y1="68.37" x2="303.12" y2="68.37" stroke="#1565C0" stroke-width="0.4" marker-start="url(#arrowR)" marker-end="url(#arrow)"/>
<text x="169.06" y="66.87" font-size="3.5" fill="#1565C0" text-anchor="middle">718.6</text>
<text x="169.06" y="71.37" font-size="2.5" fill="#1565C0" font-style="italic" text-anchor="middle">(28.291")</text>
<line x1="303.12" y1="35.00" x2="321.12" y2="35.00" stroke="#1565C0" stroke-width="0.3"/>
<line x1="303.12" y1="50.37" x2="321.12" y2="50.37" stroke="#1565C0" stroke-width="0.3"/>
<line x1="321.12" y1="35.00" x2="321.12" y2="50.37" stroke="#1565C0" stroke-width="0.4" marker-start="url(#arrowR)" marker-end="url(#arrow)"/>
<text x="319.62" y="42.69" font-size="3.5" fill="#1565C0" text-anchor="middle" transform="rotate(-90 319.62 42.69)">41.2</text>
<text x="323.62" y="42.69" font-size="2.5" fill="#1565C0" font-style="italic" text-anchor="middle" transform="rotate(-90 323.62 42.69)">(1.622")</text>
</g>
<g id="view-rear">
<rect x="779.44" y="35.00" width="148.20" height="15.37" fill="none" stroke="#1a1a1a" stroke-width="0.7"/>
<text x="779.44" y="32.00" font-size="3.0" fill="#1a1a1a" font-weight="bold">REAR</text>
<line x1="779.44" y1="50.37" x2="779.44" y2="68.37" stroke="#1565C0" stroke-width="0.3"/>
<line x1="927.64" y1="50.37" x2="927.64" y2="68.37" stroke="#1565C0" stroke-width="0.3"/>
<line x1="779.44" y1="68.37" x2="927.64" y2="68.37" stroke="#1565C0" stroke-width="0.4" marker-start="url(#arrowR)" marker-end="url(#arrow)"/>
<text x="853.54" y="66.87" font-size="3.5" fill="#1565C0" text-anchor="middle">397.2</text>
<text x="853.54" y="71.37" font-size="2.5" fill="#1565C0" font-style="italic" text-anchor="middle">(15.638")</text>
</g>
<g id="view-isometric_front">
<polygon points="406.38,234.51 495.30,234.51 495.30,225.29 406.38,225.29" fill="#f0f0f0" stroke="#1a1a1a" stroke-width="0.6"/>
<polygon points="406.38,225.29 495.30,225.29 588.18,171.67 499.26,171.67" fill="#e0e0e0" stroke="#1a1a1a" stroke-width="0.6"/>
<polygon points="495.30,234.51 495.30,225.29 588.18,171.67" fill="#d0d0d0" stroke="#1a1a1a" stroke-width="0.6"/>
</g>
<rect x="15.0" y="267.5" width="390.0" height="22.0" fill="#f5f5f5" stroke="#1a1a1a" stroke-width="0.4"/>
<text x="18.0" y="274.5" font-size="5.0" fill="#1a1a1a" font-weight="bold">MR27s Gen1_EN — MR27s Gen1_EN</text>
<text x="18.0" y="280.5" font-size="3.0" fill="#1a1a1a"><tspan font-weight="bold">Units: </tspan>Dimensions in mm (in)</text>
<text x="63.0" y="280.5" font-size="3.0" fill="#1a1a1a"><tspan font-weight="bold">Scale: </tspan>NTS</text>
<text x="108.0" y="280.5" font-size="3.0" fill="#1a1a1a"><tspan font-weight="bold">Rev: </tspan>A</text>
<text x="153.0" y="280.5" font-size="3.0" fill="#1a1a1a"><tspan font-weight="bold">Date: </tspan>2026-06-17</text>
<text x="198.0" y="280.5" font-size="3.0" fill="#1a1a1a"><tspan font-weight="bold">By: </tspan></text>
<text x="402.0" y="280.5" font-size="3.5" fill="#1a1a1a" text-anchor="end" font-weight="bold">MPMedia</text>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

+105
View File
@@ -0,0 +1,105 @@
{
"schema_version": "1.0",
"generated_at": "2026-06-17T20:41:52.027768+00:00",
"source_path": "/data/MR27s Gen1_EN.step",
"model_name": "MR27s Gen1_EN",
"mode": "enclosure_only",
"style": "rendered",
"layout_mode": "single_sheet",
"engine_used": "build123d",
"fallback_invoked": false,
"units": {
"primary": "mm",
"secondary": "in"
},
"overall_width": 397.2,
"overall_height": 41.2,
"overall_depth": 718.6,
"bounding_box": {
"x_min": -144.35,
"x_max": 252.85,
"y_min": -293.94,
"y_max": 424.66,
"z_min": 637.4,
"z_max": 678.6
},
"active_area": null,
"mounting_dimensions": null,
"mounting_variants": [],
"selected_parts": [
"TOSN-DY398P-EMC Small PCB Board",
"BNC-1~BNC-TH_BNC-50KWYE~a9jc.step",
"Light Sensor Bracket",
"2P Phoenix Connector Terminal",
"3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07.step",
"J7~XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step",
"3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07",
"DZ-LP6608 REV1.0",
"P270HVN03.0",
"Board~a9jc.step",
"BSCZ-TX3361",
"J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc.step",
"27-inch High-speed Rail Display Screen Assembly Drawing",
"HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step",
"8Ω 5W Speaker (Model 3070)",
"27-inch Screen Clamp 1",
"27-inch Upper Aluminum Panel",
"Small Glass Mirror Lens",
"Tempered Glass 3+3 Laminated",
"27-inch Screen Clamp 2",
"Remote Control + Light Sensor Board",
"Screen Clamp",
"27-inch Lower Aluminum Panel",
"Rear Cover",
"Mounting Plate",
"M3x6 Countersunk Screw",
"J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step",
"Terminal Board",
"27-inch Aluminum Frame",
"XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step",
"TOSN-AD120P12V10A-120W"
],
"excluded_parts": [],
"mapping_file_used": null,
"datablock": {
"model_number": "MR27s Gen1_EN",
"display_name": "MR27s Gen1_EN",
"revision": "A",
"drawing_date": "2026-06-17",
"drawn_by": "",
"company": "MPMedia",
"units_note": "Dimensions in mm (in)",
"scale": "NTS",
"custom_fields": {}
},
"layout": {
"views_included": [
"front",
"isometric_front",
"left",
"rear",
"bottom"
],
"sheet_size": "A3_landscape",
"scale_ratio": "NTS",
"dimension_style": "baseline",
"iso_style": "shaded_render"
},
"outputs": {
"diagram_png": "/data/MR27s Gen1_EN__external-diagram.png",
"diagram_pdf": null,
"diagram_svg": "/data/MR27s Gen1_EN__external-diagram.svg",
"iso_png": null,
"front_png": "front",
"side_png": "left",
"rear_png": null,
"meta_json": "/data/MR27s Gen1_EN__meta.json",
"variant_outputs": null
},
"warnings": [
"Active area (screen aperture) not detected — omitted from diagram."
],
"notes": [
"SVG written: MR27s Gen1_EN__external-diagram.svg"
]
}
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

@@ -0,0 +1,77 @@
<svg xmlns="http://www.w3.org/2000/svg" width="420mm" height="297mm" viewBox="0 0 420 297" font-family="Arial, Helvetica, sans-serif">
<defs>
<marker id="arrow" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto">
<path d="M0,0 L6,3 L0,6 Z" fill="#1565C0"/>
</marker>
<marker id="arrowR" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto-start-reverse">
<path d="M0,0 L6,3 L0,6 Z" fill="#1565C0"/>
</marker>
</defs>
<rect width="420" height="297" fill="white" stroke="none"/>
<rect x="7.5" y="7.5" width="412.5" height="289.5" fill="none" stroke="#1a1a1a" stroke-width="0.5"/>
<g id="view-front">
<rect x="89.45" y="35.00" width="148.20" height="9.06" fill="none" stroke="#1a1a1a" stroke-width="0.7"/>
<text x="89.45" y="32.00" font-size="3.0" fill="#1a1a1a" font-weight="bold">FRONT</text>
<line x1="89.45" y1="44.06" x2="89.45" y2="62.06" stroke="#1565C0" stroke-width="0.3"/>
<line x1="237.65" y1="44.06" x2="237.65" y2="62.06" stroke="#1565C0" stroke-width="0.3"/>
<line x1="89.45" y1="62.06" x2="237.65" y2="62.06" stroke="#1565C0" stroke-width="0.4" marker-start="url(#arrowR)" marker-end="url(#arrow)"/>
<text x="163.55" y="60.56" font-size="3.5" fill="#1565C0" text-anchor="middle">826.0</text>
<text x="163.55" y="65.06" font-size="2.5" fill="#1565C0" font-style="italic" text-anchor="middle">(32.520")</text>
<line x1="89.45" y1="35.00" x2="71.45" y2="35.00" stroke="#1565C0" stroke-width="0.3"/>
<line x1="89.45" y1="44.06" x2="71.45" y2="44.06" stroke="#1565C0" stroke-width="0.3"/>
<line x1="71.45" y1="35.00" x2="71.45" y2="44.06" stroke="#1565C0" stroke-width="0.4" marker-start="url(#arrowR)" marker-end="url(#arrow)"/>
<text x="69.95" y="39.53" font-size="3.5" fill="#1565C0" text-anchor="middle" transform="rotate(-90 69.95 39.53)">50.5</text>
<text x="73.95" y="39.53" font-size="2.5" fill="#1565C0" font-style="italic" text-anchor="middle" transform="rotate(-90 73.95 39.53)">(1.988")</text>
</g>
<g id="view-bottom">
<rect x="89.45" y="84.06" width="148.20" height="34.45" fill="none" stroke="#1a1a1a" stroke-width="0.7"/>
<text x="89.45" y="81.06" font-size="3.0" fill="#1a1a1a" font-weight="bold">BOTTOM</text>
<line x1="89.45" y1="118.51" x2="89.45" y2="136.51" stroke="#1565C0" stroke-width="0.3"/>
<line x1="237.65" y1="118.51" x2="237.65" y2="136.51" stroke="#1565C0" stroke-width="0.3"/>
<line x1="89.45" y1="136.51" x2="237.65" y2="136.51" stroke="#1565C0" stroke-width="0.4" marker-start="url(#arrowR)" marker-end="url(#arrow)"/>
<text x="163.55" y="135.01" font-size="3.5" fill="#1565C0" text-anchor="middle">826.0</text>
<text x="163.55" y="139.51" font-size="2.5" fill="#1565C0" font-style="italic" text-anchor="middle">(32.520")</text>
</g>
<g id="view-left">
<rect x="35.00" y="35.00" width="34.45" height="9.06" fill="none" stroke="#1a1a1a" stroke-width="0.7"/>
<text x="35.00" y="32.00" font-size="3.0" fill="#1a1a1a" font-weight="bold">SIDE</text>
<line x1="35.00" y1="44.06" x2="35.00" y2="62.06" stroke="#1565C0" stroke-width="0.3"/>
<line x1="69.45" y1="44.06" x2="69.45" y2="62.06" stroke="#1565C0" stroke-width="0.3"/>
<line x1="35.00" y1="62.06" x2="69.45" y2="62.06" stroke="#1565C0" stroke-width="0.4" marker-start="url(#arrowR)" marker-end="url(#arrow)"/>
<text x="52.22" y="60.56" font-size="3.5" fill="#1565C0" text-anchor="middle">192.0</text>
<text x="52.22" y="65.06" font-size="2.5" fill="#1565C0" font-style="italic" text-anchor="middle">(7.559")</text>
<line x1="69.45" y1="35.00" x2="87.45" y2="35.00" stroke="#1565C0" stroke-width="0.3"/>
<line x1="69.45" y1="44.06" x2="87.45" y2="44.06" stroke="#1565C0" stroke-width="0.3"/>
<line x1="87.45" y1="35.00" x2="87.45" y2="44.06" stroke="#1565C0" stroke-width="0.4" marker-start="url(#arrowR)" marker-end="url(#arrow)"/>
<text x="85.95" y="39.53" font-size="3.5" fill="#1565C0" text-anchor="middle" transform="rotate(-90 85.95 39.53)">50.5</text>
<text x="89.95" y="39.53" font-size="2.5" fill="#1565C0" font-style="italic" text-anchor="middle" transform="rotate(-90 89.95 39.53)">(1.988")</text>
</g>
<g id="view-rear">
<rect x="312.10" y="35.00" width="148.20" height="9.06" fill="none" stroke="#1a1a1a" stroke-width="0.7"/>
<text x="312.10" y="32.00" font-size="3.0" fill="#1a1a1a" font-weight="bold">REAR</text>
<line x1="312.10" y1="44.06" x2="312.10" y2="62.06" stroke="#1565C0" stroke-width="0.3"/>
<line x1="460.30" y1="44.06" x2="460.30" y2="62.06" stroke="#1565C0" stroke-width="0.3"/>
<line x1="312.10" y1="62.06" x2="460.30" y2="62.06" stroke="#1565C0" stroke-width="0.4" marker-start="url(#arrowR)" marker-end="url(#arrow)"/>
<text x="386.20" y="60.56" font-size="3.5" fill="#1565C0" text-anchor="middle">826.0</text>
<text x="386.20" y="65.06" font-size="2.5" fill="#1565C0" font-style="italic" text-anchor="middle">(32.520")</text>
</g>
<g id="view-isometric_front">
<polygon points="377.83,216.20 466.75,216.20 466.75,210.76 377.83,210.76" fill="#f0f0f0" stroke="#1a1a1a" stroke-width="0.6"/>
<polygon points="377.83,210.76 466.75,210.76 478.68,203.87 389.76,203.87" fill="#e0e0e0" stroke="#1a1a1a" stroke-width="0.6"/>
<polygon points="466.75,216.20 466.75,210.76 478.68,203.87" fill="#d0d0d0" stroke="#1a1a1a" stroke-width="0.6"/>
</g>
<rect x="15.0" y="267.5" width="390.0" height="22.0" fill="#f5f5f5" stroke="#1a1a1a" stroke-width="0.4"/>
<text x="18.0" y="274.5" font-size="5.0" fill="#1a1a1a" font-weight="bold">MR28uws Gen1_EN — MR28uws Gen1_EN</text>
<text x="18.0" y="280.5" font-size="3.0" fill="#1a1a1a"><tspan font-weight="bold">Units: </tspan>Dimensions in mm (in)</text>
<text x="63.0" y="280.5" font-size="3.0" fill="#1a1a1a"><tspan font-weight="bold">Scale: </tspan>NTS</text>
<text x="108.0" y="280.5" font-size="3.0" fill="#1a1a1a"><tspan font-weight="bold">Rev: </tspan>A</text>
<text x="153.0" y="280.5" font-size="3.0" fill="#1a1a1a"><tspan font-weight="bold">Date: </tspan>2026-06-17</text>
<text x="198.0" y="280.5" font-size="3.0" fill="#1a1a1a"><tspan font-weight="bold">By: </tspan></text>
<text x="402.0" y="280.5" font-size="3.5" fill="#1a1a1a" text-anchor="end" font-weight="bold">MPMedia</text>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

+105
View File
@@ -0,0 +1,105 @@
{
"schema_version": "1.0",
"generated_at": "2026-06-17T20:43:53.406236+00:00",
"source_path": "/data/MR28uws Gen1_EN.step",
"model_name": "MR28uws Gen1_EN",
"mode": "enclosure_only",
"style": "rendered",
"layout_mode": "single_sheet",
"engine_used": "build123d",
"fallback_invoked": false,
"units": {
"primary": "mm",
"secondary": "in"
},
"overall_width": 826.0,
"overall_height": 50.5,
"overall_depth": 192.0,
"bounding_box": {
"x_min": -540.64,
"x_max": 285.36,
"y_min": -86.21,
"y_max": 105.79,
"z_min": 799.5,
"z_max": 850.0
},
"active_area": null,
"mounting_dimensions": null,
"mounting_variants": [],
"selected_parts": [
"J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc.step",
"Small Glass Lens",
"8Ω 5W Speaker (Model 3070)",
"TOSN-AD120P12V10A-120W",
"3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07",
"28\" Screen Pressure Clip",
"28\" Terminal Board",
"3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07.step",
"XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step",
"TOSN-DY398P-EMC Subboard",
"HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step",
"Glass 3+3",
"BSCZ-TX3361",
"28\" Rear Cover",
"28\" Lower Aluminum Plate",
"28\" High-speed Rail Display Screen Assembly Drawing",
"Board~a9jc.step",
"jgj-hy0280HD03 Module",
"Remote Control + Light Sensor Board",
"2P Phoenix Terminal",
"BNC-1~BNC-TH_BNC-50KWYE~a9jc.step",
"J7~XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step",
"28\" Constant Current Board",
"28\" Strip Screen Aluminum Frame",
"28\" Screen Pressure Clip 2",
"M3x6 Countersunk Screw",
"28\" Upper Aluminum Plate",
"28\" Screen Pressure Clip 1",
"28\" Light Sensor Bracket",
"28\" Mounting Plate",
"J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step"
],
"excluded_parts": [],
"mapping_file_used": null,
"datablock": {
"model_number": "MR28uws Gen1_EN",
"display_name": "MR28uws Gen1_EN",
"revision": "A",
"drawing_date": "2026-06-17",
"drawn_by": "",
"company": "MPMedia",
"units_note": "Dimensions in mm (in)",
"scale": "NTS",
"custom_fields": {}
},
"layout": {
"views_included": [
"front",
"isometric_front",
"left",
"rear",
"bottom"
],
"sheet_size": "A3_landscape",
"scale_ratio": "NTS",
"dimension_style": "baseline",
"iso_style": "shaded_render"
},
"outputs": {
"diagram_png": "/data/MR28uws Gen1_EN__external-diagram.png",
"diagram_pdf": null,
"diagram_svg": "/data/MR28uws Gen1_EN__external-diagram.svg",
"iso_png": null,
"front_png": "front",
"side_png": "left",
"rear_png": null,
"meta_json": "/data/MR28uws Gen1_EN__meta.json",
"variant_outputs": null
},
"warnings": [
"Active area (screen aperture) not detected — omitted from diagram."
],
"notes": [
"SVG written: MR28uws Gen1_EN__external-diagram.svg"
]
}
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+730
View File
@@ -0,0 +1,730 @@
──────────────────────────────────────────────────────────────
MR16s Gen1_EN.step
──────────────────────────────────────────────────────────────
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
--- run 1: BOM + thumbnails (kernel + openpyxl + pyrender/OSMesa) ---
INFO step_processor: Loading: MR16s Gen1_EN.step
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG build123d: monkey-patching `Vector.add` and `Vector.sub`
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG fontTools.ttLib.ttFont: Reading 'OS/2' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'OS/2' table
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG fontTools.ttLib.ttFont: Reading 'OS/2' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'OS/2' table
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG fontTools.ttLib.ttFont: Reading 'OS/2' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'OS/2' table
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG fontTools.ttLib.ttFont: Reading 'OS/2' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'OS/2' table
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG fontTools.ttLib.ttFont: Reading 'OS/2' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'OS/2' table
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG fontTools.ttLib.ttFont: Reading 'OS/2' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'OS/2' table
INFO step_processor.loader: [build123d] Loading: MR16s Gen1_EN.step
DEBUG build123d: Adding children XH4X1-2_54~CONN-TH_4P-P2_50_MEGASTAR_ZX-XH2_54-4PZZ~a9jc_step to
DEBUG build123d: Updated parent of HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc_step to
DEBUG build123d: Adding children HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc_step to
DEBUG build123d: Adding children BNC-1~BNC-TH_BNC-50KWYE~a9jc_step to
DEBUG build123d: Adding children BNC-1~BNC-TH_BNC-50KWYE~a9jc_step to
DEBUG build123d: Updated parent of J7~XH4X1-2_54~CONN-TH_4P-P2_50_MEGASTAR_ZX-XH2_54-4PZZ~a9jc_step to
DEBUG build123d: Updated parent of J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc_step to
DEBUG build123d: Updated parent of J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc_step to
DEBUG build123d: Updated parent of J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc_step to
DEBUG build123d: Adding children J7~XH4X1-2_54~CONN-TH_4P-P2_50_MEGASTAR_ZX-XH2_54-4PZZ~a9jc_step,J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc_step,J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc_step,J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc_step,Board~a9jc_step to
DEBUG build123d: Updated parent of 3D_YZ-006-V3_HDMI_TO_2SDI_2025-11-07_step to
DEBUG build123d: Adding children 3D_YZ-006-V3_HDMI_TO_2SDI_2025-11-07_step to
DEBUG build123d: Updated parent of 15_6"_Rear_Cover to
DEBUG build123d: Updated parent of 15_6"_Terminal_Board to
DEBUG build123d: Updated parent of 3D_YZ-006-V3_HDMI_TO_2SDI_2025-11-07 to
DEBUG build123d: Updated parent of BSCZ-TX3361 to
DEBUG build123d: Updated parent of Remote_Control_+_Light_Sensor_Board to
DEBUG build123d: Adding children 15_6"_Aluminum_Frame,15_6"_Tempered_Glass_3+3_Laminated,G156HAN02_0--20221031_G156HAN02_0_PSpec_DBEST,15_6"_Rear_Cover,15_6"_Mounting_Plate,15_6"_Terminal_Board,2P_Phoenix_Terminal,3D_YZ-006-V3_HDMI_TO_2SDI_2025-11-07,15_6"_Light_Sensor_Bracket,8Ω_5W_Speaker__Model_3070_,8Ω_5W_Speaker__Model_3070_,DZ-LP0632_Light_Sensor_Control_Board,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,TOSN-AD120P12V10A-120W,BSCZ-TX3361,Upper_Aluminum_Plate,Lower_Aluminum_Plate,Remote_Control_+_Light_Sensor_Board,Small_Glass_Lens,TOSN-DY398P-EMC_Sub-board to
DEBUG build123d: Updated parent of 15_6"_High-Speed_Rail_Display_Assembly_Drawing to
DEBUG build123d: Adding children 15_6"_High-Speed_Rail_Display_Assembly_Drawing to
INFO step_processor.loader: [build123d] Loaded: MR16s Gen1_EN.step | 5655 faces | 84 parts
INFO step_processor: [build123d] Loaded: MR16s Gen1_EN.step
INFO step_processor.bom: STEP text parser found 28 unique part names
INFO step_processor.bom: BOM extracted: 28 parts
INFO step_processor.bom: BOM XLSX → MR16s Gen1_EN_bom.xlsx
INFO step_processor: BOM XLSX → /data/MR16s Gen1_EN_bom.xlsx
DEBUG trimesh.util: falling back to hashlib hashing: `pip install xxhash`for 50x faster cache checks
DEBUG trimesh.util: face_normals didn't match triangles, ignoring!
DEBUG trimesh.util: `trimesh.load(force='mesh')` is a compatibility wrapper for `trimesh.load_mesh`
INFO step_processor.renderer: STL fallback: 1312154 faces (uniform color)
DEBUG OpenGL.platform.ctypesloader: Loaded libOSMesa.so => libOSMesa.so.8 <CDLL 'libOSMesa.so.8', handle 5555691ab310 at 0x7fffb4377290>
INFO OpenGL.acceleratesupport: No OpenGL_accelerate module loaded: No module named 'OpenGL_accelerate'
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_OBJECT_ATTACHED_OBJECTS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_OBJECT_DELETE_STATUS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_OBJECT_INFO_LOG_LENGTH_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_OBJECT_SHADER_SOURCE_LENGTH_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_OBJECT_SUBTYPE_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_OBJECT_VALIDATE_STATUS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_PROGRAM_OBJECT_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_SHADER_OBJECT_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GLhalfARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glAttachObjectARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glDeleteObjectARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glDetachObjectARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glGetAttachedObjectsARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glGetHandleARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glGetInfoLogARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glGetObjectParameterfvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glGetObjectParameterivARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glInitShaderObjectsARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.fragment_shader.GLhalfARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.fragment_shader.glInitFragmentShaderARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_shader.GL_OBJECT_ACTIVE_ATTRIBUTES_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_shader.GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_shader.GLhalfARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_shader.glGetObjectParameterivARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_shader.glInitVertexShaderARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_CURRENT_MATRIX_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_CURRENT_MATRIX_STACK_DEPTH_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX0_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX10_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX11_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX12_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX13_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX14_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX15_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX16_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX17_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX18_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX19_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX1_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX20_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX21_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX22_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX23_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX24_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX25_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX26_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX27_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX28_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX29_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX2_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX30_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX31_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX3_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX4_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX5_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX6_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX7_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX8_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX9_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_ATTRIBS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_ENV_PARAMETERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_INSTRUCTIONS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_MATRICES_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_PARAMETERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_TEMPORARIES_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_ADDRESS_REGISTERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_ATTRIBS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_BINDING_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_ERROR_POSITION_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_ERROR_STRING_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_FORMAT_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_FORMAT_ASCII_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_INSTRUCTIONS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_LENGTH_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_NATIVE_ATTRIBS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_NATIVE_PARAMETERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_NATIVE_TEMPORARIES_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_PARAMETERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_STRING_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_TEMPORARIES_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_TRANSPOSE_CURRENT_MATRIX_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_VERTEX_PROGRAM_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GLhalfARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glBindProgramARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glDeleteProgramsARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGenProgramsARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGetProgramEnvParameterdvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGetProgramEnvParameterfvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGetProgramLocalParameterdvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGetProgramLocalParameterfvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGetProgramStringARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGetProgramivARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glInitVertexProgramARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramEnvParameter4dARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramEnvParameter4dvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramEnvParameter4fARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramEnvParameter4fvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramLocalParameter4dARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramLocalParameter4dvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramLocalParameter4fARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramLocalParameter4fvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramStringARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.geometry_shader4.GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.geometry_shader4.GL_MAX_GEOMETRY_VARYING_COMPONENTS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.geometry_shader4.GL_MAX_VERTEX_VARYING_COMPONENTS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.geometry_shader4.GLhalfARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.geometry_shader4.glFramebufferTextureFaceARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.geometry_shader4.glInitGeometryShader4ARB
DEBUG PIL.Image: Importing PngImagePlugin
INFO step_processor.renderer: Rendered: MR16s Gen1_EN_front.png
INFO step_processor.renderer: Rendered: MR16s Gen1_EN_rear.png
INFO step_processor.renderer: Rendered: MR16s Gen1_EN_left.png
INFO step_processor.renderer: Rendered: MR16s Gen1_EN_right.png
INFO step_processor.renderer: Rendered: MR16s Gen1_EN_iso_left.png
INFO step_processor.renderer: Rendered: MR16s Gen1_EN_iso_right.png
INFO step_processor: Thumbnails: 6 PNG(s) written
INFO step_processor: Done.
────────────────────────────────────────────────────────────
MR16s Gen1_EN.step [build123d]
BOM: 28 parts → MR16s Gen1_EN_bom.xlsx
Thumbnails: 6 PNG(s)
────────────────────────────────────────────────────────────
--- run 2: geometry query ---
INFO Loading: MR16s Gen1_EN.step
INFO [build123d] Loading: MR16s Gen1_EN.step
INFO [build123d] Loaded: MR16s Gen1_EN.step | 5655 faces | 84 parts
INFO [build123d] Loaded: MR16s Gen1_EN.step
BOUNDING BOX — MR16s Gen1_EN.step
───────────────────────────────────
Axis Dimension
───────────────────────────────────
Width (X) 248.6 mm (9.787 in)
Depth (Y) 459.2 mm (18.079 in)
Height (Z) 41.2 mm (1.622 in)
───────────────────────────────────
--- run 3: external dimensional diagram (cairosvg) ---
INFO Loading: MR16s Gen1_EN.step
INFO [build123d] Loading: MR16s Gen1_EN.step
INFO [build123d] Loaded: MR16s Gen1_EN.step | 5655 faces | 84 parts
INFO [build123d] Loaded: MR16s Gen1_EN.step
INFO [build123d] Loading: MR16s Gen1_EN.step
INFO [build123d] Loaded: MR16s Gen1_EN.step | 5655 faces | 84 parts
INFO STEP text parser found 28 unique part names
INFO BOM extracted: 28 parts
WARNING Active area detection error: No module named 'OCC'
INFO PNG written: MR16s Gen1_EN__external-diagram.png
INFO STL fallback: 1312154 faces (uniform color)
INFO No OpenGL_accelerate module loaded: No module named 'OpenGL_accelerate'
INFO Rendered: MR16s Gen1_EN_iso_left.png
INFO Rendered: MR16s Gen1_EN_right.png
INFO Rendered: MR16s Gen1_EN_left.png
INFO Rendered: MR16s Gen1_EN_bottom.png
INFO Rendered: MR16s Gen1_EN_front.png
INFO Diagram → MR16s Gen1_EN__external-diagram.svg
INFO Done.
────────────────────────────────────────────────────────────
MR16s Gen1_EN.step [build123d]
Diagram → MR16s Gen1_EN__external-diagram.svg
────────────────────────────────────────────────────────────
PASS: MR16s Gen1_EN.step
──────────────────────────────────────────────────────────────
MR27s Gen1_EN.step
──────────────────────────────────────────────────────────────
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
--- run 1: BOM + thumbnails (kernel + openpyxl + pyrender/OSMesa) ---
INFO step_processor: Loading: MR27s Gen1_EN.step
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG build123d: monkey-patching `Vector.add` and `Vector.sub`
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG fontTools.ttLib.ttFont: Reading 'OS/2' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'OS/2' table
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG fontTools.ttLib.ttFont: Reading 'OS/2' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'OS/2' table
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG fontTools.ttLib.ttFont: Reading 'OS/2' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'OS/2' table
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG fontTools.ttLib.ttFont: Reading 'OS/2' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'OS/2' table
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG fontTools.ttLib.ttFont: Reading 'OS/2' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'OS/2' table
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG fontTools.ttLib.ttFont: Reading 'OS/2' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'OS/2' table
INFO step_processor.loader: [build123d] Loading: MR27s Gen1_EN.step
DEBUG build123d: Adding children XH4X1-2_54~CONN-TH_4P-P2_50_MEGASTAR_ZX-XH2_54-4PZZ~a9jc_step to
DEBUG build123d: Updated parent of HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc_step to
DEBUG build123d: Adding children HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc_step to
DEBUG build123d: Adding children BNC-1~BNC-TH_BNC-50KWYE~a9jc_step to
DEBUG build123d: Adding children BNC-1~BNC-TH_BNC-50KWYE~a9jc_step to
DEBUG build123d: Updated parent of J7~XH4X1-2_54~CONN-TH_4P-P2_50_MEGASTAR_ZX-XH2_54-4PZZ~a9jc_step to
DEBUG build123d: Updated parent of J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc_step to
DEBUG build123d: Updated parent of J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc_step to
DEBUG build123d: Updated parent of J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc_step to
DEBUG build123d: Adding children J7~XH4X1-2_54~CONN-TH_4P-P2_50_MEGASTAR_ZX-XH2_54-4PZZ~a9jc_step,J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc_step,J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc_step,J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc_step,Board~a9jc_step to
DEBUG build123d: Updated parent of 3D_YZ-006-V3_HDMI_TO_2SDI_2025-11-07_step to
DEBUG build123d: Adding children 3D_YZ-006-V3_HDMI_TO_2SDI_2025-11-07_step to
DEBUG build123d: Updated parent of Rear_Cover to
DEBUG build123d: Updated parent of 3D_YZ-006-V3_HDMI_TO_2SDI_2025-11-07 to
DEBUG build123d: Updated parent of BSCZ-TX3361 to
DEBUG build123d: Updated parent of Terminal_Board to
DEBUG build123d: Updated parent of Remote_Control_+_Light_Sensor_Board to
DEBUG build123d: Adding children 27-inch_Aluminum_Frame,Tempered_Glass_3+3_Laminated,P270HVN03_0,Rear_Cover,2P_Phoenix_Connector_Terminal,3D_YZ-006-V3_HDMI_TO_2SDI_2025-11-07,BSCZ-TX3361,TOSN-AD120P12V10A-120W,Mounting_Plate,Terminal_Board,8Ω_5W_Speaker__Model_3070_,8Ω_5W_Speaker__Model_3070_,DZ-LP6608_REV1_0,Light_Sensor_Bracket,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,27-inch_Upper_Aluminum_Panel,27-inch_Lower_Aluminum_Panel,Small_Glass_Mirror_Lens,Remote_Control_+_Light_Sensor_Board,TOSN-DY398P-EMC_Small_PCB_Board,Screen_Clamp,27-inch_Screen_Clamp_1,27-inch_Screen_Clamp_2,27-inch_Screen_Clamp_1,27-inch_Screen_Clamp_2 to
DEBUG build123d: Updated parent of 27-inch_High-speed_Rail_Display_Screen_Assembly_Drawing to
DEBUG build123d: Adding children 27-inch_High-speed_Rail_Display_Screen_Assembly_Drawing to
INFO step_processor.loader: [build123d] Loaded: MR27s Gen1_EN.step | 6732 faces | 102 parts
INFO step_processor: [build123d] Loaded: MR27s Gen1_EN.step
INFO step_processor.bom: STEP text parser found 31 unique part names
INFO step_processor.bom: BOM extracted: 31 parts
INFO step_processor.bom: BOM XLSX → MR27s Gen1_EN_bom.xlsx
INFO step_processor: BOM XLSX → /data/MR27s Gen1_EN_bom.xlsx
DEBUG trimesh.util: falling back to hashlib hashing: `pip install xxhash`for 50x faster cache checks
DEBUG trimesh.util: face_normals didn't match triangles, ignoring!
DEBUG trimesh.util: `trimesh.load(force='mesh')` is a compatibility wrapper for `trimesh.load_mesh`
INFO step_processor.renderer: STL fallback: 1493746 faces (uniform color)
DEBUG OpenGL.platform.ctypesloader: Loaded libOSMesa.so => libOSMesa.so.8 <CDLL 'libOSMesa.so.8', handle 555563e68ed0 at 0x7fffb4313b10>
INFO OpenGL.acceleratesupport: No OpenGL_accelerate module loaded: No module named 'OpenGL_accelerate'
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_OBJECT_ATTACHED_OBJECTS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_OBJECT_DELETE_STATUS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_OBJECT_INFO_LOG_LENGTH_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_OBJECT_SHADER_SOURCE_LENGTH_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_OBJECT_SUBTYPE_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_OBJECT_VALIDATE_STATUS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_PROGRAM_OBJECT_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_SHADER_OBJECT_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GLhalfARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glAttachObjectARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glDeleteObjectARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glDetachObjectARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glGetAttachedObjectsARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glGetHandleARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glGetInfoLogARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glGetObjectParameterfvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glGetObjectParameterivARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glInitShaderObjectsARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.fragment_shader.GLhalfARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.fragment_shader.glInitFragmentShaderARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_shader.GL_OBJECT_ACTIVE_ATTRIBUTES_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_shader.GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_shader.GLhalfARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_shader.glGetObjectParameterivARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_shader.glInitVertexShaderARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_CURRENT_MATRIX_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_CURRENT_MATRIX_STACK_DEPTH_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX0_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX10_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX11_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX12_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX13_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX14_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX15_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX16_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX17_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX18_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX19_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX1_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX20_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX21_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX22_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX23_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX24_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX25_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX26_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX27_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX28_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX29_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX2_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX30_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX31_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX3_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX4_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX5_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX6_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX7_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX8_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX9_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_ATTRIBS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_ENV_PARAMETERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_INSTRUCTIONS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_MATRICES_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_PARAMETERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_TEMPORARIES_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_ADDRESS_REGISTERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_ATTRIBS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_BINDING_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_ERROR_POSITION_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_ERROR_STRING_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_FORMAT_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_FORMAT_ASCII_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_INSTRUCTIONS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_LENGTH_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_NATIVE_ATTRIBS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_NATIVE_PARAMETERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_NATIVE_TEMPORARIES_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_PARAMETERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_STRING_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_TEMPORARIES_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_TRANSPOSE_CURRENT_MATRIX_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_VERTEX_PROGRAM_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GLhalfARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glBindProgramARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glDeleteProgramsARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGenProgramsARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGetProgramEnvParameterdvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGetProgramEnvParameterfvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGetProgramLocalParameterdvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGetProgramLocalParameterfvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGetProgramStringARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGetProgramivARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glInitVertexProgramARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramEnvParameter4dARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramEnvParameter4dvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramEnvParameter4fARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramEnvParameter4fvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramLocalParameter4dARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramLocalParameter4dvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramLocalParameter4fARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramLocalParameter4fvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramStringARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.geometry_shader4.GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.geometry_shader4.GL_MAX_GEOMETRY_VARYING_COMPONENTS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.geometry_shader4.GL_MAX_VERTEX_VARYING_COMPONENTS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.geometry_shader4.GLhalfARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.geometry_shader4.glFramebufferTextureFaceARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.geometry_shader4.glInitGeometryShader4ARB
DEBUG PIL.Image: Importing PngImagePlugin
INFO step_processor.renderer: Rendered: MR27s Gen1_EN_front.png
INFO step_processor.renderer: Rendered: MR27s Gen1_EN_rear.png
INFO step_processor.renderer: Rendered: MR27s Gen1_EN_left.png
INFO step_processor.renderer: Rendered: MR27s Gen1_EN_right.png
INFO step_processor.renderer: Rendered: MR27s Gen1_EN_iso_left.png
INFO step_processor.renderer: Rendered: MR27s Gen1_EN_iso_right.png
INFO step_processor: Thumbnails: 6 PNG(s) written
INFO step_processor: Done.
────────────────────────────────────────────────────────────
MR27s Gen1_EN.step [build123d]
BOM: 31 parts → MR27s Gen1_EN_bom.xlsx
Thumbnails: 6 PNG(s)
────────────────────────────────────────────────────────────
--- run 2: geometry query ---
INFO Loading: MR27s Gen1_EN.step
INFO [build123d] Loading: MR27s Gen1_EN.step
INFO [build123d] Loaded: MR27s Gen1_EN.step | 6732 faces | 102 parts
INFO [build123d] Loaded: MR27s Gen1_EN.step
BOUNDING BOX — MR27s Gen1_EN.step
───────────────────────────────────
Axis Dimension
───────────────────────────────────
Width (X) 397.2 mm (15.638 in)
Depth (Y) 718.6 mm (28.291 in)
Height (Z) 41.2 mm (1.622 in)
───────────────────────────────────
--- run 3: external dimensional diagram (cairosvg) ---
INFO Loading: MR27s Gen1_EN.step
INFO [build123d] Loading: MR27s Gen1_EN.step
INFO [build123d] Loaded: MR27s Gen1_EN.step | 6732 faces | 102 parts
INFO [build123d] Loaded: MR27s Gen1_EN.step
INFO [build123d] Loading: MR27s Gen1_EN.step
INFO [build123d] Loaded: MR27s Gen1_EN.step | 6732 faces | 102 parts
INFO STEP text parser found 31 unique part names
INFO BOM extracted: 31 parts
WARNING Active area detection error: No module named 'OCC'
INFO PNG written: MR27s Gen1_EN__external-diagram.png
INFO STL fallback: 1493746 faces (uniform color)
INFO No OpenGL_accelerate module loaded: No module named 'OpenGL_accelerate'
INFO Rendered: MR27s Gen1_EN_left.png
INFO Rendered: MR27s Gen1_EN_right.png
INFO Rendered: MR27s Gen1_EN_bottom.png
INFO Rendered: MR27s Gen1_EN_iso_left.png
INFO Rendered: MR27s Gen1_EN_front.png
INFO Diagram → MR27s Gen1_EN__external-diagram.svg
INFO Done.
────────────────────────────────────────────────────────────
MR27s Gen1_EN.step [build123d]
Diagram → MR27s Gen1_EN__external-diagram.svg
────────────────────────────────────────────────────────────
PASS: MR27s Gen1_EN.step
──────────────────────────────────────────────────────────────
MR28uws Gen1_EN.step
──────────────────────────────────────────────────────────────
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
--- run 1: BOM + thumbnails (kernel + openpyxl + pyrender/OSMesa) ---
INFO step_processor: Loading: MR28uws Gen1_EN.step
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG build123d: monkey-patching `Vector.add` and `Vector.sub`
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG fontTools.ttLib.ttFont: Reading 'OS/2' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'OS/2' table
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG fontTools.ttLib.ttFont: Reading 'OS/2' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'OS/2' table
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG fontTools.ttLib.ttFont: Reading 'OS/2' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'OS/2' table
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG fontTools.ttLib.ttFont: Reading 'OS/2' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'OS/2' table
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG fontTools.ttLib.ttFont: Reading 'OS/2' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'OS/2' table
DEBUG fontTools.ttLib.ttFont: Reading 'name' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'name' table
DEBUG fontTools.ttLib.ttFont: Reading 'OS/2' table from disk
DEBUG fontTools.ttLib.ttFont: Decompiling 'OS/2' table
INFO step_processor.loader: [build123d] Loading: MR28uws Gen1_EN.step
DEBUG build123d: Adding children XH4X1-2_54~CONN-TH_4P-P2_50_MEGASTAR_ZX-XH2_54-4PZZ~a9jc_step to
DEBUG build123d: Updated parent of HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc_step to
DEBUG build123d: Adding children HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc_step to
DEBUG build123d: Adding children BNC-1~BNC-TH_BNC-50KWYE~a9jc_step to
DEBUG build123d: Adding children BNC-1~BNC-TH_BNC-50KWYE~a9jc_step to
DEBUG build123d: Updated parent of J7~XH4X1-2_54~CONN-TH_4P-P2_50_MEGASTAR_ZX-XH2_54-4PZZ~a9jc_step to
DEBUG build123d: Updated parent of J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc_step to
DEBUG build123d: Updated parent of J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc_step to
DEBUG build123d: Updated parent of J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc_step to
DEBUG build123d: Adding children J7~XH4X1-2_54~CONN-TH_4P-P2_50_MEGASTAR_ZX-XH2_54-4PZZ~a9jc_step,J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc_step,J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc_step,J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc_step,Board~a9jc_step to
DEBUG build123d: Updated parent of 3D_YZ-006-V3_HDMI_TO_2SDI_2025-11-07_step to
DEBUG build123d: Adding children 3D_YZ-006-V3_HDMI_TO_2SDI_2025-11-07_step to
DEBUG build123d: Updated parent of 28"_Rear_Cover to
DEBUG build123d: Updated parent of 28"_Mounting_Plate to
DEBUG build123d: Updated parent of Remote_Control_+_Light_Sensor_Board to
DEBUG build123d: Updated parent of 28"_Terminal_Board to
DEBUG build123d: Updated parent of 3D_YZ-006-V3_HDMI_TO_2SDI_2025-11-07 to
DEBUG build123d: Updated parent of BSCZ-TX3361 to
DEBUG build123d: Updated parent of 28"_Constant_Current_Board to
DEBUG build123d: Adding children 28"_Strip_Screen_Aluminum_Frame,28"_Upper_Aluminum_Plate,28"_Lower_Aluminum_Plate,Glass_3+3,28"_Rear_Cover,Small_Glass_Lens,28"_Mounting_Plate,Remote_Control_+_Light_Sensor_Board,28"_Light_Sensor_Bracket,28"_Terminal_Board,2P_Phoenix_Terminal,3D_YZ-006-V3_HDMI_TO_2SDI_2025-11-07,BSCZ-TX3361,TOSN-AD120P12V10A-120W,TOSN-DY398P-EMC_Subboard,8Ω_5W_Speaker__Model_3070_,8Ω_5W_Speaker__Model_3070_,28"_Constant_Current_Board,28"_Screen_Pressure_Clip,28"_Screen_Pressure_Clip_1,28"_Screen_Pressure_Clip_2,jgj-hy0280HD03_Module,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw,M3x6_Countersunk_Screw to
DEBUG build123d: Updated parent of 28"_High-speed_Rail_Display_Screen_Assembly_Drawing to
DEBUG build123d: Adding children 28"_High-speed_Rail_Display_Screen_Assembly_Drawing to
INFO step_processor.loader: [build123d] Loaded: MR28uws Gen1_EN.step | 5891 faces | 94 parts
INFO step_processor: [build123d] Loaded: MR28uws Gen1_EN.step
INFO step_processor.bom: STEP text parser found 31 unique part names
INFO step_processor.bom: BOM extracted: 31 parts
INFO step_processor.bom: BOM XLSX → MR28uws Gen1_EN_bom.xlsx
INFO step_processor: BOM XLSX → /data/MR28uws Gen1_EN_bom.xlsx
DEBUG trimesh.util: falling back to hashlib hashing: `pip install xxhash`for 50x faster cache checks
DEBUG trimesh.util: face_normals didn't match triangles, ignoring!
DEBUG trimesh.util: `trimesh.load(force='mesh')` is a compatibility wrapper for `trimesh.load_mesh`
INFO step_processor.renderer: STL fallback: 1252930 faces (uniform color)
DEBUG OpenGL.platform.ctypesloader: Loaded libOSMesa.so => libOSMesa.so.8 <CDLL 'libOSMesa.so.8', handle 55556092a370 at 0x7fffb4c381d0>
INFO OpenGL.acceleratesupport: No OpenGL_accelerate module loaded: No module named 'OpenGL_accelerate'
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_OBJECT_ATTACHED_OBJECTS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_OBJECT_DELETE_STATUS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_OBJECT_INFO_LOG_LENGTH_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_OBJECT_SHADER_SOURCE_LENGTH_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_OBJECT_SUBTYPE_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_OBJECT_VALIDATE_STATUS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_PROGRAM_OBJECT_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GL_SHADER_OBJECT_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.GLhalfARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glAttachObjectARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glDeleteObjectARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glDetachObjectARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glGetAttachedObjectsARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glGetHandleARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glGetInfoLogARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glGetObjectParameterfvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glGetObjectParameterivARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.shader_objects.glInitShaderObjectsARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.fragment_shader.GLhalfARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.fragment_shader.glInitFragmentShaderARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_shader.GL_OBJECT_ACTIVE_ATTRIBUTES_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_shader.GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_shader.GLhalfARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_shader.glGetObjectParameterivARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_shader.glInitVertexShaderARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_CURRENT_MATRIX_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_CURRENT_MATRIX_STACK_DEPTH_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX0_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX10_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX11_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX12_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX13_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX14_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX15_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX16_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX17_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX18_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX19_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX1_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX20_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX21_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX22_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX23_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX24_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX25_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX26_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX27_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX28_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX29_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX2_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX30_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX31_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX3_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX4_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX5_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX6_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX7_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX8_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MATRIX9_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_ATTRIBS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_ENV_PARAMETERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_INSTRUCTIONS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_MATRICES_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_PARAMETERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_MAX_PROGRAM_TEMPORARIES_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_ADDRESS_REGISTERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_ATTRIBS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_BINDING_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_ERROR_POSITION_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_ERROR_STRING_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_FORMAT_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_FORMAT_ASCII_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_INSTRUCTIONS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_LENGTH_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_NATIVE_ATTRIBS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_NATIVE_PARAMETERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_NATIVE_TEMPORARIES_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_PARAMETERS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_STRING_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_TEMPORARIES_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_TRANSPOSE_CURRENT_MATRIX_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GL_VERTEX_PROGRAM_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.GLhalfARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glBindProgramARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glDeleteProgramsARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGenProgramsARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGetProgramEnvParameterdvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGetProgramEnvParameterfvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGetProgramLocalParameterdvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGetProgramLocalParameterfvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGetProgramStringARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glGetProgramivARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glInitVertexProgramARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramEnvParameter4dARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramEnvParameter4dvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramEnvParameter4fARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramEnvParameter4fvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramLocalParameter4dARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramLocalParameter4dvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramLocalParameter4fARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramLocalParameter4fvARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.vertex_program.glProgramStringARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.geometry_shader4.GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.geometry_shader4.GL_MAX_GEOMETRY_VARYING_COMPONENTS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.geometry_shader4.GL_MAX_VERTEX_VARYING_COMPONENTS_ARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.geometry_shader4.GLhalfARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.geometry_shader4.glFramebufferTextureFaceARB
DEBUG OpenGL.GL.shaders: Found no alternate for: OpenGL.GL.ARB.geometry_shader4.glInitGeometryShader4ARB
DEBUG PIL.Image: Importing PngImagePlugin
INFO step_processor.renderer: Rendered: MR28uws Gen1_EN_front.png
INFO step_processor.renderer: Rendered: MR28uws Gen1_EN_rear.png
INFO step_processor.renderer: Rendered: MR28uws Gen1_EN_left.png
INFO step_processor.renderer: Rendered: MR28uws Gen1_EN_right.png
INFO step_processor.renderer: Rendered: MR28uws Gen1_EN_iso_left.png
INFO step_processor.renderer: Rendered: MR28uws Gen1_EN_iso_right.png
INFO step_processor: Thumbnails: 6 PNG(s) written
INFO step_processor: Done.
────────────────────────────────────────────────────────────
MR28uws Gen1_EN.step [build123d]
BOM: 31 parts → MR28uws Gen1_EN_bom.xlsx
Thumbnails: 6 PNG(s)
────────────────────────────────────────────────────────────
--- run 2: geometry query ---
INFO Loading: MR28uws Gen1_EN.step
INFO [build123d] Loading: MR28uws Gen1_EN.step
INFO [build123d] Loaded: MR28uws Gen1_EN.step | 5891 faces | 94 parts
INFO [build123d] Loaded: MR28uws Gen1_EN.step
BOUNDING BOX — MR28uws Gen1_EN.step
───────────────────────────────────
Axis Dimension
───────────────────────────────────
Width (X) 826.0 mm (32.520 in)
Depth (Y) 192.0 mm (7.559 in)
Height (Z) 50.5 mm (1.988 in)
───────────────────────────────────
--- run 3: external dimensional diagram (cairosvg) ---
INFO Loading: MR28uws Gen1_EN.step
INFO [build123d] Loading: MR28uws Gen1_EN.step
INFO [build123d] Loaded: MR28uws Gen1_EN.step | 5891 faces | 94 parts
INFO [build123d] Loaded: MR28uws Gen1_EN.step
INFO [build123d] Loading: MR28uws Gen1_EN.step
INFO [build123d] Loaded: MR28uws Gen1_EN.step | 5891 faces | 94 parts
INFO STEP text parser found 31 unique part names
INFO BOM extracted: 31 parts
WARNING Active area detection error: No module named 'OCC'
INFO PNG written: MR28uws Gen1_EN__external-diagram.png
INFO STL fallback: 1252930 faces (uniform color)
INFO No OpenGL_accelerate module loaded: No module named 'OpenGL_accelerate'
INFO Rendered: MR28uws Gen1_EN_right.png
INFO Rendered: MR28uws Gen1_EN_front.png
INFO Rendered: MR28uws Gen1_EN_left.png
INFO Rendered: MR28uws Gen1_EN_bottom.png
INFO Rendered: MR28uws Gen1_EN_iso_left.png
INFO Diagram → MR28uws Gen1_EN__external-diagram.svg
INFO Done.
────────────────────────────────────────────────────────────
MR28uws Gen1_EN.step [build123d]
Diagram → MR28uws Gen1_EN__external-diagram.svg
────────────────────────────────────────────────────────────
PASS: MR28uws Gen1_EN.step
==============================================================
Phase 0 smoke test: 3 passed, 0 failed
Artifacts: /Users/jasonstedwell/Documents/CODING/step parse/_phase0_out
==============================================================
SMOKE_EXIT=0
Executable
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Build the Phase 0 CLI image for linux/amd64 (Unraid target).
# Works from an arm64 Mac via buildx + QEMU emulation (slow first time).
set -euo pipefail
cd "$(dirname "$0")"
IMAGE="${IMAGE:-step-parser}"
TAG="${TAG:-dev}"
PLATFORM="${PLATFORM:-linux/amd64}"
REGISTRY="${REGISTRY:-registry.alwisp.com/jason}"
echo "Building ${IMAGE}:${TAG} for ${PLATFORM} ..."
docker buildx build --platform "${PLATFORM}" -t "${IMAGE}:${TAG}" --load .
cat <<EOF
Built ${IMAGE}:${TAG}.
Next:
./smoke-test.sh # validate the kernel + render + diagram
docker run --rm --entrypoint pip ${IMAGE}:${TAG} freeze > requirements.lock.txt
Push to the Unraid registry once green:
docker tag ${IMAGE}:${TAG} ${REGISTRY}/${IMAGE}:latest
docker push ${REGISTRY}/${IMAGE}:latest
EOF
View File
+80
View File
@@ -0,0 +1,80 @@
annotated-types==0.7.0
anthropic==0.109.2
anyio==4.14.0
anytree==2.13.0
asttokens==3.0.1
build123d==0.11.0
cadquery-ocp-novtk==7.9.3.1.1
cadquery-ocp-proxy==7.9.3.1.1
cairocffi==1.7.1
CairoSVG==2.9.0
certifi==2026.6.17
cffi==2.0.0
charset-normalizer==3.4.7
cssselect2==0.9.0
decorator==5.3.1
defusedxml==0.7.1
distro==1.9.0
docstring_parser==0.18.0
et_xmlfile==2.0.0
executing==2.2.1
ezdxf==1.4.4
fonttools==4.63.0
freetype-py==2.5.1
h11==0.16.0
httpcore==1.0.9
httpx==0.28.1
idna==3.18
ImageIO==2.37.3
ipython==9.14.1
ipython_pygments_lexers==1.1.1
jedi==0.20.0
jiter==0.15.0
joblib==1.5.3
lib3mf==2.5.0
matplotlib-inline==0.2.2
mpmath==1.3.0
narwhals==2.22.1
networkx==3.6.1
numpy==2.4.6
ocp_gordon==0.2.0
ocpsvg==0.6.0
openpyxl==3.1.5
pandas==3.0.3
parso==0.8.7
pexpect==4.9.0
pillow==12.2.0
prompt_toolkit==3.0.52
psutil==7.2.2
ptyprocess==0.7.0
pure_eval==0.2.3
pycparser==3.0
pydantic==2.13.4
pydantic_core==2.46.4
pyglet==2.1.14
Pygments==2.20.0
PyOpenGL==3.1.7
pyparsing==3.3.2
pyrender==0.1.45
python-dateutil==2.9.0.post0
requests==2.34.2
scikit-learn==1.9.0
scipy==1.17.1
six==1.17.0
sniffio==1.3.1
stack-data==0.6.3
svgelements==1.9.6
svgpathtools==1.7.2
svgwrite==1.4.3
sympy==1.14.0
threadpoolctl==3.6.0
tinycss2==1.5.1
traitlets==5.15.1
trianglesolver==1.2
trimesh==4.12.2
typing-inspection==0.4.2
typing_extensions==4.15.0
urllib3==2.7.0
wcwidth==0.8.1
webcolors==24.8.0
webencodings==0.5.1
+26
View File
@@ -0,0 +1,26 @@
# STEP Parser — Phase 0 Python dependencies (linux/amd64 wheels)
#
# These are floor pins for the first green build. After the image builds and the
# smoke test passes, freeze exact versions:
# docker run --rm --entrypoint pip step-parser:dev freeze > requirements.lock.txt
# and switch the Dockerfile to install the lockfile for reproducible Unraid builds.
# --- CAD kernel ---------------------------------------------------------------
# build123d pulls cadquery-ocp (OpenCASCADE), which ships manylinux x86_64 wheels.
build123d>=0.7
# --- geometry / data ----------------------------------------------------------
numpy>=1.24
pandas>=2.0
openpyxl>=3.1 # MPM-branded .xlsx BOM output
# --- offscreen rendering (6-view thumbnails) ----------------------------------
trimesh>=4.0
pyrender>=0.1.45 # hard-pins PyOpenGL==3.1.0 — do NOT also pin PyOpenGL here (conflict)
Pillow>=10.0
# --- external dimensional diagram export --------------------------------------
cairosvg>=2.7 # SVG -> PNG/PDF
# --- Chinese -> English part-name translation ---------------------------------
anthropic>=0.39 # needs ANTHROPIC_API_KEY at runtime (optional in Phase 0)
+276
View File
@@ -0,0 +1,276 @@
# STEP Processor Skill — Project Context
### CoWork Memory Document · MPMedia Engineering
**Updated:** June 2026 · **Status:** ✅ FULLY TESTED — Smoke-tested on 3 Chinese-sourced STEP files
---
## What This Is
A Python-based CAD processing skill for MPMedia's engineering workflow. Reads STEP/STP files (display enclosures, mounting kits, hardware assemblies) and automates: thumbnail generation, parts BOM export (MPM-branded Excel), Chinese-to-English label translation, STEP file rewriting with English labels, geometric queries, and external dimensional diagram creation.
Lives as a CoWork skill folder with a `SKILL.md`. Callable from Claude or directly from CLI.
---
## Current Working State (as of June 2026)
### Smoke Test Results
All three production STEP files tested end-to-end:
| File | Faces | Parts (STEP parser) | Translations | _EN.step |
|------|-------|---------------------|--------------|----------|
| MR16s Gen1 | 5,655 | 28 | 16/16 | ✅ All English |
| MR27s Gen1 | 6,732 | 31 | 18/18 | ✅ All English |
| MR28uws Gen1 | 5,891 | 31 | 20/20 | ✅ All English |
### What Works
- **STEP loading**: build123d primary, FreeCAD headless fallback
- **GBK encoding**: Chinese CAD files fully decoded (see Architecture Notes below)
- **BOM extraction**: STEP text parser as primary source — correctly reads Chinese names
- **Translation**: Claude API (`claude-haiku-4-5-20251001`) — batched, single call per file
- **STEP rewriter**: `_EN.step` produced with English labels in both PRODUCT name fields
- **BOM export**: MPM-branded `.xlsx` (Montserrat/Open Sans, Dark Shade header, Gold border, alternating rows)
- **Thumbnails**: 6 PNG views via pyrender (front, rear, left, right, iso_left, iso_right)
### BOM Output Format
Columns in Excel output order:
| Column | Header in Excel |
|--------|----------------|
| part_number | Part # |
| part_description | Part Description |
| quantity | Qty |
| level | Level |
| parent | Parent |
| bbox_x_mm | X (mm) |
| bbox_y_mm | Y (mm) |
| bbox_z_mm | Z (mm) |
| notes | Notes |
| part_name_supplier | Supplier Part Name |
`part_description` = translated English name (was `part_name_english` internally).
`part_name_supplier` = original Chinese name from supplier file (last column, was `part_name_original`).
---
## Architecture Notes (Critical — Read Before Debugging)
### GBK Encoding Fix
STEP files from Chinese CAD tools (SolidWorks CN, etc.) embed raw GBK bytes in `PRODUCT` entity name strings. This caused two separate problems, each with a different fix:
**Problem 1: BOM shows garbled Chinese (mojibake)**
OpenCASCADE (OCC) / build123d's STEP reader applies an internal codec that maps each 2-byte GBK sequence to incorrect Unicode codepoints. This is NOT reversible via latin-1 → GBK round-trip because OCC's codec is not latin-1.
Fix: Bypass OCC entirely for part name extraction. `bom.py` reads the raw STEP file text directly with encoding detection (UTF-8 → GBK → latin-1 fallback) and parses `PRODUCT` entities via regex. This is the primary name source; OCC assembly walk is fallback only.
**Problem 2: _EN.step still shows Chinese in CAD viewer**
The rewriter (`rewriter.py`) was reading the file as UTF-8, turning GBK bytes into replacement chars (U+FFFD). Chinese names became `???` and never matched the translation map.
Fix: `rewriter.py` uses the same encoding-detection reader as `bom.py`. File is read as GBK when UTF-8 produces replacement chars.
**Problem 3: Only first PRODUCT name field was replaced**
ISO 10303-21 `PRODUCT` entity format: `#N = PRODUCT('id', 'name', 'description', (#...))`. Both the first and second quoted strings carry the part name. CAD viewers (including OpenCASCADE CAD Assistant) display the second field.
Fix: Updated `PRODUCT_PATTERN` regex to capture both fields with 5 groups. Replacement writes the translated name into both positions.
### Translation API Model
Current model: `claude-haiku-4-5-20251001`
Previous value `claude-sonnet-4-20250514` was returning 404 — updated in `translator.py`.
### BOM Excel Library
`openpyxl` must be installed in the venv. The skill falls back to CSV silently if it's missing — don't rely on this fallback, install it explicitly.
---
## Prerequisites for a New macOS Computer
### Hardware Requirements
- Apple Silicon Mac (M1/M2/M3/M4) — all wheels are native arm64
- **No Rosetta required**
- macOS 13 Sequoia or later recommended
### Software Stack
```
Python 3.103.12 (3.13 works but less tested; 3.9 too old)
Homebrew (for cairo system libs)
~/step-processor-env (Python venv — all pip packages go here)
```
### Python Packages (pip install in venv)
```bash
pip install cadquery-ocp # OCC kernel, native arm64, ~300MB
pip install build123d # STEP loader, primary
pip install trimesh pyrender # thumbnail rendering pipeline
pip install Pillow numpy pandas # image and data processing
pip install anthropic # Claude API client
pip install openpyxl # Excel BOM output — REQUIRED
pip install cairosvg # SVG→PNG/PDF for diagrams (optional, diagrams only)
```
### System Libraries (Homebrew)
```bash
brew install cairo pango gdk-pixbuf libffi # required for cairosvg
```
### FreeCAD Fallback (Optional)
Only needed if build123d fails on a specific file.
- Download official arm64 `FreeCAD.app` from https://github.com/FreeCAD/FreeCAD/releases/latest
- Drag to `/Applications`, launch once so macOS approves it
- Verify: `/Applications/FreeCAD.app/Contents/Resources/bin/freecadcmd --version`
> **macOS 15 Sequoia:** conda-forge FreeCAD is killed by Gatekeeper. Use the official signed `.app` only.
### Anthropic API Key
The key must be available in the environment where the processor runs.
**For interactive terminal use:**
```bash
echo 'export ANTHROPIC_API_KEY="sk-ant-YOUR-KEY"' >> ~/.zshrc
source ~/.zshrc
```
**For Desktop Commander / CoWork (non-interactive shell):**
```bash
echo 'export ANTHROPIC_API_KEY="sk-ant-YOUR-KEY"' >> ~/.zshenv
```
`~/.zshrc` is only sourced in interactive shells. Desktop Commander spawns non-interactive zsh — it reads `~/.zshenv` instead. **Both files should have the key.**
---
## Setup — Fast Path (New Mac)
```bash
# 1. Create venv
python3 -m venv ~/step-processor-env
source ~/step-processor-env/bin/activate
# 2. Install all packages
pip install --upgrade pip
pip install cadquery-ocp build123d trimesh pyrender Pillow numpy pandas anthropic openpyxl
# 3. System libs for diagram export
brew install cairo pango gdk-pixbuf libffi
pip install cairosvg
# 4. API key (do BOTH for interactive + Desktop Commander)
echo 'export ANTHROPIC_API_KEY="sk-ant-YOUR-KEY"' >> ~/.zshrc
echo 'export ANTHROPIC_API_KEY="sk-ant-YOUR-KEY"' >> ~/.zshenv
source ~/.zshrc
# 5. Verify
python -c "import build123d, openpyxl, anthropic, trimesh; print('ALL OK')"
# 6. Test run
cd /path/to/step-skill-folder
python step_processor.py yourfile.step --no-thumbnails --verbose
```
Expected output for Chinese STEP file:
```
INFO [build123d] Loaded: yourfile.step | NNNN faces | NN parts
INFO STEP text parser found NN unique part names
INFO BOM extracted: NN parts
INFO BOM XLSX → yourfile_bom.xlsx
INFO Chinese part names detected — auto-translating
INFO API returned NN translations
INFO _EN.step written: yourfile_EN.step (NN labels replaced)
```
---
## File Structure
```
STEP File Skill/
step_processor.py ← CLI entry point
modules/
__init__.py
loader.py ← build123d load + GBK mojibake helper
bom.py ← STEP text parser + MPM-branded xlsx output
renderer.py ← 6-view PNG thumbnails (pyrender)
translator.py ← Claude API translation (claude-haiku-4-5-20251001)
rewriter.py ← _EN.step writer (GBK-aware, both PRODUCT fields)
query_engine.py ← Natural language geometry queries + REPL
external_diagram.py ← Dimensional diagram generator
schemas/
external_diagram_schema.json
parts_mapping_schema.json
templates/
datablock_template.md
SKILL.md ← Claude skill instructions
INSTALL.md ← Library setup details
SETUP_CHECKLIST.md ← Step-by-step setup + progressive tests
COWORK_CONTEXT.md ← This file
```
---
## CLI Reference
```bash
# Activate environment first (every new terminal session)
source ~/step-processor-env/bin/activate
cd /path/to/STEP\ File\ Skill
# Default: thumbnails + BOM + auto-translate if Chinese detected
python step_processor.py enclosure.step
# BOM only (no thumbnails, no translate)
python step_processor.py enclosure.step --no-thumbnails --no-translate
# Force translation even if auto-detect is off
python step_processor.py enclosure.step --translate
# Single geometric query and exit
python step_processor.py enclosure.step --query "list all mounting holes"
# Interactive geometry REPL
python step_processor.py enclosure.step --repl
# External dimensional diagram
python step_processor.py enclosure.step --diagram
python step_processor.py enclosure.step --diagram --diagram-pdf
python step_processor.py enclosure.step --diagram --diagram-mode enclosure_plus_mounting
# Verbose (shows backend selection, timing, fallback notices)
python step_processor.py enclosure.step --verbose
```
---
## Known Issues and Pending Work
### BOM bbox Enrichment (partial)
Rows beyond the first only get 5.0 × 5.0 × 5.0mm placeholder bounding boxes. The OCC child enumeration only retrieves the top-level shape's children correctly. Root cause: build123d's `.children` on compound shapes doesn't walk sub-assemblies for bbox. Fix: map part names from STEP text parser back to OCC children by label — non-trivial due to OCC label mangling on CJK files.
### Mounting Hole Filter — Minimum Diameter
The query engine's mounting hole detector returns PCB vias (0.4mm diameter) alongside actual mounting holes. Needs a minimum diameter threshold (recommend 2.0mm floor). Pending update to `query_engine.py`.
### Phase 7 Full Test
The SETUP_CHECKLIST.md Phase 7 tests have been validated for Tests 1, 2, 4 (BOM, translation, rewrite). Tests 3 (thumbnails), 5 (REPL), 6/7 (diagrams) not yet re-run post GBK fix. Thumbnails were verified in an earlier session; diagram code is scaffolded but output quality against production files not fully validated.
### Conda freecad_env Cleanup
From a prior session: `conda activate freecad_env && conda deactivate && conda env remove -n freecad_env`. The conda FreeCAD approach was abandoned in favor of the signed FreeCAD.app. This env is dead weight on the local machine.
---
## Library Stack Decision Log
| Decision | Choice | Reason |
|----------|--------|--------|
| CAD kernel | build123d | Native arm64 arm; clean API; same OCC as existing viewer |
| Fallback CAD | FreeCAD.app (signed) | conda-forge builds killed by Gatekeeper on Sequoia |
| Translation | Claude Haiku API | Batched, manufacturing-context prompted, flags ambiguity |
| Diagrams | SVG-first + cairosvg | No GUI dependency; vector quality; cairosvg handles PNG/PDF |
| Excel | openpyxl | MPM brand formatting, column control, frozen panes |
| Rejected | pythonocc | Rosetta/x64 conda dependency — non-starter on Apple Silicon |
| Rejected | conda-forge FreeCAD | Unsigned binaries killed by Gatekeeper on macOS 15 |
---
## Planned Integration Points
- **Odoo V18 MRP**: Model number from `meta.json` triggers lookup of weight, BOM cost, stock status
- **CoWork product docs**: `meta.json` feeds product data cards; diagram PNGs embed in Knowledge Base articles
- **RDMC** (`rdmc.messagepoint.tv`): Thumbnail PNGs for display inventory visual reference
- **OnSign.tv**: Enclosure dimensions for content sizing reference
+133
View File
@@ -0,0 +1,133 @@
# INSTALL.md — STEP Processor Setup (Apple Silicon / macOS arm64)
No Rosetta. No x64 sub-environments. Both libraries run natively.
---
## 1. build123d (Primary — native arm64, pip-based)
build123d depends on `cadquery-ocp`, which ships native arm64 wheels
via GitHub Releases (not yet on PyPI, but stable).
### Step 1 — Install the arm64 OCP wheel
Go to: https://github.com/CadQuery/OCP/releases
Download the wheel matching your Python version, e.g.:
`cadquery_ocp-7.x.x-cp311-cp311-macosx_12_0_arm64.whl`
Install it:
```bash
pip install /path/to/cadquery_ocp-*.whl
```
### Step 2 — Install build123d and rendering stack
```bash
pip install build123d
pip install trimesh pyrender Pillow numpy pandas anthropic
```
### Step 3 — Set API key
```bash
export ANTHROPIC_API_KEY=sk-ant-...
# Add to ~/.zshrc to persist
```
### Step 4 — Verify
```bash
python -c "import build123d; print('build123d OK')"
python -c "import trimesh; print('trimesh OK')"
python -c "import pyrender; print('pyrender OK')"
```
---
## 2. FreeCAD Headless (Fallback — native arm64 via conda-forge)
FreeCAD has been available as a native macOS-arm64 conda-forge package
since late 2024. Install it in its own conda environment to avoid
dependency conflicts with build123d.
### Step 1 — Install Miniforge (arm64) if not already present
Download from: https://github.com/conda-forge/miniforge/releases
Select: `Miniforge3-MacOSX-arm64.sh`
```bash
bash Miniforge3-MacOSX-arm64.sh
```
### Step 2 — Create FreeCAD environment
```bash
conda create -n freecad_env python=3.11
conda activate freecad_env
conda install -c conda-forge freecad
```
### Step 3 — Verify headless operation
```bash
FreeCADCmd --version
# or
python -c "import FreeCAD; import Part; print('FreeCAD headless OK')"
```
**Important:** Never import `FreeCADGui` in headless scripts — it will crash.
### Step 4 — Make FreeCAD importable from your build123d environment
Add the FreeCAD conda env's site-packages to your PATH, or run scripts
via the conda freecad_env directly:
```bash
conda run -n freecad_env python step_processor.py myfile.step
```
---
## Quick Test
Once either library is installed:
```bash
# Test with a sample STEP file
python step_processor.py sample.step --verbose
# Test query REPL
python step_processor.py sample.step --repl
```
---
## Dependency Summary
| Package | Purpose | Required for |
|---------------|--------------------------------|---------------------|
| build123d | STEP loading + geometry | Primary path |
| cadquery-ocp | OCC kernel (arm64 wheel) | build123d |
| trimesh | Mesh handling for rendering | PNG thumbnails |
| pyrender | Offscreen rendering | PNG thumbnails |
| Pillow | PNG file writing | PNG thumbnails |
| numpy | Geometry math | Both paths |
| pandas | BOM CSV output | BOM export |
| anthropic | Claude API translation | Translation |
| FreeCAD | Fallback STEP loading | Fallback path only |
---
## 3. cairosvg (required for PNG and PDF export from diagrams)
```bash
pip install cairosvg
# On macOS, may also need:
brew install cairo pango gdk-pixbuf libffi
```
Verify:
```bash
python -c "import cairosvg; print('cairosvg OK')"
```
File diff suppressed because one or more lines are too long
+29
View File
@@ -0,0 +1,29 @@
part_number,part_name_original,part_name_english,quantity,level,parent,bbox_x_mm,bbox_y_mm,bbox_z_mm,notes
001,"15.6"" Rear Cover","15.6"" Rear Cover",1,0,,248.6,459.2,40.0,parsed from STEP text
002,Remote Control + Light Sensor Board,Remote Control + Light Sensor Board,1,0,,242.3,392.9,6.0,parsed from STEP text
003,J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step,J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step,1,0,,215.9,363.8,8.8,parsed from STEP text
004,"15.6"" Tempered Glass 3+3 Laminated","15.6"" Tempered Glass 3+3 Laminated",1,0,,241.2,451.8,21.5,parsed from STEP text
005,M3x6 Countersunk Screw,M3x6 Countersunk Screw,1,0,,238.2,448.8,21.0,parsed from STEP text
006,DZ-LP0632 Light Sensor Control Board,DZ-LP0632 Light Sensor Control Board,1,0,,130.5,10.0,20.0,parsed from STEP text
007,J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc.step,J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc.step,2,0,,33.82,28.2,19.0,parsed from STEP text
008,3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07,3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07,1,0,,48.37,103.79,16.25,parsed from STEP text
009,TOSN-DY398P-EMC Sub-board,TOSN-DY398P-EMC Sub-board,1,0,,92.0,21.0,17.7,parsed from STEP text
010,TOSN-AD120P12V10A-120W,TOSN-AD120P12V10A-120W,1,0,,33.0,73.0,14.7,parsed from STEP text
011,2P Phoenix Terminal,2P Phoenix Terminal,1,0,,33.0,73.0,14.7,parsed from STEP text
012,HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step,HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step,1,0,,30.0,120.0,11.6,parsed from STEP text
013,G156HAN02.0--20221031_G156HAN02.0_PSpec_DBEST,G156HAN02.0--20221031_G156HAN02.0_PSpec_DBEST,1,0,,5.0,5.0,5.0,parsed from STEP text
014,3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07.step,3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07.step,1,0,,5.0,5.0,5.0,parsed from STEP text
015,Small Glass Lens,Small Glass Lens,1,0,,5.0,5.0,5.0,parsed from STEP text
016,Board~a9jc.step,Board~a9jc.step,1,0,,5.0,5.0,5.0,parsed from STEP text
017,J7~XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step,J7~XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step,1,0,,5.0,5.0,5.0,parsed from STEP text
018,"15.6"" Terminal Board","15.6"" Terminal Board",1,0,,5.0,5.0,5.0,parsed from STEP text
019,XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step,XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step,1,0,,5.0,5.0,5.0,parsed from STEP text
020,Lower Aluminum Plate,Lower Aluminum Plate,1,0,,5.0,5.0,5.0,parsed from STEP text
021,Upper Aluminum Plate,Upper Aluminum Plate,1,0,,5.0,5.0,5.0,parsed from STEP text
022,BSCZ-TX3361,BSCZ-TX3361,1,0,,5.0,5.0,5.0,parsed from STEP text
023,BNC-1~BNC-TH_BNC-50KWYE~a9jc.step,BNC-1~BNC-TH_BNC-50KWYE~a9jc.step,1,0,,5.0,5.0,5.0,parsed from STEP text
024,"15.6"" Mounting Plate","15.6"" Mounting Plate",1,0,,5.0,5.0,5.0,parsed from STEP text
025,"15.6"" Light Sensor Bracket","15.6"" Light Sensor Bracket",1,0,,5.0,5.0,5.0,parsed from STEP text
026,"15.6"" High-Speed Rail Display Assembly Drawing","15.6"" High-Speed Rail Display Assembly Drawing",1,0,,5.0,5.0,5.0,parsed from STEP text
027,"15.6"" Aluminum Frame","15.6"" Aluminum Frame",1,0,,5.0,5.0,5.0,parsed from STEP text
028,8Ω 5W Speaker (Model 3070),8Ω 5W Speaker (Model 3070),1,0,,5.0,5.0,5.0,parsed from STEP text
1 part_number part_name_original part_name_english quantity level parent bbox_x_mm bbox_y_mm bbox_z_mm notes
2 001 15.6" Rear Cover 15.6" Rear Cover 1 0 248.6 459.2 40.0 parsed from STEP text
3 002 Remote Control + Light Sensor Board Remote Control + Light Sensor Board 1 0 242.3 392.9 6.0 parsed from STEP text
4 003 J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step 1 0 215.9 363.8 8.8 parsed from STEP text
5 004 15.6" Tempered Glass 3+3 Laminated 15.6" Tempered Glass 3+3 Laminated 1 0 241.2 451.8 21.5 parsed from STEP text
6 005 M3x6 Countersunk Screw M3x6 Countersunk Screw 1 0 238.2 448.8 21.0 parsed from STEP text
7 006 DZ-LP0632 Light Sensor Control Board DZ-LP0632 Light Sensor Control Board 1 0 130.5 10.0 20.0 parsed from STEP text
8 007 J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc.step J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc.step 2 0 33.82 28.2 19.0 parsed from STEP text
9 008 3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07 3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07 1 0 48.37 103.79 16.25 parsed from STEP text
10 009 TOSN-DY398P-EMC Sub-board TOSN-DY398P-EMC Sub-board 1 0 92.0 21.0 17.7 parsed from STEP text
11 010 TOSN-AD120P12V10A-120W TOSN-AD120P12V10A-120W 1 0 33.0 73.0 14.7 parsed from STEP text
12 011 2P Phoenix Terminal 2P Phoenix Terminal 1 0 33.0 73.0 14.7 parsed from STEP text
13 012 HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step 1 0 30.0 120.0 11.6 parsed from STEP text
14 013 G156HAN02.0--20221031_G156HAN02.0_PSpec_DBEST G156HAN02.0--20221031_G156HAN02.0_PSpec_DBEST 1 0 5.0 5.0 5.0 parsed from STEP text
15 014 3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07.step 3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07.step 1 0 5.0 5.0 5.0 parsed from STEP text
16 015 Small Glass Lens Small Glass Lens 1 0 5.0 5.0 5.0 parsed from STEP text
17 016 Board~a9jc.step Board~a9jc.step 1 0 5.0 5.0 5.0 parsed from STEP text
18 017 J7~XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step J7~XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step 1 0 5.0 5.0 5.0 parsed from STEP text
19 018 15.6" Terminal Board 15.6" Terminal Board 1 0 5.0 5.0 5.0 parsed from STEP text
20 019 XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step 1 0 5.0 5.0 5.0 parsed from STEP text
21 020 Lower Aluminum Plate Lower Aluminum Plate 1 0 5.0 5.0 5.0 parsed from STEP text
22 021 Upper Aluminum Plate Upper Aluminum Plate 1 0 5.0 5.0 5.0 parsed from STEP text
23 022 BSCZ-TX3361 BSCZ-TX3361 1 0 5.0 5.0 5.0 parsed from STEP text
24 023 BNC-1~BNC-TH_BNC-50KWYE~a9jc.step BNC-1~BNC-TH_BNC-50KWYE~a9jc.step 1 0 5.0 5.0 5.0 parsed from STEP text
25 024 15.6" Mounting Plate 15.6" Mounting Plate 1 0 5.0 5.0 5.0 parsed from STEP text
26 025 15.6" Light Sensor Bracket 15.6" Light Sensor Bracket 1 0 5.0 5.0 5.0 parsed from STEP text
27 026 15.6" High-Speed Rail Display Assembly Drawing 15.6" High-Speed Rail Display Assembly Drawing 1 0 5.0 5.0 5.0 parsed from STEP text
28 027 15.6" Aluminum Frame 15.6" Aluminum Frame 1 0 5.0 5.0 5.0 parsed from STEP text
29 028 8Ω 5W Speaker (Model 3070) 8Ω 5W Speaker (Model 3070) 1 0 5.0 5.0 5.0 parsed from STEP text
Binary file not shown.
+29
View File
@@ -0,0 +1,29 @@
part_number,part_name_original,part_name_english,quantity,level,parent,bbox_x_mm,bbox_y_mm,bbox_z_mm,notes
001,15.6寸后盖,"15.6"" Rear Cover",1,0,,248.6,459.2,40.0,parsed from STEP text; machine-translated
002,遥控+光感板,Remote Control + Light Sensor Board,1,0,,242.3,392.9,6.0,parsed from STEP text; machine-translated
003,J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step,J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step,1,0,,215.9,363.8,8.8,parsed from STEP text
004,15.6寸钢化玻璃3+3夹胶,"15.6"" Tempered Glass 3+3 Laminated",1,0,,241.2,451.8,21.5,parsed from STEP text; machine-translated
005,M3x6沉头螺丝,M3x6 Countersunk Screw,1,0,,238.2,448.8,21.0,parsed from STEP text; machine-translated
006,DZ-LP0632感光控制板,DZ-LP0632 Light Sensor Control Board,1,0,,130.5,10.0,20.0,parsed from STEP text; machine-translated
007,J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc.step,J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc.step,2,0,,33.82,28.2,19.0,parsed from STEP text
008,3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07,3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07,1,0,,48.37,103.79,16.25,parsed from STEP text
009,TOSN-DY398P-EMC小板,TOSN-DY398P-EMC Sub-board,1,0,,92.0,21.0,17.7,parsed from STEP text; machine-translated
010,TOSN-AD120P12V10A-120W,TOSN-AD120P12V10A-120W,1,0,,33.0,73.0,14.7,parsed from STEP text
011,2P凤凰端子,2P Phoenix Terminal,1,0,,33.0,73.0,14.7,parsed from STEP text; machine-translated
012,HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step,HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step,1,0,,30.0,120.0,11.6,parsed from STEP text
013,G156HAN02.0--20221031_G156HAN02.0_PSpec_DBEST,G156HAN02.0--20221031_G156HAN02.0_PSpec_DBEST,1,0,,5.0,5.0,5.0,parsed from STEP text
014,3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07.step,3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07.step,1,0,,5.0,5.0,5.0,parsed from STEP text
015,小玻璃镜片,Small Glass Lens,1,0,,5.0,5.0,5.0,parsed from STEP text; machine-translated
016,Board~a9jc.step,Board~a9jc.step,1,0,,5.0,5.0,5.0,parsed from STEP text
017,J7~XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step,J7~XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step,1,0,,5.0,5.0,5.0,parsed from STEP text
018,15.6寸端子板,"15.6"" Terminal Board",1,0,,5.0,5.0,5.0,parsed from STEP text; machine-translated
019,XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step,XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step,1,0,,5.0,5.0,5.0,parsed from STEP text
020,下铝板,Lower Aluminum Plate,1,0,,5.0,5.0,5.0,parsed from STEP text; machine-translated
021,上铝板,Upper Aluminum Plate,1,0,,5.0,5.0,5.0,parsed from STEP text; machine-translated
022,BSCZ-TX3361,BSCZ-TX3361,1,0,,5.0,5.0,5.0,parsed from STEP text
023,BNC-1~BNC-TH_BNC-50KWYE~a9jc.step,BNC-1~BNC-TH_BNC-50KWYE~a9jc.step,1,0,,5.0,5.0,5.0,parsed from STEP text
024,15.6寸安装板,"15.6"" Mounting Plate",1,0,,5.0,5.0,5.0,parsed from STEP text; machine-translated
025,15.6寸光感支架,"15.6"" Light Sensor Bracket",1,0,,5.0,5.0,5.0,parsed from STEP text; machine-translated
026,15.6寸高铁显示屏总装图,"15.6"" High-Speed Rail Display Assembly Drawing",1,0,,5.0,5.0,5.0,parsed from STEP text; machine-translated
027,15.6寸铝框,"15.6"" Aluminum Frame",1,0,,5.0,5.0,5.0,parsed from STEP text; machine-translated
028,8欧5W喇叭(3070款),8Ω 5W Speaker (Model 3070),1,0,,5.0,5.0,5.0,parsed from STEP text; machine-translated
1 part_number part_name_original part_name_english quantity level parent bbox_x_mm bbox_y_mm bbox_z_mm notes
2 001 15.6寸后盖 15.6" Rear Cover 1 0 248.6 459.2 40.0 parsed from STEP text; machine-translated
3 002 遥控+光感板 Remote Control + Light Sensor Board 1 0 242.3 392.9 6.0 parsed from STEP text; machine-translated
4 003 J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step 1 0 215.9 363.8 8.8 parsed from STEP text
5 004 15.6寸钢化玻璃3+3夹胶 15.6" Tempered Glass 3+3 Laminated 1 0 241.2 451.8 21.5 parsed from STEP text; machine-translated
6 005 M3x6沉头螺丝 M3x6 Countersunk Screw 1 0 238.2 448.8 21.0 parsed from STEP text; machine-translated
7 006 DZ-LP0632感光控制板 DZ-LP0632 Light Sensor Control Board 1 0 130.5 10.0 20.0 parsed from STEP text; machine-translated
8 007 J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc.step J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc.step 2 0 33.82 28.2 19.0 parsed from STEP text
9 008 3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07 3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07 1 0 48.37 103.79 16.25 parsed from STEP text
10 009 TOSN-DY398P-EMC小板 TOSN-DY398P-EMC Sub-board 1 0 92.0 21.0 17.7 parsed from STEP text; machine-translated
11 010 TOSN-AD120P12V10A-120W TOSN-AD120P12V10A-120W 1 0 33.0 73.0 14.7 parsed from STEP text
12 011 2P凤凰端子 2P Phoenix Terminal 1 0 33.0 73.0 14.7 parsed from STEP text; machine-translated
13 012 HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step 1 0 30.0 120.0 11.6 parsed from STEP text
14 013 G156HAN02.0--20221031_G156HAN02.0_PSpec_DBEST G156HAN02.0--20221031_G156HAN02.0_PSpec_DBEST 1 0 5.0 5.0 5.0 parsed from STEP text
15 014 3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07.step 3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07.step 1 0 5.0 5.0 5.0 parsed from STEP text
16 015 小玻璃镜片 Small Glass Lens 1 0 5.0 5.0 5.0 parsed from STEP text; machine-translated
17 016 Board~a9jc.step Board~a9jc.step 1 0 5.0 5.0 5.0 parsed from STEP text
18 017 J7~XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step J7~XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step 1 0 5.0 5.0 5.0 parsed from STEP text
19 018 15.6寸端子板 15.6" Terminal Board 1 0 5.0 5.0 5.0 parsed from STEP text; machine-translated
20 019 XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step 1 0 5.0 5.0 5.0 parsed from STEP text
21 020 下铝板 Lower Aluminum Plate 1 0 5.0 5.0 5.0 parsed from STEP text; machine-translated
22 021 上铝板 Upper Aluminum Plate 1 0 5.0 5.0 5.0 parsed from STEP text; machine-translated
23 022 BSCZ-TX3361 BSCZ-TX3361 1 0 5.0 5.0 5.0 parsed from STEP text
24 023 BNC-1~BNC-TH_BNC-50KWYE~a9jc.step BNC-1~BNC-TH_BNC-50KWYE~a9jc.step 1 0 5.0 5.0 5.0 parsed from STEP text
25 024 15.6寸安装板 15.6" Mounting Plate 1 0 5.0 5.0 5.0 parsed from STEP text; machine-translated
26 025 15.6寸光感支架 15.6" Light Sensor Bracket 1 0 5.0 5.0 5.0 parsed from STEP text; machine-translated
27 026 15.6寸高铁显示屏总装图 15.6" High-Speed Rail Display Assembly Drawing 1 0 5.0 5.0 5.0 parsed from STEP text; machine-translated
28 027 15.6寸铝框 15.6" Aluminum Frame 1 0 5.0 5.0 5.0 parsed from STEP text; machine-translated
29 028 8欧5W喇叭(3070款) 8Ω 5W Speaker (Model 3070) 1 0 5.0 5.0 5.0 parsed from STEP text; machine-translated
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.
+32
View File
@@ -0,0 +1,32 @@
part_number,part_name_original,part_name_english,quantity,level,parent,bbox_x_mm,bbox_y_mm,bbox_z_mm,notes
001,TOSN-DY398P-EMC小板,TOSN-DY398P-EMC Small PCB Board,1,0,,397.2,718.6,40.0,parsed from STEP text; machine-translated
002,BNC-1~BNC-TH_BNC-50KWYE~a9jc.step,BNC-1~BNC-TH_BNC-50KWYE~a9jc.step,1,0,,390.9,652.3,6.0,parsed from STEP text
003,光感支架,Light Sensor Bracket,1,0,,362.1,623.7,12.1,parsed from STEP text; machine-translated
004,2P凤凰端子,2P Phoenix Connector Terminal,1,0,,389.8,711.2,23.1,parsed from STEP text; machine-translated
005,3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07.step,3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07.step,1,0,,33.82,28.2,19.0,parsed from STEP text
006,J7~XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step,J7~XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step,1,0,,48.37,103.79,16.25,parsed from STEP text
007,3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07,3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07,1,0,,110.0,49.0,8.6,parsed from STEP text
008,DZ-LP6608 REV1.0,DZ-LP6608 REV1.0,1,0,,102.0,50.0,16.0,parsed from STEP text
009,P270HVN03.0,P270HVN03.0,1,0,,386.0,483.0,22.6,parsed from STEP text
010,Board~a9jc.step,Board~a9jc.step,1,0,,152.0,10.0,21.6,parsed from STEP text
011,BSCZ-TX3361,BSCZ-TX3361,1,0,,33.0,73.0,14.7,parsed from STEP text
012,J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc.step,J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc.step,2,0,,33.0,73.0,14.7,parsed from STEP text
013,27寸高铁显示屏总装图,27-inch High-speed Rail Display Screen Assembly Drawing,1,0,,45.0,160.0,11.6,parsed from STEP text; machine-translated
014,HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step,HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step,1,0,,92.0,21.0,17.1,parsed from STEP text
015,8欧5W喇叭(3070款),8Ω 5W Speaker (Model 3070),1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
016,27寸屏压件1,27-inch Screen Clamp 1,1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
017,27寸上铝板,27-inch Upper Aluminum Panel,1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
018,小玻璃镜片,Small Glass Mirror Lens,1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
019,钢化玻璃3+3夹胶,Tempered Glass 3+3 Laminated,1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
020,27寸屏压件2,27-inch Screen Clamp 2,1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
021,遥控+光感板,Remote Control + Light Sensor Board,1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
022,屏压件,Screen Clamp,1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
023,27寸下铝板,27-inch Lower Aluminum Panel,1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
024,后盖,Rear Cover,1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
025,安装板,Mounting Plate,1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
026,M3x6沉头螺丝,M3x6 Countersunk Screw,1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
027,J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step,J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step,1,0,,6.0,6.0,5.0,parsed from STEP text
028,端子板,Terminal Board,1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
029,27寸铝框,27-inch Aluminum Frame,1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
030,XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step,XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step,1,0,,6.0,6.0,5.0,parsed from STEP text
031,TOSN-AD120P12V10A-120W,TOSN-AD120P12V10A-120W,1,0,,6.0,6.0,5.0,parsed from STEP text
1 part_number part_name_original part_name_english quantity level parent bbox_x_mm bbox_y_mm bbox_z_mm notes
2 001 TOSN-DY398P-EMC小板 TOSN-DY398P-EMC Small PCB Board 1 0 397.2 718.6 40.0 parsed from STEP text; machine-translated
3 002 BNC-1~BNC-TH_BNC-50KWYE~a9jc.step BNC-1~BNC-TH_BNC-50KWYE~a9jc.step 1 0 390.9 652.3 6.0 parsed from STEP text
4 003 光感支架 Light Sensor Bracket 1 0 362.1 623.7 12.1 parsed from STEP text; machine-translated
5 004 2P凤凰端子 2P Phoenix Connector Terminal 1 0 389.8 711.2 23.1 parsed from STEP text; machine-translated
6 005 3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07.step 3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07.step 1 0 33.82 28.2 19.0 parsed from STEP text
7 006 J7~XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step J7~XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step 1 0 48.37 103.79 16.25 parsed from STEP text
8 007 3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07 3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07 1 0 110.0 49.0 8.6 parsed from STEP text
9 008 DZ-LP6608 REV1.0 DZ-LP6608 REV1.0 1 0 102.0 50.0 16.0 parsed from STEP text
10 009 P270HVN03.0 P270HVN03.0 1 0 386.0 483.0 22.6 parsed from STEP text
11 010 Board~a9jc.step Board~a9jc.step 1 0 152.0 10.0 21.6 parsed from STEP text
12 011 BSCZ-TX3361 BSCZ-TX3361 1 0 33.0 73.0 14.7 parsed from STEP text
13 012 J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc.step J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc.step 2 0 33.0 73.0 14.7 parsed from STEP text
14 013 27寸高铁显示屏总装图 27-inch High-speed Rail Display Screen Assembly Drawing 1 0 45.0 160.0 11.6 parsed from STEP text; machine-translated
15 014 HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step 1 0 92.0 21.0 17.1 parsed from STEP text
16 015 8欧5W喇叭(3070款) 8Ω 5W Speaker (Model 3070) 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
17 016 27寸屏压件1 27-inch Screen Clamp 1 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
18 017 27寸上铝板 27-inch Upper Aluminum Panel 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
19 018 小玻璃镜片 Small Glass Mirror Lens 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
20 019 钢化玻璃3+3夹胶 Tempered Glass 3+3 Laminated 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
21 020 27寸屏压件2 27-inch Screen Clamp 2 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
22 021 遥控+光感板 Remote Control + Light Sensor Board 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
23 022 屏压件 Screen Clamp 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
24 023 27寸下铝板 27-inch Lower Aluminum Panel 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
25 024 后盖 Rear Cover 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
26 025 安装板 Mounting Plate 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
27 026 M3x6沉头螺丝 M3x6 Countersunk Screw 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
28 027 J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step 1 0 6.0 6.0 5.0 parsed from STEP text
29 028 端子板 Terminal Board 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
30 029 27寸铝框 27-inch Aluminum Frame 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
31 030 XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step 1 0 6.0 6.0 5.0 parsed from STEP text
32 031 TOSN-AD120P12V10A-120W TOSN-AD120P12V10A-120W 1 0 6.0 6.0 5.0 parsed from STEP text
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.
+32
View File
@@ -0,0 +1,32 @@
part_number,part_name_original,part_name_english,quantity,level,parent,bbox_x_mm,bbox_y_mm,bbox_z_mm,notes
001,J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc.step,J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc.step,2,0,,826.0,192.0,48.0,parsed from STEP text
002,小玻璃镜片,Small Glass Lens,1,0,,39.85,185.7,9.0,parsed from STEP text; machine-translated
003,8欧5W喇叭(3070款),8Ω 5W Speaker (Model 3070),1,0,,39.85,185.7,9.0,parsed from STEP text; machine-translated
004,TOSN-AD120P12V10A-120W,TOSN-AD120P12V10A-120W,1,0,,759.6,185.7,6.0,parsed from STEP text
005,3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07,3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07,1,0,,821.4,187.4,23.1,parsed from STEP text
006,28寸屏压件,"28"" Screen Pressure Clip",1,0,,12.0,28.0,1.1,parsed from STEP text; machine-translated
007,28寸端子板,"28"" Terminal Board",1,0,,618.0,183.6,24.1,parsed from STEP text; machine-translated
008,3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07.step,3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07.step,1,0,,12.3,65.0,14.0,parsed from STEP text
009,XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step,XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step,1,0,,21.0,92.0,19.2,parsed from STEP text
010,TOSN-DY398P-EMC小板,TOSN-DY398P-EMC Subboard,1,0,,10.0,152.0,21.6,parsed from STEP text; machine-translated
011,HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step,HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step,1,0,,28.2,33.82,19.0,parsed from STEP text
012,玻璃3+3,Glass 3+3,1,0,,103.79,48.37,16.25,parsed from STEP text; machine-translated
013,BSCZ-TX3361,BSCZ-TX3361,1,0,,49.0,110.0,8.6,parsed from STEP text
014,28寸后盖,"28"" Rear Cover",1,0,,102.0,50.0,16.0,parsed from STEP text; machine-translated
015,28寸下铝板,"28"" Lower Aluminum Plate",1,0,,50.0,40.0,12.6,parsed from STEP text; machine-translated
016,28寸高铁显示屏总装图,"28"" High-speed Rail Display Screen Assembly Drawing",1,0,,73.0,33.0,14.7,parsed from STEP text; machine-translated
017,Board~a9jc.step,Board~a9jc.step,1,0,,73.0,33.0,14.7,parsed from STEP text
018,jgj-hy0280HD03模组,jgj-hy0280HD03 Module,1,0,,95.0,42.0,8.5,parsed from STEP text; machine-translated
019,遥控+光感板,Remote Control + Light Sensor Board,1,0,,59.0,79.0,39.2,parsed from STEP text; machine-translated
020,2P凤凰端子,2P Phoenix Terminal,1,0,,29.0,21.0,29.7,parsed from STEP text; machine-translated
021,BNC-1~BNC-TH_BNC-50KWYE~a9jc.step,BNC-1~BNC-TH_BNC-50KWYE~a9jc.step,1,0,,29.0,21.0,24.9,parsed from STEP text
022,J7~XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step,J7~XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step,1,0,,722.0,154.6,16.8,parsed from STEP text
023,28寸恒流板,"28"" Constant Current Board",1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
024,28寸条屏铝框,"28"" Strip Screen Aluminum Frame",1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
025,28寸屏压件2,"28"" Screen Pressure Clip 2",1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
026,M3x6沉头螺丝,M3x6 Countersunk Screw,1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
027,28寸上铝板,"28"" Upper Aluminum Plate",1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
028,28寸屏压件1,"28"" Screen Pressure Clip 1",1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
029,28寸光感支架,"28"" Light Sensor Bracket",1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
030,28寸安装板,"28"" Mounting Plate",1,0,,6.0,6.0,5.0,parsed from STEP text; machine-translated
031,J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step,J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step,1,0,,6.0,6.0,5.0,parsed from STEP text
1 part_number part_name_original part_name_english quantity level parent bbox_x_mm bbox_y_mm bbox_z_mm notes
2 001 J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc.step J4~BNC-1~BNC-TH_BNC-50KWYE~a9jc.step 2 0 826.0 192.0 48.0 parsed from STEP text
3 002 小玻璃镜片 Small Glass Lens 1 0 39.85 185.7 9.0 parsed from STEP text; machine-translated
4 003 8欧5W喇叭(3070款) 8Ω 5W Speaker (Model 3070) 1 0 39.85 185.7 9.0 parsed from STEP text; machine-translated
5 004 TOSN-AD120P12V10A-120W TOSN-AD120P12V10A-120W 1 0 759.6 185.7 6.0 parsed from STEP text
6 005 3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07 3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07 1 0 821.4 187.4 23.1 parsed from STEP text
7 006 28寸屏压件 28" Screen Pressure Clip 1 0 12.0 28.0 1.1 parsed from STEP text; machine-translated
8 007 28寸端子板 28" Terminal Board 1 0 618.0 183.6 24.1 parsed from STEP text; machine-translated
9 008 3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07.step 3D_YZ-006-V3 HDMI TO 2SDI_2025-11-07.step 1 0 12.3 65.0 14.0 parsed from STEP text
10 009 XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step 1 0 21.0 92.0 19.2 parsed from STEP text
11 010 TOSN-DY398P-EMC小板 TOSN-DY398P-EMC Subboard 1 0 10.0 152.0 21.6 parsed from STEP text; machine-translated
12 011 HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step 1 0 28.2 33.82 19.0 parsed from STEP text
13 012 玻璃3+3 Glass 3+3 1 0 103.79 48.37 16.25 parsed from STEP text; machine-translated
14 013 BSCZ-TX3361 BSCZ-TX3361 1 0 49.0 110.0 8.6 parsed from STEP text
15 014 28寸后盖 28" Rear Cover 1 0 102.0 50.0 16.0 parsed from STEP text; machine-translated
16 015 28寸下铝板 28" Lower Aluminum Plate 1 0 50.0 40.0 12.6 parsed from STEP text; machine-translated
17 016 28寸高铁显示屏总装图 28" High-speed Rail Display Screen Assembly Drawing 1 0 73.0 33.0 14.7 parsed from STEP text; machine-translated
18 017 Board~a9jc.step Board~a9jc.step 1 0 73.0 33.0 14.7 parsed from STEP text
19 018 jgj-hy0280HD03模组 jgj-hy0280HD03 Module 1 0 95.0 42.0 8.5 parsed from STEP text; machine-translated
20 019 遥控+光感板 Remote Control + Light Sensor Board 1 0 59.0 79.0 39.2 parsed from STEP text; machine-translated
21 020 2P凤凰端子 2P Phoenix Terminal 1 0 29.0 21.0 29.7 parsed from STEP text; machine-translated
22 021 BNC-1~BNC-TH_BNC-50KWYE~a9jc.step BNC-1~BNC-TH_BNC-50KWYE~a9jc.step 1 0 29.0 21.0 24.9 parsed from STEP text
23 022 J7~XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step J7~XH4X1-2.54~CONN-TH_4P-P2.50_MEGASTAR_ZX-XH2.54-4PZZ~a9jc.step 1 0 722.0 154.6 16.8 parsed from STEP text
24 023 28寸恒流板 28" Constant Current Board 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
25 024 28寸条屏铝框 28" Strip Screen Aluminum Frame 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
26 025 28寸屏压件2 28" Screen Pressure Clip 2 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
27 026 M3x6沉头螺丝 M3x6 Countersunk Screw 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
28 027 28寸上铝板 28" Upper Aluminum Plate 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
29 028 28寸屏压件1 28" Screen Pressure Clip 1 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
30 029 28寸光感支架 28" Light Sensor Bracket 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
31 030 28寸安装板 28" Mounting Plate 1 0 6.0 6.0 5.0 parsed from STEP text; machine-translated
32 031 J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step J5~HDMI_A_F_SMD_19P~HDMI-SMD_HDMI-HX-19D~a9jc.step 1 0 6.0 6.0 5.0 parsed from STEP text
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+392
View File
@@ -0,0 +1,392 @@
# STEP Processor — macOS Setup Checklist
### Apple Silicon (Mac Studio M-series) · No Rosetta Required
Work through this top to bottom. Each section has a ✅ verification command
so you know it worked before moving on.
---
## PHASE 1 — Prerequisites
### 1.1 — Confirm Python version
```bash
python3 --version
```
**You need Python 3.10, 3.11, or 3.12.**
3.13 will work but is less tested. 3.9 is too old.
If your version is wrong, install via Homebrew:
```bash
brew install python@3.11
```
Then use `python3.11` explicitly in all commands below.
---
### 1.2 — Confirm Homebrew is installed
```bash
brew --version
```
If missing:
```bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```
---
### 1.3 — Create a dedicated virtual environment
Keep everything isolated from your system Python.
Pick a folder — e.g. your CoWork dev directory.
```bash
python3 -m venv ~/step-processor-env
source ~/step-processor-env/bin/activate
```
You should see `(step-processor-env)` in your terminal prompt.
**All pip commands below assume this environment is active.**
✅ Verify:
```bash
which python # should point inside step-processor-env
```
---
## PHASE 2 — Primary Library: build123d
### 2.1 — Upgrade pip first
```bash
pip install --upgrade pip
```
---
### 2.2 — Install cadquery-ocp (the OCC kernel, native arm64)
This is now on PyPI directly — no manual wheel download needed.
```bash
pip install cadquery-ocp
```
This is a large download (~300MB). Let it run.
✅ Verify:
```bash
python -c "import OCP; print('OCP kernel OK')"
```
---
### 2.3 — Install build123d
```bash
pip install build123d
```
✅ Verify:
```bash
python -c "import build123d; print('build123d OK')"
```
---
### 2.4 — Install the rendering stack
```bash
pip install trimesh pyrender Pillow numpy pandas anthropic openpyxl
```
> **Note:** `openpyxl` is required for Excel BOM output. If missing, the skill silently
> falls back to CSV. Install it here to get MPM-branded `.xlsx` output.
✅ Verify each:
```bash
python -c "import trimesh; print('trimesh OK')"
python -c "import pyrender; print('pyrender OK')"
python -c "import PIL; print('Pillow OK')"
python -c "import pandas; print('pandas OK')"
python -c "import anthropic; print('anthropic OK')"
python -c "import openpyxl; print('openpyxl OK')"
```
---
## PHASE 3 — Diagram Export: cairosvg
Required for converting SVG diagrams to PNG and PDF.
### 3.1 — Install system libraries via Homebrew
```bash
brew install cairo pango gdk-pixbuf libffi
```
### 3.2 — Install cairosvg
```bash
pip install cairosvg
```
✅ Verify:
```bash
python -c "import cairosvg; print('cairosvg OK')"
```
If you get a library not found error, run this and retry:
```bash
export PKG_CONFIG_PATH="/opt/homebrew/lib/pkgconfig"
pip install cairosvg
```
---
## PHASE 4 — Fallback Library: FreeCAD Headless
Only needed if build123d fails on a specific file.
> **macOS 15 Sequoia note:** The conda-forge FreeCAD package is killed silently
> by Gatekeeper on Sequoia due to unsigned binaries. Use the official signed
> FreeCAD.app instead (approach below). Conda approach will NOT work on Sequoia.
### 4.1 — Install official FreeCAD.app (signed & notarized)
Download the latest arm64 release from:
```
https://github.com/FreeCAD/FreeCAD/releases/latest
```
Open the .dmg and drag FreeCAD.app to /Applications as normal.
Launch it once from /Applications so macOS registers the security approval.
### 4.2 — Verify headless via app bundle
The binary is `freecadcmd` (all lowercase) inside the bundle:
```bash
/Applications/FreeCAD.app/Contents/Resources/bin/freecadcmd --version
# Should print: FreeCAD 1.x.x Revision: ...
```
✅ Verify Python import:
```bash
/Applications/FreeCAD.app/Contents/Resources/bin/python -c \
"import sys; sys.path.insert(0, '/Applications/FreeCAD.app/Contents/Resources/lib'); \
import FreeCAD; print('FreeCAD', FreeCAD.Version())"
```
**Key paths for loader.py:**
- CLI binary: `/Applications/FreeCAD.app/Contents/Resources/bin/freecadcmd`
- Python interpreter: `/Applications/FreeCAD.app/Contents/Resources/bin/python`
- Module path (add to sys.path): `/Applications/FreeCAD.app/Contents/Resources/lib`
The code invokes FreeCAD using the bundle's own Python — no conda env needed.
---
## PHASE 5 — API Key
### 5.1 — Set your Anthropic API key
```bash
echo 'export ANTHROPIC_API_KEY="sk-ant-YOUR-KEY-HERE"' >> ~/.zshrc
echo 'export ANTHROPIC_API_KEY="sk-ant-YOUR-KEY-HERE"' >> ~/.zshenv
source ~/.zshrc
```
> **Important:** Add the key to **both** `~/.zshrc` AND `~/.zshenv`.
> `~/.zshrc` is only sourced in interactive terminal sessions.
> `~/.zshenv` is sourced for all zsh invocations including Desktop Commander's
> non-interactive shells — without it, translation will silently fail when
> running via CoWork/Desktop Commander.
✅ Verify:
```bash
echo $ANTHROPIC_API_KEY # should print your key (or at least sk-ant-...)
```
---
## PHASE 6 — Place the Skill Files
### 6.1 — Copy the step-processor folder to your working location
Recommended: somewhere in your CoWork project or a dedicated tools directory.
```
~/your-project/
step-processor/
step_processor.py
modules/
__init__.py
loader.py
bom.py
renderer.py
translator.py
rewriter.py
query_engine.py
external_diagram.py
schemas/
external_diagram_schema.json
parts_mapping_schema.json
templates/
datablock_template.md
SKILL.md
INSTALL.md
```
### 6.2 — Activate your environment before running
```bash
source ~/step-processor-env/bin/activate
cd ~/your-project/step-processor
```
---
## PHASE 7 — Test Run (do this when you have a STEP file ready)
Work through these tests in order. Stop at the first failure and fix it
before continuing.
---
### TEST 1 — Confirm the skill loads without errors
```bash
python step_processor.py --help
```
Expected: prints the CLI help/usage block with no ImportError or traceback.
---
### TEST 2 — Load a STEP file and extract BOM
Replace `yourfile.step` with your actual file path.
```bash
python step_processor.py /path/to/yourfile.step --no-thumbnails --verbose
```
Expected output:
```
INFO Loading: yourfile.step
INFO [build123d] Loaded: yourfile.step
INFO BOM extracted: N parts
INFO BOM XLSX → /path/to/yourfile_bom.xlsx
```
Open the `.xlsx` file to confirm part names look right.
If you see Chinese characters in the `Supplier Part Name` column — that's expected and correct.
The `Part Description` column will be populated with English in Test 4.
If you see a CSV instead of xlsx, `openpyxl` is not installed — run `pip install openpyxl`.
---
### TEST 3 — Generate thumbnails
```bash
python step_processor.py /path/to/yourfile.step --no-bom --verbose
```
Expected: 6 PNG files appear next to the STEP file:
`yourfile_front.png`, `yourfile_bottom.png`, `yourfile_left.png`,
`yourfile_right.png`, `yourfile_iso_left.png`, `yourfile_iso_right.png`
Open them in Preview to confirm they look like your enclosure from different angles.
---
### TEST 4 — Translation (requires API key)
Only run this if the BOM from Test 2 has Chinese part names.
```bash
python step_processor.py /path/to/yourfile.step --no-thumbnails --translate --verbose
```
Expected: `part_name_english` column in the CSV is now populated with English names.
A `yourfile_EN.step` should appear if Chinese labels were found in the STEP file itself.
---
### TEST 5 — Geometry query REPL
```bash
python step_processor.py /path/to/yourfile.step --no-thumbnails --no-bom --repl
```
At the `>` prompt, try:
```
> bounding box
> list all holes
> all parts
> help
> exit
```
Confirm the output tables look reasonable for your file.
---
### TEST 6 — External dimensional diagram
```bash
python step_processor.py /path/to/yourfile.step --diagram --verbose
```
Expected files next to the STEP:
- `yourfile__external-diagram.svg`
- `yourfile__external-diagram.png`
- `yourfile__meta.json`
Open the SVG in a browser (drag and drop into Safari or Chrome) to inspect the diagram.
Open the PNG for the rasterized version.
Open the JSON to confirm the metadata block populated correctly.
---
### TEST 7 — Diagram with PDF output
```bash
python step_processor.py /path/to/yourfile.step --diagram --diagram-pdf --verbose
```
Expected: adds `yourfile__external-diagram.pdf` to the outputs.
Open in Preview to confirm it renders cleanly.
---
## Quick Reference — Re-activating After Restart
Every time you open a new terminal session:
```bash
source ~/step-processor-env/bin/activate
cd ~/your-project/step-processor
export ANTHROPIC_API_KEY="sk-ant-..." # only if not already in ~/.zshrc
```
---
## Troubleshooting Quick Reference
| Symptom | Fix |
|---------|-----|
| `ModuleNotFoundError: build123d` | `pip install build123d` (check env is activated) |
| `ModuleNotFoundError: OCP` | `pip install cadquery-ocp` |
| `ModuleNotFoundError: cairosvg` | `brew install cairo && pip install cairosvg` |
| `pyrender` offscreen crash | Expected on some macOS setups — thumbnails will warn but diagram still generates |
| BOM shows no parts / 1 part | Normal for single-body STEP files — the STEP text parser falls back to 1-row BOM |
| Chinese labels not translated | Check `$ANTHROPIC_API_KEY` is set. If running via CoWork/Desktop Commander, add key to `~/.zshenv` not just `~/.zshrc` |
| BOM output is CSV not xlsx | `openpyxl` not installed — `pip install openpyxl` with venv active |
| `_EN.step` entity count mismatch warning | File has unusual STEP encoding — rewrite skipped, original untouched, BOM still correct |
| FreeCAD fallback not working | Use signed FreeCAD.app — conda-forge is Gatekeeper-blocked on Sequoia. Path: `/Applications/FreeCAD.app/Contents/Resources/bin/freecadcmd` (all lowercase) |
| Diagram SVG opens blank | Check `yourfile__meta.json` warnings array for geometry errors |
+430
View File
@@ -0,0 +1,430 @@
# STEP File Processor Skill
## Purpose
This skill enables Claude to work with STEP/STP CAD files used in manufacturing.
It provides:
- PNG thumbnail extraction at 6 camera angles
- Parts manifest / BOM export to CSV with automatic Chinese→English translation
- Optional English-labeled STEP copy (source file never modified)
- Natural language geometric query interface (holes, faces, dimensions, quantities)
Designed for MPMedia's display enclosure and mounting kit product lines.
---
## When to invoke this skill
Trigger on any of:
- User provides a `.step` or `.stp` file path and asks to process, view, extract, or query it
- User asks to "generate thumbnails" or "render views" of a STEP file
- User asks to "extract BOM", "get parts list", or "show manifest" from a STEP file
- User asks a geometric question about a STEP file: holes, faces, dimensions, quantities
- User asks to "translate" a STEP file's part names
- Claude Code context where a STEP file is present in the working directory
**Read this entire SKILL.md before writing any code.**
---
## Library Stack
### Primary: build123d
- Modern OpenCASCADE Python wrapper; native Apple Silicon arm64 wheels
- Clean, Pythonic API for STEP reading, assembly traversal, geometry queries
- Offscreen PNG rendering via: build123d → export GLB/OBJ → trimesh → pyrender
- Install: `pip install build123d trimesh pyrender pillow numpy pandas anthropic`
- ARM64 OCP wheel required first — see INSTALL.md
### Fallback: FreeCAD headless
- Native macOS-arm64 build available via conda-forge (verified Nov 2024)
- Used when build123d fails to load or render a specific file
- Invoked via subprocess calling `FreeCADCmd` or by importing `FreeCAD` + `Part`
- **Never import FreeCADGui** — headless only, GUI modules will crash
- Install: `conda install -c conda-forge freecad` (arm64 native, no Rosetta)
### Translation: Claude API (claude-sonnet-4-20250514)
- Translates Chinese part names with manufacturing context awareness
- e.g. 安装支架 → "Mounting Bracket" (not just "installation support")
- Requires ANTHROPIC_API_KEY in environment
### Supporting libraries (both tracks)
- `Pillow` — PNG save/compose
- `numpy` — geometry math
- `pandas` — CSV/BOM output
- `trimesh` + `pyrender` — GLB→PNG rendering fallback path
---
## File Structure
```
step-processor/
step_processor.py ← CLI entry point + REPL
modules/
loader.py ← STEP loading (build123d primary, FreeCAD fallback)
bom.py ← Assembly tree traversal + BOM extraction
renderer.py ← Offscreen PNG rendering (6 views)
translator.py ← Claude API translation layer
rewriter.py ← STEP label patcher (_EN copy only, never modifies source)
query_engine.py ← Geometric query handler (holes, faces, dimensions)
INSTALL.md
SKILL.md
```
---
## Dual-Track Fallback Pattern
Every module must implement this pattern:
```python
def load_step(filepath):
try:
return _load_via_build123d(filepath)
except ImportError:
logger.warning("build123d not available — falling back to FreeCAD")
return _load_via_freecad(filepath)
except Exception as e:
logger.warning(f"build123d failed ({e}) — falling back to FreeCAD")
return _load_via_freecad(filepath)
```
Always log fallbacks at WARNING level so the user knows which path ran.
FreeCAD fallback invocation pattern (subprocess, cleanest isolation):
```python
import subprocess, json, tempfile
def _load_via_freecad(filepath):
script = f"""
import FreeCAD, Part, json
shape = Part.read("{filepath}")
# extract data, write to temp JSON
"""
result = subprocess.run(["FreeCADCmd", "--console", "-c", script], capture_output=True)
# parse result...
```
---
## Camera Views
Always produce these 6 views unless user specifies otherwise:
| View Name | Camera Direction | File Suffix |
|------------|---------------------------|------------------|
| Front | +Y looking toward origin | `_front.png` |
| Bottom | -Z looking up | `_bottom.png` |
| Left | -X looking right | `_left.png` |
| Right | +X looking left | `_right.png` |
| Iso Left | +X+Y+Z (left-forward) | `_iso_left.png` |
| Iso Right | -X+Y+Z (right-forward) | `_iso_right.png` |
Default resolution: 1024×768. All PNGs saved to same folder as source STEP file.
---
## Output Naming Convention
Given input: `/path/to/EnclosureA.step`
| Output | Path |
|---------------------|-----------------------------------|
| Thumbnails (×6) | `/path/to/EnclosureA_front.png` etc. |
| BOM CSV | `/path/to/EnclosureA_bom.csv` |
| English STEP copy | `/path/to/EnclosureA_EN.step` |
**Never overwrite or modify the source STEP file.**
`_EN.step` is only created when Chinese labels are detected. If all labels are
already ASCII/English, skip silently and note it in output.
---
## BOM CSV Schema
```
part_number, part_name_original, part_name_english, quantity, level, parent,
bbox_x_mm, bbox_y_mm, bbox_z_mm, notes
```
- `level` = assembly depth (0 = root, 1 = direct children, etc.)
- `parent` = part_name_english of parent assembly
- `notes` = translator flags (e.g. "machine-translated", "ambiguous term")
- Bounding boxes in millimeters, 2 decimal places
- If no translation needed, `part_name_original` == `part_name_english`
---
## Translation Logic
```
bom.py extracts all part names
→ translator.py checks each name for CJK unicode range (\u4e00-\u9fff)
→ if any found: batch call Claude API with this system prompt:
"You are a mechanical engineering translator specializing in Chinese
manufacturing CAD files for display and enclosure products. Translate
the following part names from Chinese to English. Preserve technical
precision. Use standard hardware terminology. Output ONLY a JSON object
mapping original → translated, nothing else.
Example: {\"安装支架\": \"Mounting Bracket\", \"螺钉M4\": \"M4 Screw\"}"
→ inject translations into BOM CSV
→ if translation requested: produce _EN.step via rewriter.py
```
Flag uncertain translations in the `notes` column rather than silently guessing.
---
## STEP Label Rewriting (_EN copy)
`rewriter.py` produces a translated copy — line-by-line, targeted replacement:
1. Read source file line by line
2. Identify lines containing `PRODUCT('`, `PRODUCT_DEFINITION_CONTEXT(` etc.
3. Extract quoted name strings (first argument of PRODUCT entity)
4. Replace CJK strings with translated equivalents from the translation map
5. Write to `{filename}_EN.step`
6. Validate: entity `#` count before == after. If mismatch → abort, warn, delete partial output
**Do not use broad regex that could touch entity reference numbers (#123 etc.)**
Target only the quoted string arguments of known STEP entity types.
---
## Geometric Query Engine
`query_engine.py` handles natural language queries about loaded geometry.
### Supported Query Types
| Query pattern | What it extracts |
|----------------------------------|-------------------------------------------------------|
| "list all mounting holes" | Cylindrical faces, axis ⊥ to primary face, dia < 15mm|
| "list all holes" | All cylindrical through-features |
| "holes diameter [N]mm" | Filter holes by diameter |
| "tapped holes" / "threaded" | Cylinders with helical features (if present in model) |
| "face count" | Total face count by type (planar, cylindrical, etc.) |
| "bounding box" | Overall model extents in mm |
| "part [name] dimensions" | Bounding box of a specific named part |
| "largest face" | Largest planar face area in mm² |
| "wall thickness" | Min distance between opposing parallel faces |
| "all parts" | Full assembly listing with quantities |
### Query Invocation
**CLI (single query, exit after):**
```bash
python step_processor.py enclosure.step --query "list all mounting holes with diameter and depth"
```
**REPL (interactive, geometry stays loaded in memory between queries):**
```bash
python step_processor.py enclosure.step --repl
> list mounting holes
> bounding box
> how many M4 holes
> exit
```
### Query Output Format
Always return a structured table:
```
MOUNTING HOLES — EnclosureA.step
─────────────────────────────────────────────────────────────
# Part Name Diameter Depth Position (x,y,z)
─────────────────────────────────────────────────────────────
1 Front Panel 4.20 mm 8.00 mm (12.50, 0.00, 45.20)
2 Front Panel 4.20 mm 8.00 mm (87.50, 0.00, 45.20)
─────────────────────────────────────────────────────────────
Total: 8 holes across 3 parts
```
---
## CLI Reference
```
python step_processor.py <file.step> [options]
Options:
--thumbnails Generate 6 PNG views (default: on)
--no-thumbnails Skip PNG generation
--bom Export BOM CSV (default: on)
--no-bom Skip BOM export
--translate Auto-translate Chinese labels in BOM + produce _EN.step
--no-translate Skip translation even if Chinese labels detected
--query "..." Run a single geometric query and exit
--repl Enter interactive query REPL
--resolution WxH Thumbnail resolution (default: 1024x768)
--views front,iso_left,iso_right Comma-separated subset of views
--verbose Show library selection, fallback notices, timing
```
**Default behavior (no flags):** `--thumbnails --bom` plus auto-detect translation.
---
## Error Handling
| Condition | Behavior |
|------------------------------------|--------------------------------------------------------|
| File not found | Exit with clear message, no partial output |
| Neither library available | Exit with INSTALL.md reference |
| build123d render fails | Auto-fallback to FreeCAD path, log warning |
| Translation API unavailable | Write BOM with original names only, note in output |
| STEP has no assembly structure | Treat as single part, one-row BOM |
| Chinese detected, no translate flag| Auto-translate by default (skip with --no-translate) |
| _EN.step entity count mismatch | Abort rewrite, warn, delete partial file |
| FreeCAD headless GUI module import | Catch ImportError, skip that module, continue |
---
## INSTALL.md Summary
**build123d (primary — native arm64, no conda):**
```bash
# 1. Install the arm64 OCP wheel from CadQuery GitHub releases:
# https://github.com/CadQuery/OCP/releases — pick macosx_*_arm64.whl for your Python
pip install path/to/cadquery_ocp-*.whl
# 2. Install build123d and rendering stack
pip install build123d trimesh pyrender pillow numpy pandas anthropic
# 3. Set API key
export ANTHROPIC_API_KEY=sk-ant-...
```
**FreeCAD (fallback — native arm64 via conda):**
```bash
# Install Miniforge arm64 if not already present:
# https://github.com/conda-forge/miniforge/releases
conda install -c conda-forge freecad
# Verify headless works:
FreeCADCmd --version
```
No Rosetta. No x64 sub-environment. Both libraries run natively on Apple Silicon.
---
## Notes for Claude
- Default run = thumbnails + BOM + auto-translate if Chinese detected
- When a user asks a geometry question, invoke query_engine — don't describe the STEP file from the BOM alone
- Always confirm output file locations at end of run
- If Chinese labels found, proactively note it and offer to show the translation map
- In REPL mode, keep responses tight — it's a terminal session
- When describing holes or geometry, lead with the table, not prose
- If build123d fallback fires, tell the user which library ran
---
## Sub-skill: External Dimensional Diagram
### Purpose
Generates a standardized external dimensional diagram for installation planning,
product reference, and non-CAD users. NOT a manufacturing drawing.
Think: installation reference sheet with clean orthographic views + one ISO.
### Terminology
Use: "external dimensional diagram"
Never use: shop drawing, manufacturing drawing, fabrication drawing
### Entry point
```python
from modules.external_diagram import step_external_diagram
meta = step_external_diagram(
path="enclosure.step",
mode="enclosure_only", # or enclosure_plus_mounting, mounting_only
mapping_file="MR28UW_mapping.json", # optional
datablock_file="MR28UW.md", # optional, auto-detected if omitted
options={
"style": "line_drawing", # or "rendered" — auto if omitted
"pdf": True, # default False
"mounting_variant": "Wall Mount", # for enclosure_plus_mounting mode
"render_variants": True, # generate diagram per mounting variant
}
)
```
### Modes
- `enclosure_only` — enclosure body only (default when no mapping file)
- `enclosure_plus_mounting` — enclosure + selected mounting subassembly
- `mounting_only` — mounting geometry only
### Styles
- `line_drawing` — pure SVG wireframe (MR28 reference style). Auto-selected for simple enclosures.
- `rendered` — rendered ISO views composited with dimensioned orthographic line views
(MR16 reference style). Auto-selected for complex assemblies (>200 faces).
### Layout auto-selection
- `single_sheet` — default for most models
- `multi_page` — auto-selected when any dimension > 1200mm or part count > 60
### Outputs per run
| File | Always? | Description |
|------|---------|-------------|
| `{stem}__external-diagram.svg` | Yes | Source SVG, always retained |
| `{stem}__external-diagram.png` | Yes | PNG export via cairosvg |
| `{stem}__external-diagram.pdf` | Optional (`--pdf` flag) | PDF via cairosvg |
| `{stem}__meta.json` | Yes | Full metadata per schema |
| Individual view PNGs | Rendered style only | front, side, rear, iso |
| Variant diagrams | If `render_variants=True` | One set per mounting variant |
### Dimensions shown
Required: overall width, height, depth
Auto-detected and shown when found:
- Active area / screen aperture (with diagonal in mm and inches)
- Mounting hole chain spacing (horizontal or vertical, auto-selected by readability)
- VESA pattern if detected
- Edge offsets to mounting geometry
Units: metric primary (mm) with imperial in parentheses, smaller italic
### Parts mapping file
JSON file per product model — classifies parts as enclosure/mounting/internal/fastener.
Drives show/hide logic per mode. Supports mounting variants.
Schema: `schemas/parts_mapping_schema.json`
Example: `schemas/parts_mapping_schema.json``examples` array
### Datablock / title block
Sourced from a `.md` file alongside the STEP file (auto-detected by matching stem name)
or passed explicitly as `datablock_file`.
Template: `templates/datablock_template.md`
Edit the template to configure: model number, display name, revision, date, company,
and any custom fields. Custom fields appear in the data block footer.
### Metadata schema
Full JSON schema: `schemas/external_diagram_schema.json`
Designed for AI consumption, Odoo product data integration, search indexing,
and CoWork documentation workflows. All geometry in mm.
### CLI
```bash
# Basic — enclosure only, auto style
python step_processor.py enclosure.step --diagram
# With options
python step_processor.py enclosure.step --diagram --diagram-mode enclosure_plus_mounting \
--diagram-style line_drawing --diagram-pdf \
--mapping mapping/MR28UW_mapping.json \
--datablock MR28UW.md
# Render all mounting variants
python step_processor.py enclosure.step --diagram --diagram-mode enclosure_plus_mounting \
--diagram-variants
```
### Notes for Claude
- When a user says "generate a diagram", "make a dimensional sheet", or "create reference drawing" → invoke this sub-skill
- Always confirm mode with user if unclear (enclosure_only vs with mounting)
- If no mapping file exists, default to enclosure_only and note it
- If multiple mounting variants detected in assembly tree, offer to render each
- The meta.json output is the primary artifact for downstream AI/workflow consumption
- cairosvg is required for PNG/PDF export: `pip install cairosvg`
- Weight/mass fields in meta.json are reserved for future Odoo product data integration
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
# step-processor modules package
Binary file not shown.
Binary file not shown.
+275
View File
@@ -0,0 +1,275 @@
"""
bom.py — BOM extraction from STEP assembly tree.
Primary: build123d assembly traversal.
Fallback: STEP ISO 10303-21 text parser for PRODUCT entities.
Always produces a complete DataFrame; saved as MPM-branded Excel (.xlsx).
"""
import logging
import math
import re
from collections import Counter
from pathlib import Path
import pandas as pd
from .loader import StepModel
logger = logging.getLogger("step_processor.bom")
BOM_COLUMNS = [
"part_number", "part_name_original", "part_name_english",
"quantity", "level", "parent",
"bbox_x_mm", "bbox_y_mm", "bbox_z_mm", "notes"
]
# ── Excel output — MPM brand palette (hex, no #) ─────────────────────────────
_MPM_DARK_SHADE = "232022" # header background + body text
_MPM_LIGHT_SHADE = "F5F1EC" # header text
_MPM_WARM_OFF_WHITE = "FAF7F2" # alternating row tint
_MPM_MIDDLE_GOLD = "DCBB4F" # accent border under header row
# Column rename + reorder for stakeholder-facing Excel output.
# Internal processing always uses BOM_COLUMNS names.
_XLSX_RENAME = {
"part_name_english": "part_description",
"part_name_original": "part_name_supplier",
}
_XLSX_ORDER = [
"part_number", "part_description", "quantity", "level", "parent",
"bbox_x_mm", "bbox_y_mm", "bbox_z_mm", "notes", "part_name_supplier",
]
_XLSX_HEADERS = {
"part_number": "Part #",
"part_description": "Part Description",
"quantity": "Qty",
"level": "Level",
"parent": "Parent",
"bbox_x_mm": "X (mm)",
"bbox_y_mm": "Y (mm)",
"bbox_z_mm": "Z (mm)",
"notes": "Notes",
"part_name_supplier": "Supplier Part Name",
}
_XLSX_WIDTHS = {
"part_number": 12, "part_description": 40, "quantity": 8,
"level": 7, "parent": 22, "bbox_x_mm": 11, "bbox_y_mm": 11,
"bbox_z_mm": 11, "notes": 34, "part_name_supplier": 40,
}
def _safe(v):
"""Convert NaN/None → None so openpyxl writes blank cells."""
if v is None:
return None
try:
if isinstance(v, float) and math.isnan(v):
return None
except Exception:
pass
return v
def extract_bom(model: StepModel) -> pd.DataFrame:
"""Extract BOM from a loaded StepModel. Returns DataFrame with BOM_COLUMNS.
Name-extraction strategy
------------------------
The STEP text parser is always the primary source for part_name_original.
It reads raw bytes with GBK/UTF-8 encoding detection, correctly decoding
Chinese CAD part labels.
OCC's STEP reader (used by build123d) applies an internal codec that maps
each 2-byte GBK sequence to an incorrect Unicode codepoint — the resulting
strings cannot be recovered. We therefore never rely on child.label for
part names when the file may contain CJK characters.
OCC assembly walk (_bom_from_parts) is kept as a fallback only for files
where the text parser returns nothing (e.g., non-PRODUCT-entity STEP files).
"""
rows = []
# Primary: STEP text parser — encoding-aware, correct for ASCII and CJK files
rows = _bom_from_step_text(model.path)
if not rows and model.backend == "build123d" and model.parts:
# Fallback: OCC assembly walk (CJK names will be garbled but structure intact)
logger.debug("STEP text parser empty — falling back to OCC assembly walk")
rows = _bom_from_parts(model.parts)
if not rows:
logger.info("No assembly structure — treating as single part")
stem = model.path.stem
rows = [{"part_number": "001", "part_name_original": stem,
"part_name_english": stem, "quantity": 1, "level": 0,
"parent": "", "bbox_x_mm": None, "bbox_y_mm": None,
"bbox_z_mm": None, "notes": "single-body file"}]
df = pd.DataFrame(rows, columns=BOM_COLUMNS)
if model.backend == "build123d":
df = _enrich_bboxes(model, df)
logger.info(f"BOM extracted: {len(df)} parts")
return df
def _bom_from_parts(parts: list) -> list:
name_counts = Counter(p["name"] for p in parts)
seen = set()
rows = []
for i, p in enumerate(parts):
name = p["name"]
if name in seen:
continue
seen.add(name)
rows.append({
"part_number": f"{len(rows)+1:03d}",
"part_name_original": name,
"part_name_english": name,
"quantity": name_counts[name],
"level": p.get("level", 0),
"parent": p.get("parent", ""),
"bbox_x_mm": None, "bbox_y_mm": None, "bbox_z_mm": None,
"notes": "",
})
return rows
def _read_step_text(step_path: Path) -> str:
"""Read STEP file text with CJK-aware encoding detection.
STEP files from Chinese manufacturers embed raw GBK bytes in name strings.
Strategy: try UTF-8 first (correct for modern files); if replacement chars
appear, retry as GBK (covers Chinese CAD exports); fall back to latin-1
which never fails (may contain mojibake, but at least it's readable).
"""
for enc in ('utf-8', 'gbk'):
try:
text = step_path.read_text(encoding=enc)
if enc == 'utf-8' and '' in text:
# Replacement chars detected — GBK bytes can't be UTF-8
continue
return text
except (UnicodeDecodeError, LookupError):
continue
return step_path.read_text(encoding='latin-1', errors='replace')
def _bom_from_step_text(step_path: Path) -> list:
"""Parse STEP ISO 10303-21 PRODUCT entities directly."""
try:
text = _read_step_text(step_path)
except Exception as e:
logger.warning(f"Could not read STEP text: {e}")
return []
pattern = re.compile(r"#\d+\s*=\s*PRODUCT\s*\(\s*'([^']*)'", re.IGNORECASE)
seen = {}
for match in pattern.finditer(text):
name = match.group(1).strip()
if not name or name.upper() in ("", "NONE"):
continue
if name in seen:
seen[name]["quantity"] += 1
else:
seen[name] = {
"part_number": f"{len(seen)+1:03d}",
"part_name_original": name, "part_name_english": name,
"quantity": 1, "level": 0, "parent": "",
"bbox_x_mm": None, "bbox_y_mm": None, "bbox_z_mm": None,
"notes": "parsed from STEP text",
}
rows = list(seen.values())
if rows:
logger.info(f"STEP text parser found {len(rows)} unique part names")
return rows
def _enrich_bboxes(model: StepModel, df: pd.DataFrame) -> pd.DataFrame:
"""Add bounding box dims per part from build123d. Best-effort."""
try:
bb = model.shape.bounding_box()
if len(df) == 1:
df.at[0, "bbox_x_mm"] = round(bb.size.X, 2)
df.at[0, "bbox_y_mm"] = round(bb.size.Y, 2)
df.at[0, "bbox_z_mm"] = round(bb.size.Z, 2)
else:
children = getattr(model.shape, "children", []) or []
for i, child in enumerate(children):
if i >= len(df):
break
try:
cb = child.bounding_box()
df.at[i, "bbox_x_mm"] = round(cb.size.X, 2)
df.at[i, "bbox_y_mm"] = round(cb.size.Y, 2)
df.at[i, "bbox_z_mm"] = round(cb.size.Z, 2)
except Exception:
pass
except Exception as e:
logger.debug(f"bbox enrichment skipped: {e}")
return df
def save_bom_csv(df: pd.DataFrame, step_path: Path) -> Path:
"""Write BOM DataFrame to CSV (legacy fallback)."""
out_path = step_path.parent / f"{step_path.stem}_bom.csv"
df.to_csv(out_path, index=False)
logger.info(f"BOM CSV → {out_path.name}")
return out_path
def save_bom_xlsx(df: pd.DataFrame, step_path: Path) -> Path:
"""Write BOM DataFrame to an MPM-branded Excel workbook.
Column changes vs internal schema (BOM_COLUMNS):
part_name_english → Part Description (column 2)
part_name_original → Supplier Part Name (last column)
Falls back to CSV if openpyxl is unavailable.
"""
out_path = step_path.parent / f"{step_path.stem}_bom.xlsx"
try:
from openpyxl import Workbook
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
from openpyxl.utils import get_column_letter
except ImportError:
logger.warning("openpyxl not installed — falling back to CSV")
return save_bom_csv(df, step_path)
# Build display DataFrame
disp = df.rename(columns=_XLSX_RENAME).copy()
for col in _XLSX_ORDER:
if col not in disp.columns:
disp[col] = None
disp = disp[_XLSX_ORDER]
wb = Workbook()
ws = wb.active
ws.title = "Bill of Materials"
gold_border = Border(bottom=Side(style="medium", color=_MPM_MIDDLE_GOLD))
hdr_fill = PatternFill("solid", fgColor=_MPM_DARK_SHADE)
hdr_font = Font(name="Montserrat", bold=True, color=_MPM_LIGHT_SHADE, size=10)
hdr_align = Alignment(horizontal="center", vertical="center", wrap_text=True)
# Header row
for c, col in enumerate(_XLSX_ORDER, 1):
cell = ws.cell(row=1, column=c, value=_XLSX_HEADERS.get(col, col))
cell.font = hdr_font
cell.fill = hdr_fill
cell.alignment = hdr_align
cell.border = gold_border
ws.column_dimensions[get_column_letter(c)].width = _XLSX_WIDTHS.get(col, 15)
ws.row_dimensions[1].height = 28
# Data rows
body_font = Font(name="Open Sans", size=10, color=_MPM_DARK_SHADE)
body_align = Alignment(horizontal="left", vertical="center")
for r, (_, row) in enumerate(disp.iterrows(), 2):
fill = PatternFill("solid", fgColor=_MPM_WARM_OFF_WHITE if r % 2 == 0 else "FFFFFF")
for c, col in enumerate(_XLSX_ORDER, 1):
cell = ws.cell(row=r, column=c, value=_safe(row[col]))
cell.font = body_font
cell.fill = fill
cell.alignment = body_align
ws.freeze_panes = "A2"
wb.save(str(out_path))
logger.info(f"BOM XLSX → {out_path.name}")
return out_path
File diff suppressed because it is too large Load Diff
+187
View File
@@ -0,0 +1,187 @@
"""
loader.py — STEP file loading with build123d primary and FreeCAD fallback.
Returns a StepModel dataclass used by all other modules.
FreeCAD fallback invokes the signed app bundle Python to avoid
Gatekeeper issues on macOS 15 Sequoia.
"""
import json
import logging
import subprocess
import tempfile
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Optional
logger = logging.getLogger("step_processor.loader")
FREECAD_PYTHON = "/Applications/FreeCAD.app/Contents/Resources/bin/python"
FREECAD_LIB = "/Applications/FreeCAD.app/Contents/Resources/lib"
FREECAD_CMD = "/Applications/FreeCAD.app/Contents/Resources/bin/freecadcmd"
@dataclass
class StepModel:
"""Unified model object returned by load_step(). Used by all modules."""
shape: Any
backend: str # "build123d" | "freecad"
path: Path
parts: list = field(default_factory=list)
face_count: int = 0
metadata: dict = field(default_factory=dict)
def load_step(filepath) -> Optional["StepModel"]:
"""Load a STEP file. Tries build123d first; falls back to FreeCAD."""
step_path = Path(filepath).expanduser().resolve()
if not step_path.exists():
logger.error(f"File not found: {step_path}")
return None
try:
return _load_via_build123d(step_path)
except ImportError:
logger.warning("build123d not available — falling back to FreeCAD")
return _load_via_freecad(step_path)
except Exception as e:
logger.warning(f"build123d failed ({type(e).__name__}: {e}) — falling back to FreeCAD")
return _load_via_freecad(step_path)
def _load_via_build123d(step_path: Path) -> "StepModel":
"""Load using build123d. Raises on failure."""
from build123d import import_step
logger.info(f"[build123d] Loading: {step_path.name}")
shape = import_step(str(step_path))
face_count = 0
try:
face_count = sum(1 for _ in shape.faces())
except Exception:
pass
parts = _extract_parts_build123d(shape)
logger.info(f"[build123d] Loaded: {step_path.name} | {face_count} faces | {len(parts)} parts")
return StepModel(shape=shape, backend="build123d", path=step_path,
parts=parts, face_count=face_count)
def _fix_gbk_mojibake(s: str) -> str:
"""
Recover Chinese text stored as mojibake in STEP part labels.
STEP files from Chinese CAD tools (SolidWorks CN, etc.) embed raw GBK bytes
in PRODUCT name strings. OpenCASCADE reads STEP strings as latin-1, which
re-interprets those GBK bytes as latin-1 code points — classic mojibake.
Fix: re-encode the string to latin-1 (restoring the original GBK byte
sequence) then decode as GBK to get correct Unicode Chinese characters.
If the string is pure ASCII, or the round-trip fails (already valid Unicode
or a non-GBK extended char), returns the original string unchanged.
"""
if not s or all(ord(c) < 128 for c in s):
return s # pure ASCII: nothing to fix
try:
return s.encode('latin-1').decode('gbk')
except (UnicodeDecodeError, UnicodeEncodeError):
return s # not GBK mojibake — leave original
def _extract_parts_build123d(shape) -> list:
"""Walk build123d compound tree and extract named parts."""
parts = []
def _walk(compound, level=0, parent_name=""):
children = []
try:
children = compound.children if hasattr(compound, "children") else []
except Exception:
pass
if children:
for child in children:
raw = (getattr(child, "label", "") or
getattr(child, "name", "") or f"Part_{level}")
name = _fix_gbk_mojibake(raw)
parts.append({"name": name, "level": level, "parent": parent_name})
_walk(child, level + 1, name)
else:
raw = (getattr(compound, "label", "") or
getattr(compound, "name", "") or "")
if raw:
name = _fix_gbk_mojibake(raw)
parts.append({"name": name, "level": level, "parent": parent_name})
_walk(shape)
return parts
def _load_via_freecad(step_path: Path) -> Optional["StepModel"]:
"""Load using FreeCAD app bundle Python via subprocess."""
if not Path(FREECAD_PYTHON).exists():
logger.error(f"FreeCAD Python not found at {FREECAD_PYTHON}. Install FreeCAD.app.")
return None
logger.info(f"[FreeCAD] Loading: {step_path.name}")
script = f"""
import sys, json
sys.path.insert(0, {repr(FREECAD_LIB)})
import FreeCAD, Part
try:
shape = Part.read({repr(str(step_path))})
bb = shape.BoundBox
sub = shape.SubShapes if hasattr(shape, 'SubShapes') else []
parts = [{{"name": f"Part_{{i}}", "level": 1, "parent": "root"}}
for i in range(len(sub))]
print(json.dumps({{"ok": True, "face_count": len(shape.Faces),
"parts": parts,
"bbox": {{"XMin": bb.XMin, "XMax": bb.XMax,
"YMin": bb.YMin, "YMax": bb.YMax,
"ZMin": bb.ZMin, "ZMax": bb.ZMax}}}}))
except Exception as e:
print(json.dumps({{"ok": False, "error": str(e)}}))
"""
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write(script)
script_path = f.name
try:
proc = subprocess.run([FREECAD_PYTHON, script_path],
capture_output=True, text=True, timeout=120)
json_line = next((l.strip() for l in proc.stdout.splitlines()
if l.strip().startswith("{")), None)
if not json_line:
logger.error(f"[FreeCAD] No JSON output. stderr: {proc.stderr[:300]}")
return None
data = json.loads(json_line)
if not data.get("ok"):
logger.error(f"[FreeCAD] Load failed: {data.get('error')}")
return None
proxy = _FreeCADShapeProxy(data["bbox"], data["face_count"])
logger.info(f"[FreeCAD] Loaded: {step_path.name} | {data['face_count']} faces")
return StepModel(shape=proxy, backend="freecad", path=step_path,
parts=data.get("parts", []),
face_count=data["face_count"],
metadata={"bbox": data["bbox"]})
except subprocess.TimeoutExpired:
logger.error("[FreeCAD] Load timed out after 120s")
return None
except Exception as e:
logger.error(f"[FreeCAD] Unexpected error: {e}")
return None
finally:
Path(script_path).unlink(missing_ok=True)
class _FreeCADShapeProxy:
"""Proxy carrying FreeCAD geometry data extracted via subprocess."""
def __init__(self, bbox_dict: dict, face_count: int):
self.BoundBox = _BoundBox(bbox_dict)
self.face_count = face_count
self.Faces = [None] * face_count
def faces(self):
for _ in range(self.face_count):
yield object()
class _BoundBox:
def __init__(self, d: dict):
self.XMin = d.get("XMin", 0); self.XMax = d.get("XMax", 0)
self.YMin = d.get("YMin", 0); self.YMax = d.get("YMax", 0)
self.ZMin = d.get("ZMin", 0); self.ZMax = d.get("ZMax", 0)
+335
View File
@@ -0,0 +1,335 @@
"""
query_engine.py — Natural language geometric query handler.
Supports both single-query (--query "...") and interactive REPL (--repl).
REPL keeps the model in memory between queries for speed.
All output is formatted ASCII tables.
Supported query types (see SKILL.md for full reference):
bounding box overall model extents
face count total faces by type
all parts full assembly listing
list all holes all cylindrical through-features
list all mounting holes cylinders dia < 15mm, axis ⊥ primary face
holes diameter N filter by diameter
wall thickness min distance between opposing parallel faces
largest face largest planar face area
help list supported queries
exit / quit exit REPL
"""
import logging
import re
import textwrap
from typing import Optional
from .loader import StepModel
logger = logging.getLogger("step_processor.query")
# Regex patterns for query routing
_BBOX_RE = re.compile(r"\b(bounding.?box|extents|dimensions|overall.?size)\b", re.I)
_FACECOUNT_RE = re.compile(r"\b(face.?count|how many faces|number of faces)\b", re.I)
_PARTS_RE = re.compile(r"\ball.?parts|part.?list|assembly|components\b", re.I)
_HOLES_RE = re.compile(r"\bholes?\b", re.I)
_MOUNTING_RE = re.compile(r"\bmounting\b", re.I)
_DIA_RE = re.compile(r"diameter\s+([\d.]+)\s*mm?", re.I)
_WALL_RE = re.compile(r"\bwall.?thickness\b", re.I)
_LARGEST_RE = re.compile(r"\blargest.?face\b", re.I)
_HELP_RE = re.compile(r"\bhelp\b", re.I)
_EXIT_RE = re.compile(r"\b(exit|quit|q)\b", re.I)
def run_query(model: StepModel, query: str) -> str:
"""Dispatch a query string and return formatted output."""
q = query.strip()
if _EXIT_RE.search(q):
return "EXIT"
if _HELP_RE.search(q):
return _help_text()
if _BBOX_RE.search(q):
return _query_bounding_box(model)
if _FACECOUNT_RE.search(q):
return _query_face_count(model)
if _PARTS_RE.search(q):
return _query_all_parts(model)
if _HOLES_RE.search(q):
dia_match = _DIA_RE.search(q)
dia_filter = float(dia_match.group(1)) if dia_match else None
mounting_only = bool(_MOUNTING_RE.search(q))
return _query_holes(model, mounting_only=mounting_only, dia_filter=dia_filter)
if _WALL_RE.search(q):
return _query_wall_thickness(model)
if _LARGEST_RE.search(q):
return _query_largest_face(model)
return (f"Query not recognized: '{q}'\n"
f"Type 'help' to see supported queries.")
def repl(model: StepModel, step_path):
"""Launch interactive REPL. Returns when user types exit/quit."""
print(f"\nSTEP Query REPL — {step_path.name}")
print("Type 'help' for supported queries, 'exit' to quit.\n")
while True:
try:
q = input("> ").strip()
except (EOFError, KeyboardInterrupt):
print()
break
if not q:
continue
result = run_query(model, q)
if result == "EXIT":
break
print(result)
print()
# ── Query implementations ─────────────────────────────────────────────────────
def _query_bounding_box(model: StepModel) -> str:
try:
if model.backend == "build123d":
bb = model.shape.bounding_box()
x = round(bb.size.X, 2)
y = round(bb.size.Y, 2)
z = round(bb.size.Z, 2)
else:
bb = model.shape.bounding_box
x = round(bb.XMax - bb.XMin, 2)
y = round(bb.YMax - bb.YMin, 2)
z = round(bb.ZMax - bb.ZMin, 2)
return _table(
f"BOUNDING BOX — {model.path.name}",
["Axis", "Dimension"],
[["Width (X)", f"{x} mm ({x/25.4:.3f} in)"],
["Depth (Y)", f"{y} mm ({y/25.4:.3f} in)"],
["Height (Z)", f"{z} mm ({z/25.4:.3f} in)"]]
)
except Exception as e:
return f"Bounding box query failed: {e}"
def _query_face_count(model: StepModel) -> str:
if model.backend != "build123d":
return f"Face count query requires build123d (loaded via {model.backend})"
try:
from build123d import Compound
all_faces = model.shape.faces()
planar = sum(1 for f in all_faces if f.geom_type() == "PLANE")
cylindrical = sum(1 for f in all_faces if f.geom_type() == "CYLINDER")
other = len(all_faces) - planar - cylindrical
return _table(
f"FACE COUNT — {model.path.name}",
["Type", "Count"],
[["Planar", str(planar)],
["Cylindrical", str(cylindrical)],
["Other", str(other)],
["Total", str(len(all_faces))]]
)
except Exception as e:
return f"Face count failed: {e}"
def _query_all_parts(model: StepModel) -> str:
if not model.parts:
return (f"No assembly structure found in {model.path.name}.\n"
f"File appears to be a single solid body.")
rows = []
for p in model.parts:
rows.append([
p.get("part_number", ""),
p.get("name", ""),
str(p.get("quantity", 1)),
str(p.get("level", 0)),
p.get("parent", ""),
])
return _table(
f"ALL PARTS — {model.path.name}",
["#", "Name", "Qty", "Level", "Parent"],
rows
) + f"\nTotal: {len(model.parts)} parts"
def _query_holes(model: StepModel, mounting_only: bool = False,
dia_filter: Optional[float] = None) -> str:
if model.backend != "build123d":
return f"Hole query requires build123d (loaded via {model.backend})"
try:
holes = _find_holes(model, mounting_only=mounting_only, dia_filter=dia_filter)
if not holes:
label = "mounting holes" if mounting_only else "holes"
qualifier = f"{dia_filter}mm" if dia_filter else ""
return f"No {label}{qualifier} found in {model.path.name}."
header = "MOUNTING HOLES" if mounting_only else "ALL HOLES"
# Group by diameter bucket for summary view
from collections import Counter
dia_counts = Counter(round(h["dia"], 1) for h in holes)
MAX_ROWS = 50
display_holes = holes[:MAX_ROWS]
rows = []
for i, h in enumerate(display_holes, 1):
rows.append([
str(i),
f"{h['dia']:.2f} mm",
f"{h['depth']:.2f} mm" if h["depth"] else "",
f"({h['x']:.1f}, {h['y']:.1f}, {h['z']:.1f})",
])
result = _table(
f"{header}{model.path.name}",
["#", "Diameter", "Depth", "Position (x,y,z)"],
rows
)
result += f"\nShowing {len(display_holes)} of {len(holes)} unique hole locations"
result += "\n\nDIAMETER SUMMARY"
result += "\n" + "" * 30
for dia, count in sorted(dia_counts.items()):
result += f"\n {dia:.1f} mm ×{count}"
result += "\n" + "" * 30
return result
except Exception as e:
return f"Hole query failed: {e}"
def _find_holes(model: StepModel, mounting_only: bool, dia_filter):
"""Extract and deduplicate cylindrical faces from build123d model.
Deduplication: round axis position to 1mm grid, group by (dia_bucket, x, y, z).
This collapses multiple cylindrical faces from the same physical hole
(e.g. inner + outer surface of same cylinder) into one entry.
"""
from OCP.BRepAdaptor import BRepAdaptor_Surface
from OCP.GeomAbs import GeomAbs_Cylinder
seen = {} # key → best entry
try:
faces = model.shape.faces()
except Exception:
return []
for face in faces:
try:
adaptor = BRepAdaptor_Surface(face.wrapped)
if adaptor.GetType() != GeomAbs_Cylinder:
continue
cyl = adaptor.Cylinder()
r = cyl.Radius()
dia = round(r * 2, 2)
# Diameter filters
if mounting_only and dia > 15.0:
continue
if dia_filter and abs(dia - dia_filter) > 0.5:
continue
axis_pt = cyl.Location()
# Round to 1mm grid for deduplication
gx = round(axis_pt.X())
gy = round(axis_pt.Y())
gz = round(axis_pt.Z())
dia_bucket = round(dia, 1)
key = (dia_bucket, gx, gy, gz)
if key not in seen:
bb = face.bounding_box()
depth = round(max(bb.size.X, bb.size.Y, bb.size.Z), 2)
seen[key] = {
"dia": dia,
"depth": depth,
"x": round(axis_pt.X(), 1),
"y": round(axis_pt.Y(), 1),
"z": round(axis_pt.Z(), 1),
}
except Exception:
continue
return list(seen.values())
def _query_wall_thickness(model: StepModel) -> str:
if model.backend != "build123d":
return f"Wall thickness query requires build123d (loaded via {model.backend})"
try:
faces = model.shape.faces()
planar = [f for f in faces if f.geom_type() == "PLANE"]
if len(planar) < 2:
return "Insufficient planar faces to determine wall thickness."
# Heuristic: find minimum non-zero distance between parallel opposing faces
min_t = None
for i, f1 in enumerate(planar):
n1 = f1.normal_at()
for f2 in planar[i+1:]:
n2 = f2.normal_at()
# Parallel if normals are anti-parallel
dot = abs(n1.dot(n2))
if dot > 0.99:
c1 = f1.center()
c2 = f2.center()
dist = round(abs((c1 - c2).dot(n1)), 3)
if dist > 0.01:
if min_t is None or dist < min_t:
min_t = dist
if min_t is None:
return "Could not determine wall thickness from available faces."
return _table(
f"WALL THICKNESS — {model.path.name}",
["Measurement", "Value"],
[["Minimum wall thickness",
f"{min_t} mm ({min_t/25.4:.3f} in)"]]
)
except Exception as e:
return f"Wall thickness query failed: {e}"
def _query_largest_face(model: StepModel) -> str:
if model.backend != "build123d":
return f"Largest face query requires build123d (loaded via {model.backend})"
try:
faces = model.shape.faces()
planar = [(f, f.area()) for f in faces if f.geom_type() == "PLANE"]
if not planar:
return "No planar faces found."
largest, area = max(planar, key=lambda x: x[1])
bb = largest.bounding_box()
return _table(
f"LARGEST PLANAR FACE — {model.path.name}",
["Property", "Value"],
[["Area", f"{round(area, 2)} mm²"],
["Width", f"{round(bb.size.X, 2)} mm"],
["Height", f"{round(bb.size.Z, 2)} mm"]]
)
except Exception as e:
return f"Largest face query failed: {e}"
# ── Formatting helpers ─────────────────────────────────────────────────────────
def _table(title: str, headers: list, rows: list) -> str:
col_widths = [len(h) for h in headers]
for row in rows:
for i, cell in enumerate(row):
col_widths[i] = max(col_widths[i], len(str(cell)))
sep = "" * (sum(col_widths) + 3 * len(headers) - 1)
lines = [title, sep]
header_line = " ".join(h.ljust(col_widths[i]) for i, h in enumerate(headers))
lines.append(header_line)
lines.append(sep)
for row in rows:
lines.append(" ".join(str(cell).ljust(col_widths[i]) for i, cell in enumerate(row)))
lines.append(sep)
return "\n".join(lines)
def _help_text() -> str:
return textwrap.dedent("""\
SUPPORTED QUERIES
─────────────────────────────────────────────────────────────
bounding box Overall model extents (W×D×H in mm)
face count Faces by type (planar, cylindrical, other)
all parts Full assembly listing with quantities
list all holes All cylindrical through-features
list all mounting holes Holes smaller than 15mm diameter
holes diameter 4.2mm Filter holes by specific diameter
wall thickness Minimum wall thickness estimate
largest face Largest planar face area
help Show this message
exit Exit the REPL
─────────────────────────────────────────────────────────────
Tip: geometry queries require build123d backend.
If the file loaded via FreeCAD fallback, only bounding box
and parts list are available.""")
+321
View File
@@ -0,0 +1,321 @@
"""
renderer.py — Offscreen PNG thumbnail generation.
Pipeline (with color): build123d → GLTF export → trimesh scene → pyrender → PNG
Pipeline (fallback): build123d → per-solid STL → colored trimesh scene → pyrender → PNG
Coordinate convention AFTER STEP→GLTF export→trimesh load:
trimesh applies a Z-up→Y-up GLTF convention that swaps STEP's Y and Z axes:
X = width (~248mm for MR16) — left/right
Y = depth (~41mm for MR16) — front/back; Y_min=screen face, Y_max=back panel
Z = height (~459mm for MR16) — tall axis; Z_min=top end, Z_max=bottom end
"front" camera sits at -Y (screen side) looking toward +Y to see the LCD face.
→ World "up" vector is (0,0,-1) — negative Z = top of display in image.
6 standard views: front, rear, left, right, iso_left, iso_right
"""
import logging
import tempfile
from pathlib import Path
import numpy as np
from .loader import StepModel
logger = logging.getLogger("step_processor.renderer")
# Camera direction vectors: where the camera is PLACED relative to model center.
# Camera always looks toward center from direction * distance.
#
# Trimesh world axes after GLTF load (STEP Y and Z are swapped by GLTF Y-up conv):
# X = width — left/right
# Y = depth — Y_min = screen face (LCD), Y_max = back panel (ports)
# Z = height — Z_min = TOP end of display, Z_max = BOTTOM end of display
#
# "front": camera at -Y (screen side) looks toward +Y → sees LCD face.
# "rear": camera at +Y (back side) looks toward -Y → sees port panel.
# "left": camera at -X looks toward +X → sees left edge.
# "right": camera at +X looks toward -X → sees right edge.
# iso views: -Y component keeps camera on screen side; -Z = toward top end.
VIEW_CAMERAS = {
"front": ( 0, -1, 0), # LCD screen face (Y_min side)
"rear": ( 0, 1, 0), # back panel/ports (Y_max side)
"left": (-1, 0, 0), # left edge (X_min side)
"right": ( 1, 0, 0), # right edge (X_max side)
"top": ( 0, 0, -1), # top end (Z_min side)
"bottom": ( 0, 0, 1), # bottom end (Z_max side)
"iso_left": (-1, -1, -0.5), # front-left-above: screen + left edge + top
"iso_right": ( 1, -1, -0.5), # front-right-above: screen + right edge + top
}
DEFAULT_VIEWS = ["front", "rear", "left", "right", "iso_left", "iso_right"]
# Color palette for per-part coloring when GLTF has no embedded colors
# 20 distinct RGBA colors (alpha=200 for slight transparency on overlaps)
PART_COLORS = [
[180, 180, 185, 255], # light steel
[ 70, 130, 180, 255], # steel blue
[205, 133, 63, 255], # peru / bronze
[ 60, 179, 113, 255], # medium sea green
[188, 143, 143, 255], # rosy brown
[100, 149, 237, 255], # cornflower blue
[255, 160, 50, 255], # dark orange
[147, 112, 219, 255], # medium purple
[ 46, 139, 87, 255], # sea green
[205, 92, 92, 255], # indian red
[135, 206, 235, 255], # sky blue
[244, 164, 96, 255], # sandy brown
[106, 90, 205, 255], # slate blue
[ 32, 178, 170, 255], # light sea green
[220, 20, 60, 255], # crimson
[218, 165, 32, 255], # goldenrod
[ 72, 61, 139, 255], # dark slate blue
[143, 188, 143, 255], # dark sea green
[255, 99, 71, 255], # tomato
[176, 196, 222, 255], # light steel blue
]
DEFAULT_WIDTH = 1024
DEFAULT_HEIGHT = 768
def render_views(model: StepModel, step_path: Path,
views=None, width=DEFAULT_WIDTH, height=DEFAULT_HEIGHT) -> dict:
"""Render PNG views. Returns dict of view_name → output Path."""
views = views or DEFAULT_VIEWS
stem = step_path.stem
out_dir = step_path.parent
results = {}
mesh = _get_mesh(model, step_path)
if mesh is None:
logger.warning("Could not obtain mesh — thumbnails skipped")
return results
for view_name in views:
if view_name not in VIEW_CAMERAS:
logger.warning(f"Unknown view '{view_name}' — skipping")
continue
out_path = out_dir / f"{stem}_{view_name}.png"
try:
_render_single_view(mesh, VIEW_CAMERAS[view_name], out_path, width, height)
results[view_name] = out_path
logger.info(f"Rendered: {out_path.name}")
except Exception as e:
logger.warning(f"Render failed for '{view_name}': {e}")
return results
def _get_mesh(model: StepModel, step_path: Path):
"""Get a single assembled trimesh.Trimesh from the model.
Always returns a single concatenated mesh (not a Scene) so the camera
distance and bounds calculations are correct and parts don't explode.
Priority:
1. build123d → GLTF → scene.dump() → concatenate with transforms applied
(preserves per-part colors if embedded in STEP)
2. build123d → single STL of full assembly (fallback, monochrome but correct)
3. FreeCAD path → bounding box box mesh
"""
try:
import trimesh
except ImportError:
logger.warning("trimesh not installed — thumbnails unavailable. pip install trimesh")
return None
if model.backend == "build123d":
# ── GLTF path: color-aware, transforms flattened via scene.dump() ─────
with tempfile.NamedTemporaryFile(suffix=".gltf", delete=False) as tmp:
gltf_path = Path(tmp.name)
try:
from build123d import export_gltf
import warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore")
export_gltf(model.shape, str(gltf_path))
scene = trimesh.load(str(gltf_path))
if isinstance(scene, trimesh.Scene) and scene.geometry:
# scene.dump() applies the full transform graph → parts at correct positions
dumped = scene.dump()
if dumped:
# Pull baseColorFactor directly from PBR material per mesh.
# .to_color() loses this in some trimesh versions.
materialized = []
for m in dumped:
try:
bc = m.visual.material.baseColorFactor
if bc is not None:
m.visual = trimesh.visual.ColorVisuals(
mesh=m, face_colors=np.array(bc, dtype=np.uint8))
else:
m.visual = trimesh.visual.ColorVisuals(
mesh=m, face_colors=[185, 190, 195, 255])
except Exception:
try:
m.visual = m.visual.to_color()
except Exception:
pass
materialized.append(m)
mesh = trimesh.util.concatenate(materialized)
if mesh is not None and len(mesh.faces) > 0:
n_colors = len(set(
tuple(c) for c in mesh.visual.face_colors[:, :3][::1000]
)) if hasattr(mesh.visual, 'face_colors') else 0
logger.info(f"GLTF: assembled {len(mesh.faces)} faces, "
f"~{n_colors} distinct colors sampled")
return mesh
except Exception as e:
logger.warning(f"GLTF path failed ({e}) — falling back to STL")
finally:
try:
gltf_path.unlink()
except Exception:
pass
# ── STL fallback: single merged mesh, correct positions, monochrome ───
with tempfile.NamedTemporaryFile(suffix=".stl", delete=False) as tmp:
stl_path = Path(tmp.name)
try:
from build123d import export_stl
export_stl(model.shape, str(stl_path))
mesh = trimesh.load(str(stl_path), force="mesh")
if mesh is not None and len(mesh.faces) > 0:
mesh.visual.face_colors = [185, 190, 195, 255]
logger.info(f"STL fallback: {len(mesh.faces)} faces (uniform color)")
return mesh
except Exception as e:
logger.warning(f"STL fallback failed ({e})")
finally:
try:
stl_path.unlink()
except Exception:
pass
# FreeCAD / last resort: bounding box
return _bbox_wireframe_mesh(model)
def _mesh_has_color(mesh) -> bool:
"""Return True if the mesh has meaningful (non-gray) face colors."""
try:
fc = mesh.visual.face_colors
if fc is None or len(fc) == 0:
return False
# If all faces are within ±25 of [128,128,128] treat as uncolored
gray = [128, 128, 128]
mean_rgb = fc[:, :3].mean(axis=0)
return not all(abs(int(mean_rgb[i]) - gray[i]) < 25 for i in range(3))
except Exception:
return False
def _bbox_wireframe_mesh(model: StepModel):
"""Create a simple box mesh from the model's bounding box. Last-resort fallback."""
try:
import trimesh
bb = model.shape.bounding_box()
# _FreeCADShapeProxy stores a _BoundBox
if hasattr(bb, "XMin"):
extents = [
bb.XMax - bb.XMin,
bb.YMax - bb.YMin,
bb.ZMax - bb.ZMin,
]
center = [
(bb.XMax + bb.XMin) / 2,
(bb.YMax + bb.YMin) / 2,
(bb.ZMax + bb.ZMin) / 2,
]
else:
# build123d BoundBox
extents = [bb.size.X, bb.size.Y, bb.size.Z]
center = [bb.center.X, bb.center.Y, bb.center.Z]
mesh = trimesh.creation.box(extents=extents)
mesh.apply_translation(center)
logger.debug("Using bbox wireframe mesh for rendering")
return mesh
except Exception as e:
logger.warning(f"Bbox wireframe mesh failed: {e}")
return None
def _render_single_view(mesh, camera_direction: tuple, out_path: Path,
width: int, height: int):
"""Render one view using pyrender offscreen, save to out_path."""
try:
import pyrender
except ImportError:
raise ImportError("pyrender not installed. pip install pyrender")
# Normalize camera direction
direction = np.array(camera_direction, dtype=float)
direction = direction / np.linalg.norm(direction)
# Bounding box of the assembled mesh
bounds = mesh.bounds # shape (2,3): [[xmin,ymin,zmin],[xmax,ymax,zmax]]
center = (bounds[0] + bounds[1]) / 2.0
diag = np.linalg.norm(bounds[1] - bounds[0])
# Camera sits at 2.5× diagonal distance from center, looking at center
camera_distance = diag * 2.5
eye = center + direction * camera_distance
# World up: (0,0,-1) = negative Z = top of display in trimesh GLTF space.
# Fallback to (0,-1,0) for top/bottom end views where direction ≈ ±Z.
world_up = np.array([0, 0, -1], dtype=float)
if abs(np.dot(direction, world_up)) > 0.9:
world_up = np.array([0, -1, 0], dtype=float)
# Proven camera frame formula (right-handed, same structure as original code):
# right = cross(world_up, direction) [world_up × backward]
# cam_up = cross(direction, right) [backward × right]
# col2 = direction [camera +Z = backward; looks down -Z]
right = np.cross(world_up, direction)
if np.linalg.norm(right) < 1e-8:
right = np.cross(np.array([1, 0, 0], dtype=float), direction)
right = right / np.linalg.norm(right)
cam_up = np.cross(direction, right)
cam_up = cam_up / np.linalg.norm(cam_up)
# 4×4 camera pose: columns = [right, cam_up, backward, eye]
camera_pose = np.eye(4)
camera_pose[:3, 0] = right
camera_pose[:3, 1] = cam_up
camera_pose[:3, 2] = direction # camera +Z = backward; pyrender looks down -Z
camera_pose[:3, 3] = eye
# Build pyrender scene — white background
pr_scene = pyrender.Scene(ambient_light=[0.35, 0.35, 0.35],
bg_color=[255, 255, 255, 255])
pr_scene.add(pyrender.Mesh.from_trimesh(mesh, smooth=False))
# FOV sized so the model fills ~80% of the frame
yfov = 2.0 * np.arctan((diag * 0.5) / camera_distance) * 1.25
camera = pyrender.PerspectiveCamera(yfov=yfov, aspectRatio=width / height)
pr_scene.add(camera, pose=camera_pose)
# Key light from camera position + fill from above-opposite
pr_scene.add(pyrender.DirectionalLight(color=np.ones(3), intensity=4.5),
pose=camera_pose)
fill_pose = np.eye(4)
# Fill light: offset from top of display (-Z) to avoid zero-vector when
# direction is parallel to (0,1,0) (e.g. rear view).
fill_dir = -direction + np.array([0, 0, -1], dtype=float)
fill_norm = np.linalg.norm(fill_dir)
if fill_norm < 1e-8:
fill_dir = np.array([0, 0, -1], dtype=float)
else:
fill_dir = fill_dir / fill_norm
fill_eye = center - fill_dir * camera_distance
fill_pose[:3, 3] = fill_eye
pr_scene.add(pyrender.DirectionalLight(color=np.ones(3), intensity=1.8),
pose=fill_pose)
# Offscreen render
r = pyrender.OffscreenRenderer(viewport_width=width, viewport_height=height)
try:
color, _ = r.render(pr_scene)
finally:
r.delete()
from PIL import Image
Image.fromarray(color).save(str(out_path))
+103
View File
@@ -0,0 +1,103 @@
"""
rewriter.py — STEP label rewriter for Chinese→English translation.
Produces {stem}_EN.step — NEVER modifies source file.
Targets only PRODUCT entity name strings.
Validates entity count before/after to ensure file integrity.
"""
import logging
import re
from pathlib import Path
logger = logging.getLogger("step_processor.rewriter")
# Targets both quoted strings in: #N = PRODUCT('id', 'name', 'description', ...)
# ISO 10303-21 PRODUCT has two name fields; CAD viewers typically display the second.
# Chinese CAD exports set both to the same Chinese string, so both must be replaced.
# Groups: (prefix) (id) (sep) (name) (suffix-quote)
PRODUCT_PATTERN = re.compile(
r"(#\d+\s*=\s*PRODUCT\s*\(\s*')([^']*)(',\s*')([^']*)(')",
re.IGNORECASE
)
ENTITY_PATTERN = re.compile(r"^#\d+\s*=\s*\S+\s*\(", re.MULTILINE)
def _read_step_for_rewrite(source_path: Path) -> str:
"""Read STEP file with GBK-aware encoding detection.
STEP files from Chinese CAD tools embed raw GBK bytes in PRODUCT name
strings. Reading as UTF-8 turns those bytes into replacement characters
(U+FFFD), which makes the Chinese→English lookup fail. We try GBK when
UTF-8 produces replacement chars so the regex substitution can actually
find and replace the Chinese strings.
"""
for enc in ('utf-8', 'gbk'):
try:
text = source_path.read_text(encoding=enc)
if enc == 'utf-8' and '' in text:
continue # has replacement chars — retry as GBK
return text
except (UnicodeDecodeError, LookupError):
continue
return source_path.read_text(encoding='latin-1', errors='replace')
def rewrite_step(source_path: Path, translation_map: dict):
"""
Produce English-labeled copy of the STEP file.
Returns output Path or None if no rewrite needed or failed.
"""
if not translation_map:
logger.info("No translations to apply — _EN.step skipped")
return None
try:
source_text = _read_step_for_rewrite(source_path)
except Exception as e:
logger.error(f"Could not read source STEP: {e}")
return None
original_count = len(ENTITY_PATTERN.findall(source_text))
if not any(orig in source_text for orig in translation_map):
logger.info("No Chinese labels in STEP text — _EN.step skipped")
return None
lines = source_text.splitlines(keepends=True)
replaced_count = 0
output_lines = []
for line in lines:
new_line, count = _replace_product_names(line, translation_map)
replaced_count += count
output_lines.append(new_line)
output_text = "".join(output_lines)
new_count = len(ENTITY_PATTERN.findall(output_text))
if new_count != original_count:
logger.error(
f"Entity count mismatch: {original_count}{new_count}. "
"Aborting — source file untouched.")
return None
if replaced_count == 0:
logger.info("No PRODUCT entities matched — _EN.step skipped")
return None
out_path = source_path.parent / f"{source_path.stem}_EN.step"
try:
out_path.write_text(output_text, encoding="utf-8")
logger.info(f"_EN.step written: {out_path.name} ({replaced_count} labels replaced)")
return out_path
except Exception as e:
logger.error(f"Failed to write _EN.step: {e}")
out_path.unlink(missing_ok=True)
return None
def _replace_product_names(line: str, translation_map: dict):
count = 0
def replacer(m):
nonlocal count
# Try id field first (group 2), fall back to name field (group 4)
# Both are Chinese in Chinese CAD exports; replace both with English.
translated = translation_map.get(m.group(2)) or translation_map.get(m.group(4))
if translated:
count += 1
# Replace both the id field and the name field
return m.group(1) + translated + m.group(3) + translated + m.group(5)
return m.group(0)
new_line = PRODUCT_PATTERN.sub(replacer, line)
return new_line, count
+107
View File
@@ -0,0 +1,107 @@
"""
translator.py — Chinese to English part name translation via Claude API.
Detects CJK unicode range. Batches all names in a single API call per file.
Flags uncertain translations in the notes column.
"""
import json
import logging
import os
import re
import pandas as pd
logger = logging.getLogger("step_processor.translator")
CJK_PATTERN = re.compile(r'[一-鿿㐀-䶿]')
SYSTEM_PROMPT = (
"You are a mechanical engineering translator specializing in Chinese "
"manufacturing CAD files for display and enclosure products. "
"Translate the following part names from Chinese to English. "
"Preserve technical precision. Use standard hardware/manufacturing terminology. "
"Output ONLY a JSON object mapping original Chinese to translated English, nothing else.\n"
'Example: {"安装支架": "Mounting Bracket", "螺钉M4": "M4 Screw", "前面板": "Front Panel"}'
)
def has_chinese(text: str) -> bool:
"""Return True if text contains CJK characters."""
return bool(CJK_PATTERN.search(str(text)))
def translate_bom(df: pd.DataFrame, model_name: str = "") -> pd.DataFrame:
"""Detect Chinese part names and translate via Claude API."""
needs_translation = df["part_name_original"].apply(has_chinese)
chinese_names = df.loc[needs_translation, "part_name_original"].unique().tolist()
if not chinese_names:
logger.info("No Chinese part names detected — translation skipped")
return df
logger.info(f"Translating {len(chinese_names)} Chinese part names...")
translation_map = _call_claude_api(chinese_names, model_name)
if not translation_map:
logger.warning("Translation API returned no results — retaining original names")
df.loc[needs_translation, "notes"] = (
df.loc[needs_translation, "notes"].apply(
lambda n: (n + "; " if n else "") + "translation-failed"))
return df
for idx, row in df.iterrows():
original = row["part_name_original"]
if has_chinese(original):
translated = translation_map.get(original)
if translated:
df.at[idx, "part_name_english"] = translated
note_tag = "ambiguous-translation" if "[?]" in translated else "machine-translated"
else:
df.at[idx, "part_name_english"] = original
note_tag = "translation-missing"
existing = row["notes"]
df.at[idx, "notes"] = (existing + "; " if existing else "") + note_tag
logger.info(f"Translated {needs_translation.sum()} parts")
return df
def get_translation_map(df: pd.DataFrame) -> dict:
"""Return dict of original → english for all translated rows."""
mask = df["part_name_original"] != df["part_name_english"]
return dict(zip(df.loc[mask, "part_name_original"],
df.loc[mask, "part_name_english"]))
def _call_claude_api(names: list, model_name: str = "") -> dict:
"""Single batched Claude API call. Returns original→translated dict."""
api_key = os.environ.get("ANTHROPIC_API_KEY")
if not api_key:
logger.error("ANTHROPIC_API_KEY not set — translation unavailable")
return {}
try:
import anthropic
except ImportError:
logger.error("anthropic package not installed — pip install anthropic")
return {}
names_json = json.dumps(names, ensure_ascii=False)
user_msg = f"Translate these part names from Chinese to English:\n{names_json}"
if model_name:
user_msg += f"\n\nContext: Parts from a {model_name} display enclosure assembly."
try:
client = anthropic.Anthropic(api_key=api_key)
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=2048,
system=SYSTEM_PROMPT,
messages=[{"role": "user", "content": user_msg}],
)
text = response.content[0].text.strip()
json_match = re.search(r'\{.*\}', text, re.DOTALL)
if json_match:
text = json_match.group(0)
result = json.loads(text)
logger.info(f"API returned {len(result)} translations")
return result
except json.JSONDecodeError as e:
logger.error(f"Translation API JSON parse error: {e}")
return {}
except Exception as e:
logger.error(f"Translation API error: {type(e).__name__}: {e}")
return {}
@@ -0,0 +1,286 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "mpmedia/step-processor/external-dimensional-diagram/v1",
"title": "ExternalDimensionalDiagram",
"description": "Schema for the external dimensional diagram sub-skill output. Designed for AI consumption, search indexing, and downstream documentation workflows (Odoo product data, CoWork, PDF assembly). All geometry values in millimeters. Imperial equivalents computed at export time.",
"type": "object",
"required": [
"schema_version", "generated_at", "source_path", "model_name",
"mode", "style", "engine_used", "units",
"overall_width", "overall_height", "overall_depth",
"bounding_box", "selected_parts", "excluded_parts",
"layout", "outputs", "warnings", "notes"
],
"properties": {
"schema_version": {
"type": "string",
"const": "1.0",
"description": "Schema version. Increment minor for additive changes, major for breaking changes."
},
"generated_at": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 UTC timestamp of generation run."
},
"source_path": {
"type": "string",
"description": "Absolute path to the source STEP file."
},
"model_name": {
"type": "string",
"description": "Human-readable product/model name. Sourced from datablock MD file if present, otherwise derived from filename stem."
},
"mode": {
"type": "string",
"enum": ["enclosure_only", "enclosure_plus_mounting", "mounting_only"],
"description": "Geometry scope mode used for this run."
},
"style": {
"type": "string",
"enum": ["line_drawing", "rendered"],
"description": "line_drawing = pure SVG wireframe/line art (MR28-style). rendered = rendered ISO views composited with dimensioned orthographic line views (MR16-style)."
},
"layout_mode": {
"type": "string",
"enum": ["single_sheet", "multi_page"],
"description": "single_sheet = all views on one SVG/PDF sheet. multi_page = one view per page, used for large/complex models or when any single view would be too dense at standard scale. Auto-selected based on model bounding box and view count unless overridden."
},
"engine_used": {
"type": "string",
"enum": ["build123d", "freecad"],
"description": "Geometry engine that successfully loaded and processed the file."
},
"fallback_invoked": {
"type": "boolean",
"description": "True if the primary engine (build123d) failed and the fallback was used."
},
"units": {
"type": "object",
"required": ["primary", "secondary"],
"properties": {
"primary": { "type": "string", "const": "mm" },
"secondary": {
"type": "string",
"const": "in",
"description": "Imperial inches shown in parentheses at reduced italic size where space allows."
}
}
},
"overall_width": { "type": "number", "description": "Overall external width in mm (X axis)." },
"overall_height": { "type": "number", "description": "Overall external height in mm (Z axis)." },
"overall_depth": { "type": "number", "description": "Overall external depth in mm (Y axis)." },
"bounding_box": {
"type": "object",
"required": ["x_min", "x_max", "y_min", "y_max", "z_min", "z_max"],
"properties": {
"x_min": { "type": "number" },
"x_max": { "type": "number" },
"y_min": { "type": "number" },
"y_max": { "type": "number" },
"z_min": { "type": "number" },
"z_max": { "type": "number" }
}
},
"active_area": {
"type": ["object", "null"],
"description": "Detected front-face aperture or screen cutout. Null if not detectable.",
"properties": {
"width": { "type": "number", "description": "mm" },
"height": { "type": "number", "description": "mm" },
"diagonal": { "type": "number", "description": "mm" },
"diagonal_inches": { "type": "number", "description": "Computed inches for display diagonal labeling." },
"offset_left": { "type": "number", "description": "mm from enclosure left edge" },
"offset_top": { "type": "number", "description": "mm from enclosure top edge" },
"detection_confidence": {
"type": "string",
"enum": ["high", "medium", "low"],
"description": "Confidence level of aperture detection."
}
}
},
"mounting_dimensions": {
"type": ["object", "null"],
"description": "Null if mode is enclosure_only or no mounting geometry detected.",
"properties": {
"pattern_type": {
"type": "string",
"enum": ["linear", "grid", "vesa", "custom", "unknown"],
"description": "Detected mounting hole pattern type."
},
"vesa_standard": {
"type": ["string", "null"],
"description": "e.g. VESA 100x100, VESA 200x200. Null if not a standard VESA pattern."
},
"hole_diameter": { "type": "number", "description": "mm. Null if mixed diameters." },
"hole_count": { "type": "integer" },
"spacing_x": { "type": ["number", "null"], "description": "Center-to-center horizontal spacing in mm." },
"spacing_y": { "type": ["number", "null"], "description": "Center-to-center vertical spacing in mm." },
"spacing_chain_x": {
"type": ["array", "null"],
"items": { "type": "number" },
"description": "Array of sequential horizontal spacings for chain dimensioning. e.g. [113, 200, 200, 200, 113]"
},
"spacing_chain_y": {
"type": ["array", "null"],
"items": { "type": "number" },
"description": "Array of sequential vertical spacings for chain dimensioning."
},
"offset_from_left": { "type": ["number", "null"] },
"offset_from_top": { "type": ["number", "null"] },
"offset_from_right": { "type": ["number", "null"] },
"offset_from_bottom": { "type": ["number", "null"] }
}
},
"mounting_variants": {
"type": ["array", "null"],
"description": "When the STEP file contains multiple mounting subassemblies (e.g. wall mount + ceiling mount + floor stand), each variant is listed here. Each can produce a separate diagram run.",
"items": {
"type": "object",
"required": ["variant_name", "part_ids"],
"properties": {
"variant_name": {
"type": "string",
"description": "Human-readable variant label. e.g. 'Wall Mount - VESA 200x200'"
},
"part_ids": {
"type": "array",
"items": { "type": "string" },
"description": "Part names/IDs from the assembly tree included in this variant."
},
"diagram_output": {
"type": ["string", "null"],
"description": "Path to the generated diagram for this variant, if rendered."
}
}
}
},
"selected_parts": {
"type": "array",
"items": { "type": "string" },
"description": "Part names included in the geometry scope for this run."
},
"excluded_parts": {
"type": "array",
"items": { "type": "string" },
"description": "Part names explicitly excluded (internal components, fasteners, etc.)."
},
"mapping_file_used": {
"type": ["string", "null"],
"description": "Path to the parts mapping JSON file used, if any."
},
"datablock": {
"type": "object",
"description": "Metadata shown in the diagram title/data block. Sourced from datablock MD file.",
"properties": {
"model_number": { "type": "string" },
"display_name": { "type": "string" },
"drawing_date": { "type": "string", "format": "date" },
"revision": { "type": "string" },
"drawn_by": { "type": "string" },
"company": { "type": "string" },
"units_note": { "type": "string", "default": "Dimensions in mm (in)" },
"scale": { "type": "string", "default": "NTS" },
"custom_fields": {
"type": "object",
"additionalProperties": { "type": "string" },
"description": "Any additional key-value pairs from the MD file to show in the data block."
}
}
},
"layout": {
"type": "object",
"description": "Describes the view layout used in the generated diagram.",
"required": ["views_included", "sheet_size"],
"properties": {
"views_included": {
"type": "array",
"items": {
"type": "string",
"enum": ["front", "rear", "left", "right", "top", "bottom", "isometric_front", "isometric_rear"]
},
"description": "Ordered list of views included in the diagram."
},
"sheet_size": {
"type": "string",
"enum": ["A4_landscape", "A4_portrait", "A3_landscape", "A3_portrait", "A2_landscape", "letter_landscape", "letter_portrait", "auto"],
"description": "Sheet size used. 'auto' selects based on model aspect ratio and view count."
},
"scale_ratio": {
"type": ["string", "null"],
"description": "e.g. '1:5', '1:10', 'NTS' (not to scale). NTS used when views are schematic rather than precise scale."
},
"dimension_style": {
"type": "string",
"enum": ["chain", "ordinate", "baseline"],
"description": "Dimensioning style used. chain = sequential (113-200-200-113). ordinate = from common datum. baseline = all from one edge."
},
"iso_style": {
"type": "string",
"enum": ["line_wireframe", "shaded_render", "none"],
"description": "Isometric view rendering approach."
}
}
},
"outputs": {
"type": "object",
"description": "Paths to all generated output files.",
"properties": {
"diagram_png": { "type": ["string", "null"] },
"diagram_pdf": { "type": ["string", "null"] },
"diagram_svg": { "type": ["string", "null"], "description": "Source SVG always retained alongside exports." },
"iso_png": { "type": ["string", "null"] },
"front_png": { "type": ["string", "null"] },
"side_png": { "type": ["string", "null"] },
"rear_png": { "type": ["string", "null"] },
"meta_json": { "type": "string", "description": "Path to this metadata JSON file." },
"variant_outputs": {
"type": ["array", "null"],
"items": {
"type": "object",
"properties": {
"variant_name": { "type": "string" },
"diagram_png": { "type": ["string", "null"] },
"diagram_pdf": { "type": ["string", "null"] },
"meta_json": { "type": ["string", "null"] }
}
}
}
}
},
"warnings": {
"type": "array",
"items": { "type": "string" },
"description": "Non-fatal issues encountered during generation. e.g. 'active area detection confidence: low', 'mounting holes not detected', 'fallback engine used'."
},
"notes": {
"type": "array",
"items": { "type": "string" },
"description": "Informational messages. e.g. 'Chinese part names translated', '3 mounting variants detected'."
}
}
}
+109
View File
@@ -0,0 +1,109 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "mpmedia/step-processor/parts-mapping/v1",
"title": "PartsMapping",
"description": "Mapping file that classifies parts from a STEP assembly tree. Used by the external diagram generator to determine geometry scope and labels. Supports Chinese-to-English normalization. One file per product model, stored alongside the STEP file or in a shared product data directory.",
"type": "object",
"required": ["model_number", "parts"],
"properties": {
"model_number": {
"type": "string",
"description": "Product model number this mapping applies to. e.g. 'MR28UW'"
},
"parts": {
"type": "object",
"description": "Keyed by original part name from STEP file (may be Chinese). Value is a classification object.",
"additionalProperties": {
"type": "object",
"required": ["english_name", "category"],
"properties": {
"english_name": {
"type": "string",
"description": "Translated/normalized English name. Used in diagrams and BOM."
},
"category": {
"type": "string",
"enum": ["enclosure", "mounting", "internal", "fastener", "display_panel", "cable", "other"],
"description": "Classification drives inclusion/exclusion logic per diagram mode."
},
"include_in_diagram": {
"type": "boolean",
"default": true,
"description": "Override flag. False = always exclude from diagrams regardless of mode."
},
"diagram_label": {
"type": ["string", "null"],
"description": "Optional custom label shown on the diagram for this part. If null, english_name is used."
},
"mounting_variant": {
"type": ["string", "null"],
"description": "If this part belongs to a specific mounting variant, name it here. e.g. 'Wall Mount VESA 200x200'. Parts with the same variant_name are shown/hidden together."
},
"notes": {
"type": "string",
"description": "Free-text notes for documentation or translator flags."
}
}
}
},
"mounting_variants": {
"type": ["array", "null"],
"description": "Named mounting configurations available in this STEP file. Each variant is a named group of mounting parts that should be shown together.",
"items": {
"type": "object",
"required": ["name"],
"properties": {
"name": { "type": "string" },
"description": { "type": "string" },
"default": { "type": "boolean", "description": "True = use this variant when mode is enclosure_plus_mounting and no variant is specified." }
}
}
}
},
"examples": [
{
"model_number": "MR28UW",
"parts": {
"前面板": {
"english_name": "Front Panel",
"category": "enclosure",
"include_in_diagram": true,
"diagram_label": null,
"mounting_variant": null
},
"安装支架": {
"english_name": "Mounting Bracket",
"category": "mounting",
"include_in_diagram": true,
"diagram_label": "Wall Mount Bracket",
"mounting_variant": "Wall Mount"
},
"VESA板": {
"english_name": "VESA Plate 200x200",
"category": "mounting",
"include_in_diagram": true,
"diagram_label": "VESA 200x200 Plate",
"mounting_variant": "VESA Mount"
},
"螺钉M4": {
"english_name": "M4 Screw",
"category": "fastener",
"include_in_diagram": false,
"notes": "Internal fastener - exclude from all diagrams"
},
"主板": {
"english_name": "Main Board",
"category": "internal",
"include_in_diagram": false
}
},
"mounting_variants": [
{ "name": "Wall Mount", "description": "Standard wall mount bracket", "default": true },
{ "name": "VESA Mount", "description": "VESA 200x200 compatible plate", "default": false }
]
}
]
}
+207
View File
@@ -0,0 +1,207 @@
#!/usr/bin/env python3
"""
step_processor.py — CLI entry point for the STEP File Processor skill.
Default run: thumbnails + BOM + auto-translate if Chinese labels detected.
Usage:
python step_processor.py <file.step> [options]
See SKILL.md or --help for full option reference.
"""
import argparse
import logging
import sys
from pathlib import Path
def _setup_logging(verbose: bool):
level = logging.DEBUG if verbose else logging.INFO
fmt = "%(levelname)-5s %(name)s: %(message)s" if verbose else "%(levelname)-5s %(message)s"
logging.basicConfig(level=level, format=fmt, stream=sys.stdout)
def _parse_args():
p = argparse.ArgumentParser(
description="STEP File Processor — thumbnails, BOM, translation, geometry queries",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
p.add_argument("step_file", help="Path to .step or .stp file")
# Output control
p.add_argument("--no-thumbnails", dest="thumbnails", action="store_false", default=True,
help="Skip PNG thumbnail generation")
p.add_argument("--no-bom", dest="bom", action="store_false", default=True,
help="Skip BOM CSV export")
p.add_argument("--translate", dest="translate", action="store_true", default=None,
help="Force translation even if auto-detect is off")
p.add_argument("--no-translate", dest="translate", action="store_false",
help="Skip translation even if Chinese labels are detected")
# Thumbnail options
p.add_argument("--resolution", default="1024x768",
help="Thumbnail resolution WxH (default: 1024x768)")
p.add_argument("--views", default=None,
help="Comma-separated views: front,bottom,left,right,iso_left,iso_right")
# Query modes
p.add_argument("--query", default=None, metavar="QUERY",
help='Run a single geometric query and exit, e.g. "list all holes"')
p.add_argument("--repl", action="store_true",
help="Launch interactive geometry query REPL")
# Diagram
p.add_argument("--diagram", action="store_true",
help="Generate external dimensional diagram")
p.add_argument("--diagram-mode",
choices=["enclosure_only", "enclosure_plus_mounting", "mounting_only"],
default="enclosure_only",
help="Diagram mode (default: enclosure_only)")
p.add_argument("--diagram-style", choices=["line_drawing", "rendered"], default=None,
help="Diagram style (auto-selected if omitted)")
p.add_argument("--diagram-pdf", action="store_true",
help="Also export diagram as PDF")
p.add_argument("--diagram-variants", action="store_true",
help="Render one diagram per mounting variant")
p.add_argument("--mapping", default=None, metavar="JSON_FILE",
help="Parts mapping JSON file for diagram mode")
p.add_argument("--datablock", default=None, metavar="MD_FILE",
help="Datablock .md file for diagram title block")
# Misc
p.add_argument("--verbose", "-v", action="store_true",
help="Show backend selection, fallback notices, timing")
return p.parse_args()
def main():
args = _parse_args()
_setup_logging(args.verbose)
log = logging.getLogger("step_processor")
step_path = Path(args.step_file).expanduser().resolve()
if not step_path.exists():
log.error(f"File not found: {step_path}")
sys.exit(1)
if step_path.suffix.lower() not in (".step", ".stp"):
log.warning(f"Unexpected extension '{step_path.suffix}' — continuing anyway")
# ── Load ─────────────────────────────────────────────────────────────────
log.info(f"Loading: {step_path.name}")
from modules.loader import load_step
model = load_step(step_path)
if model is None:
log.error("Failed to load STEP file. Check that build123d or FreeCAD is installed.")
log.error("See INSTALL.md for setup instructions.")
sys.exit(1)
log.info(f"[{model.backend}] Loaded: {step_path.name}")
# ── Query mode (single) ──────────────────────────────────────────────────
if args.query:
from modules.query_engine import run_query
result = run_query(model, args.query)
print(result)
return
# ── REPL mode ────────────────────────────────────────────────────────────
if args.repl:
from modules.query_engine import repl
repl(model, step_path)
return
# ── BOM ──────────────────────────────────────────────────────────────────
bom_df = None
if args.bom:
from modules.bom import extract_bom, save_bom_xlsx
bom_df = extract_bom(model)
xlsx_path = save_bom_xlsx(bom_df, step_path)
log.info(f"BOM XLSX → {xlsx_path}")
# ── Translation ──────────────────────────────────────────────────────────
if bom_df is not None:
from modules.translator import has_chinese, translate_bom, get_translation_map
needs_translation = args.translate
if needs_translation is None:
# Auto-detect
needs_translation = bom_df["part_name_original"].apply(has_chinese).any()
if needs_translation:
log.info("Chinese part names detected — auto-translating")
if needs_translation:
import os
if not os.environ.get("ANTHROPIC_API_KEY"):
log.warning("ANTHROPIC_API_KEY not set — skipping translation")
else:
bom_df = translate_bom(bom_df, model_name=step_path.stem)
from modules.bom import save_bom_xlsx
save_bom_xlsx(bom_df, step_path) # overwrite with translated version
translation_map = get_translation_map(bom_df)
if translation_map:
from modules.rewriter import rewrite_step
rewrite_step(step_path, translation_map)
# ── Thumbnails ───────────────────────────────────────────────────────────
if args.thumbnails:
try:
w, h = (int(x) for x in args.resolution.split("x"))
except ValueError:
log.warning(f"Invalid resolution '{args.resolution}' — using 1024x768")
w, h = 1024, 768
views = [v.strip() for v in args.views.split(",")] if args.views else None
from modules.renderer import render_views
thumb_results = render_views(model, step_path, views=views, width=w, height=h)
if thumb_results:
log.info(f"Thumbnails: {len(thumb_results)} PNG(s) written")
else:
log.warning("No thumbnails generated (pyrender unavailable or mesh failed)")
# ── External dimensional diagram ─────────────────────────────────────────
if args.diagram:
from modules.external_diagram import step_external_diagram
options = {
"pdf": args.diagram_pdf,
"render_variants": args.diagram_variants,
}
if args.diagram_style:
options["style"] = args.diagram_style
meta = step_external_diagram(
path=str(step_path),
mode=args.diagram_mode,
mapping_file=args.mapping,
datablock_file=args.datablock,
options=options,
)
if meta:
log.info(f"Diagram → {step_path.stem}__external-diagram.svg")
if args.diagram_pdf:
log.info(f"Diagram PDF → {step_path.stem}__external-diagram.pdf")
# ── Summary ──────────────────────────────────────────────────────────────
log.info("Done.")
_print_summary(step_path, model, bom_df, args)
def _print_summary(step_path: Path, model, bom_df, args):
print(f"\n{''*60}")
print(f" {step_path.name} [{model.backend}]")
if bom_df is not None:
print(f" BOM: {len(bom_df)} parts → {step_path.stem}_bom.xlsx")
en_path = step_path.parent / f"{step_path.stem}_EN.step"
if en_path.exists():
print(f" Translated STEP → {en_path.name}")
if args.thumbnails:
thumb_count = sum(1 for ext in ["_front.png", "_rear.png", "_left.png",
"_right.png", "_iso_left.png", "_iso_right.png"]
if (step_path.parent / f"{step_path.stem}{ext}").exists())
print(f" Thumbnails: {thumb_count} PNG(s)")
if args.diagram:
diag = step_path.parent / f"{step_path.stem}__external-diagram.svg"
if diag.exists():
print(f" Diagram → {diag.name}")
print(f"{''*60}\n")
if __name__ == "__main__":
main()
+29
View File
@@ -0,0 +1,29 @@
# Diagram Data Block — MR28UW
<!--
This file drives the title/data block shown on external dimensional diagrams.
Edit the fields below for your product.
All fields are optional except model_number and display_name.
Custom fields (anything below the standard set) will be appended to the data block
in the order listed, as space allows.
Do not remove the field names — the parser uses them as keys.
Leave a field blank rather than deleting it.
-->
model_number: MR28UW
display_name: 28" Ultra-Wide Stretched Bar Display
revision: A
drawing_date: 2025-01-01
drawn_by:
company: MPMedia
units_note: Dimensions in mm (in)
scale: NTS
<!-- Custom fields — add any key: value pairs below -->
<!-- These will appear in the data block footer in order -->
panel_size: 28" (711.75 mm diagonal)
aspect_ratio: 16:4.5
ip_rating: IP54 (front face)
operating_temp: -20°C to +60°C
Executable
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env bash
# Phase 0 validation — prove the native stack actually runs inside the container.
#
# For each bundled sample (MR16 / MR27 / MR28) it exercises every heavy path:
# 1. default run -> CAD kernel load + BOM .xlsx (openpyxl) + 6 thumbnails (pyrender/OSMesa)
# 2. --query -> geometry query engine
# 3. --diagram -> dimensional sheet export (cairosvg)
#
# Outputs persist on the host in ./_phase0_out for inspection.
# Translation (--translate, needs ANTHROPIC_API_KEY) is intentionally skipped here.
set -uo pipefail
cd "$(dirname "$0")"
IMAGE="${IMAGE:-step-parser}"
TAG="${TAG:-dev}"
OUT="${OUT:-$PWD/_phase0_out}"
mkdir -p "$OUT"
SAMPLES=(
"MR16s Gen1_EN.step"
"MR27s Gen1_EN.step"
"MR28uws Gen1_EN.step"
)
pass=0; fail=0
for s in "${SAMPLES[@]}"; do
stem="${s%.step}"
echo "──────────────────────────────────────────────────────────────"
echo " $s"
echo "──────────────────────────────────────────────────────────────"
# Copy the baked-in sample into the mounted dir, then process it there so all
# generated files land on the host. `set -e` makes any failing path fail the run.
docker run --rm -v "$OUT:/data" --entrypoint sh "$IMAGE:$TAG" -c "
set -e
cp '/app/skill.src/$s' '/data/$s'
echo '--- run 1: BOM + thumbnails (kernel + openpyxl + pyrender/OSMesa) ---'
python /app/skill.src/step_processor.py '/data/$s' --no-translate --verbose
echo '--- run 2: geometry query ---'
python /app/skill.src/step_processor.py '/data/$s' --query 'bounding box'
echo '--- run 3: external dimensional diagram (cairosvg) ---'
python /app/skill.src/step_processor.py '/data/$s' --no-thumbnails --no-bom --diagram
"
rc=$?
# Verify the expected artifacts actually exist on the host.
missing=""
[ -f "$OUT/${stem}_bom.xlsx" ] || missing="$missing bom.xlsx"
ls "$OUT/${stem}"_*.png >/dev/null 2>&1 || missing="$missing thumbnails"
[ -f "$OUT/${stem}__external-diagram.svg" ] || missing="$missing diagram.svg"
if [ $rc -eq 0 ] && [ -z "$missing" ]; then
echo "PASS: $s"
pass=$((pass+1))
else
echo "FAIL: $s (exit=$rc, missing:${missing:- none})"
fail=$((fail+1))
fi
done
echo "=============================================================="
echo " Phase 0 smoke test: ${pass} passed, ${fail} failed"
echo " Artifacts: $OUT"
echo "=============================================================="
[ "$fail" -eq 0 ]