跪拜 Guibai
← All articles
Frontend

A 3D Garment CAD Tool Rebuilt from Python Scripts into a Desktop App

By 前端繁华如梦 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

The project demonstrates a practical pattern for productizing research code: isolate unstable native libraries in subprocesses, use file-based progress reporting, and audit data at every cross-language boundary. The OBJ parsing bug alone is a cautionary tale for any pipeline that passes geometry between Blender and a WebGL frontend.

Summary

The original Costumy prototype required writing Python scripts and waiting minutes to see a single 3D garment result. Costumy Studio turns that into a point-and-click desktop tool: drag sliders to adjust body measurements and design parameters, get a 2D pattern preview in under 3 seconds, and run a full cloth simulation in about 25 seconds.

The architecture splits responsibilities cleanly. A React frontend handles the parameter panel, SVG pattern display, and a three.js 3D viewport. A Python backend — built on the standard library's HTTP server with zero extra dependencies — runs freesewing for pattern generation, the triangle library for mesh triangulation, and Blender's bpy module for physics baking inside an isolated subprocess. That subprocess isolation is non-negotiable: the triangle C library can crash silently beyond Python's exception handling, and bpy is not thread-safe.

Three debugging stories anchor the piece. An IDE file-sync conflict silently dropped an import and caused a full-page white screen. A polling loop reloaded a 12MB OBJ file every 1.2 seconds, freezing the UI. The hardest bug: two stray edge records in an exported OBJ caused three.js to misinterpret an entire mesh as line segments, turning a garment into white noise — a failure caught only by auditing record-type counts at the format boundary.

Takeaways
A full 3D garment pipeline — parametric pattern drafting, triangulation, Blender cloth physics, and OBJ export — runs behind a standard-library Python HTTP server with no framework dependencies.
Cloth simulation must run in a subprocess because the triangle C library can crash outside Python's exception system and bpy is not thread-safe; the original code retried up to 40 times waiting for a success marker string.
React tabs that conditionally unmount three.js scenes lose the GL context, camera state, and loaded geometry; persistent mounting with CSS display toggling avoids full rebuilds on every tab switch.
Polling a job status endpoint that always returns a garment URL once the OBJ file exists causes repeated reloads of a 12MB mesh; only load the result at the terminal done or error state.
Two stray `l` edge records in an OBJ file caused three.js's OBJLoader to construct the entire object as LineSegments instead of a Mesh, silently dropping 10,305 faces — caught by counting record types, not by reading code.
An IDE with a stale buffer overwriting disk changes can silently delete an import and white-screen a React app; when agent-modified files misbehave, re-read the file from disk first.
Electron's offscreen BrowserWindow with console-message forwarding and capturePage provides a headless debugging channel when the renderer process has no visible window.
A bake=false fast mode gives a 5–8 second fit-only preview at zero cost; real-time 30–60fps preview would require an XPBD solver in a Web Worker or a resident Python process streaming via WebSocket.
Conclusions

The triangle library's silent C-level crash and the 40-retry workaround are a stark reminder that wrapping native code in Python subprocesses is sometimes the only viable error-handling strategy — try/except simply cannot reach that failure mode.

The OBJ stray-edge bug reveals a brittle assumption in three.js's OBJLoader: mixed face and line records in one object group trigger a complete type misclassification with no warning. Any pipeline exporting OBJ from Blender for WebGL consumption should strip `l` records as a defensive post-processing step.

The IDE file-sync conflict is a concrete failure mode for AI-assisted coding workflows: an agent edits a file on disk while the same file sits open in an editor with unsaved state, and the next manual save silently reverts the agent's change. The fix is procedural — always confirm disk contents before debugging — not technical.

The architecture's deliberate avoidance of Flask or FastAPI in favor of the standard library's HTTP server is a pragmatic choice for a venv already loaded with numpy, bpy, and triangle; adding a web framework would risk dependency conflicts with no meaningful benefit for a single-user desktop tool.

Concepts & terms
XPBD (Extended Position-Based Dynamics)
A real-time physics simulation method widely used for cloth, soft bodies, and fluids. Unlike force-based methods, it works directly on positions and constraints, making it stable at low iteration counts — the algorithm behind real-time cloth in tools like CLO3D and Marvelous Designer.
bpy (Blender as a Python module)
Blender compiled as a Python library that can be imported and controlled entirely from scripts without running the Blender GUI. It provides full access to Blender's modeling, physics, and rendering capabilities, but is not thread-safe and must be used from a single thread or isolated process.
freesewing
An open-source JavaScript library for parametric sewing patterns. It generates 2D pattern pieces from body measurements using mathematical models rather than fixed templates, allowing patterns to adapt automatically to different body dimensions.
Weld modifier (Blender)
A Blender modifier that merges nearby vertices within a threshold distance into a single vertex. In cloth simulation workflows, it is used after baking to fuse seam vertices that were pulled together by sewing springs, but it can occasionally leave stray edges that belong to no face.
From the discussion
Featured comments
向新出发叭

The architecture is very clear, and the front-end/back-end separation design is clever. I've run into similar cross-language data interaction problems when building Electron apps before — especially the case you mentioned where stray edges mixed into the OBJ file caused front-end parsing errors. That's way too real: each module looks fine on its own, but when you chain them together, everything blows up. The approach of isolating Blender calls in a Python subprocess is great; it avoids main-process crashes from memory or threading issues. The off-screen window trick for capturing logs and screenshots is also really practical — I've been burned before by the separation of main-process and renderer-process logs in similar apps, and this solution is a solid idea. I'm curious: for the 25-second 3D simulation pipeline, have you considered adding a progress callback? If users could see a percentage while waiting, the experience would be much better.

前端繁华如梦

There is a progress callback — clicking 'generate cloth' shows it in real time.

See top comments, translated →
Source: juejin.cn ↗ Google Translate ↗ Backup ↗