phase 0
@@ -0,0 +1 @@
|
||||
{"sessionId":"1165102c-0750-4e7c-9c0c-9e7d88851166","pid":2450,"procStart":"Wed Jun 17 20:05:33 2026","acquiredAt":1781728917587}
|
||||
@@ -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=$?\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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-...
|
||||
@@ -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"]
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
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 |
@@ -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"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
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 |
@@ -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"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
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 |
@@ -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"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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.10–3.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
|
||||
@@ -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')"
|
||||
```
|
||||
@@ -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
|
||||
|
@@ -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
|
||||
|
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
@@ -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
|
||||
|
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -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
|
||||
|
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -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 |
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
# step-processor modules package
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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.""")
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -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'."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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 ]
|
||||