Add SVG-to-3D print designer (Phase 1 MVP) - #82
Open
ayhanars wants to merge 24 commits into
Open
Conversation
Standalone browser app under svg3d-designer/, built with Vite + React + TypeScript + three.js/R3F. Implements the SVG -> 3D -> STL Phase 1 workflow: a Figma-style shell (layer panel, 2D/3D canvas, inspector), SVG import that preserves group hierarchy as nested layers (holes and compound paths resolved correctly), 2D pan/zoom/select/move, per-layer color and independent extrusion depth synced live between the 2D and 3D views, a configurable print bed, and binary STL export built from the same scene graph shown on screen. Lives alongside the existing Ascensor.js jQuery plugin without touching it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC
Builds the app and publishes it to GitHub Pages on every push, so it's reachable at a live URL without anyone needing to clone and run it locally. Sets vite's base path to /Ascensor.js/ only inside Actions (GITHUB_ACTIONS is set automatically there) so local dev/build is unaffected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC
Add SVG-to-3D print designer (Phase 1 MVP)
…sets (#2) Six requested features on top of the Phase 1 MVP: - Light/dark mode, following the OS preference until the user toggles it explicitly (persisted to localStorage); the 3D viewport background follows the theme too. - Merge/flatten layers (Figma's Flatten Selection): combines multiple selected layers, or everything inside one selected group, into a single flat shape with each source's world transform and corner rounding baked in. Handles mixed selections (a group plus its own child, items from different parents) without corrupting the layer tree. - Undo/redo via zundo, wired to Cmd/Ctrl+Z, Shift+Z and toolbar buttons, plus Cmd/Ctrl+D duplicate, Cmd/Ctrl+A select all, Cmd/Ctrl+E merge, Delete/Backspace, and Escape — all skipped while typing in a text field. Continuous gestures (canvas drag, the corner-radius slider) collapse into a single undo step via an explicit begin/end-gesture pause. Selection changes and view-only toggles (2D/3D, grid) are intentionally excluded from history, matching how Figma's undo behaves. - Corner radius (smoothness): rounds every vertex of a shape's contours via tangent-circle fillets, independently clamped per corner so neighboring fillets can never overlap. Applied identically in the 2D preview, 3D view, and STL export — never just a cosmetic preview effect. - Artboard sizing: fit the document to the current selection's bounds, or match it to the configured print bed. - Full Bambu Lab bed presets (X1 Carbon, X1, X1E, P1S, P1P, A1, A1 mini, H2D) replacing the earlier placeholder list. Verified interactively in a real browser (Playwright): import, multi-select merge across different edge cases, corner-radius drag with undo/redo, dark mode, all keyboard shortcuts, and a re-exported STL with a rounded corner confirmed to have a valid binary structure, correct mm-scale bounding box, and zero degenerate triangles. Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC Co-authored-by: Claude <noreply@anthropic.com>
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 2, 2026 13:13
2ddc849 to
3a0832e
Compare
Two real bugs reported after testing: 1. Merging layers behaved like Figma's Exclude instead of Union: it naively concatenated each source shape's outline and rendered with fillRule=evenodd, so any area where two shapes overlapped canceled out into a hole instead of filling solid. Added a real polygon boolean union (via polygon-clipping) so overlapping shapes now combine into one solid blob, matching what "merge" actually means. 2. Layers had no Z position at all — every shape extruded from Z=0, so increasing one layer's depth made it grow through/inside whatever else was also sitting at Z=0 instead of sitting on top of it. Added a `z` field to the transform (always clamped >= 0, so nothing can go through the print bed), wired through the 3D scene graph and world-transform math, a Z field + "Drop to bed" button in the inspector, and an Auto-Stack action that stacks every layer bottom-to-top with no overlap in one click. Verified in a real browser: merging two overlapping circles now produces a single solid contour (1 subpath, no hole) instead of 2 separate subpaths that cancelled out; auto-stacking four layers with different depths (2, 5, 1.2×4mm) produced an exported STL whose Z bounding box was exactly 0-11.8mm, matching the expected cumulative sum. Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC Co-authored-by: Claude <noreply@anthropic.com>
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 2, 2026 13:34
3a0832e to
465f7f3
Compare
Two more bugs found after testing: 1. The Z field was invisible: CSS grid's 1fr tracks default to an "auto" minimum equal to their content's min-content size, and a bare <input type="number">'s browser-default intrinsic width (~170px) is wider than a 3-column track in the 240px sidebar. The 3rd column (Z) was being rendered ~120px past the sidebar's right edge, off-screen, with no horizontal scrollbar to reveal it. Same issue affected the print bed's Width/Depth/Height row. Fixed by letting grid tracks (and their item wrappers) shrink below their content's natural size. 2. Auto-Stack raised every layer's Z cumulatively in paint order regardless of whether it actually overlapped anything in X/Y, so two unrelated shapes on the same background (e.g. separate letters) ended up floating on top of each other instead of resting at the same height. Fixed: each layer's Z is now the tallest top-Z among only the previously-placed layers whose bounding box actually overlaps it. Verified in a real browser: X/Y/Z fields and the bed size row now fit without overflow (confirmed via computed bounding boxes, not just a screenshot). Re-ran auto-stack on the sample badge and checked every layer's resulting Z by hand against its actual overlaps — a background, an unrelated circle and letter sitting at the same height on top of it, and two crossing rects correctly stacking on the one they cross — then confirmed the exported STL's Z bounding box (0-3.6mm) matches exactly. Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC Co-authored-by: Claude <noreply@anthropic.com>
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 2, 2026 14:16
465f7f3 to
1ccbb81
Compare
selection outline, shape tools, space-pan Six requested items: 1. Fixed pinch-zoom hijacking the whole browser page. React attaches wheel listeners as passive by default, so e.preventDefault() inside the old onWheel prop was silently ignored — the browser's own pinch-zoom kept firing alongside ours. Replaced it with a native, non-passive wheel listener, and while at it split the gesture the way Figma does: plain scroll/two-finger swipe now pans, Cmd/Ctrl+scroll or pinch zooms (previously all wheel input was treated as zoom, which is also why panning felt wrong). 2. Fixed number fields that couldn't be cleared to type a new value (e.g. replacing "1" with "30"). They were plain controlled inputs bound straight to store state: clearing the field produced an invalid empty string, which got silently rejected, so React immediately snapped the DOM value back to the old number. NumberField now buffers the raw text locally while focused and only reconciles with the committed value on blur — applied everywhere a number field exists. 3. Added a Wireframe toggle in the 3D toolbar to see through solid surfaces and check nested/occluded geometry. 4. Added a real selected-object highlight in the 3D view: previously nothing indicated selection there at all. Tried the standard "enlarged backface-only shell" outline trick first, but our print pieces are flat and viewed mostly from above, where the dominant visible surface is the top face — so BackSide culls exactly the part that needs to show. Ended up with a same-height, XY-widened duplicate resolved with polygonOffset (the standard fix for coplanar surfaces) so the original always wins inside its own footprint and the widened rim shows past it — verified as a clean colored margin around the selected shape, with no relation to (and not to be confused with) a separate, pre-existing z-fighting artifact between unstacked overlapping layers that surfaced during testing. 5. Added a Figma-style floating shape toolbar (bottom of the 2D canvas) to add a rectangle or circle centered on the artboard. Text isn't included yet — turning a font into printable outlines is a meaningfully bigger feature on its own. 6. Space+drag now pans the 3D camera instead of orbiting, matching the 2D canvas and Figma's own convention, by swapping OrbitControls' left- mouse-button binding while space is held. Verified in a real browser: dispatched synthetic ctrlKey/non-ctrlKey wheel events and confirmed one zooms while the other purely pans (position changes, size doesn't) and both call preventDefault; walked through clear-and-retype on a number field end to end; confirmed the auto-stack + 3D selection outline together with a screenshot showing a clean purple outline around a selected shape once it was no longer coplanar with its neighbor; wireframe toggle screenshot; space+drag smoke test with no page scroll or console errors. Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC Co-authored-by: Claude <noreply@anthropic.com>
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 2, 2026 15:02
1ccbb81 to
2594f04
Compare
…adius fix, and edge bevel (#6) - Grid/Wireframe toolbar buttons now use real toggle-button styling. - Replace the 3D selection outline with a Figma-style bounding-box wireframe plus 8 corner handles, and add click-to-select in the 3D viewport (with shift/cmd/ctrl for additive selection, a drag threshold so orbiting doesn't select, and click-empty-space-to-deselect). - Fix corner-radius (and color/extrusion) editing on imported SVG layers: selecting a group showed no shape-property sections at all, since every top-level imported layer is a group. Selecting a group now shows batch-edit versions that apply to every shape inside it. - Add an independent top/bottom edge bevel (chamfer) — a new, simpler Z-axis edge treatment distinct from the existing 2D corner-radius fillet, with its own single-slider-per-edge UI. Implemented as a purpose-built extrude geometry builder (bevelExtrude.ts) rather than three.js's own ExtrudeGeometry bevel option, since that bevel is always symmetric and expands outward rather than chamfering inward. Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC Co-authored-by: Claude <noreply@anthropic.com>
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 2, 2026 15:25
2594f04 to
9893bc6
Compare
…vel (#7) - Grid and Wireframe are now actual switch controls (pill track + sliding thumb), not just buttons that change color when active. - The edge bevel now builds a proper rounded (quarter-round) fillet subdivided into multiple facets, instead of a single flat 45°-style cut. A single facet reads as an obviously faceted "cut corner"; enough small facets approximating a curve reads as a smooth, rounded edge — the same "Segments" lever a 3D modeler (e.g. Blender's Bevel modifier) would reach for. Same slider, no new UI — only the underlying geometry changed. Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC Co-authored-by: Claude <noreply@anthropic.com>
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 2, 2026 16:30
9893bc6 to
0d016ed
Compare
* Fix concave bottom bevel and 3D selection losing to overlapping large objects - The bottom edge bevel traced a concave (scooped/cove) curve instead of a convex round-over, because its arc was centered on an endpoint instead of on the sharp corner it replaces. Verified both algebraically (compared against the straight-chamfer baseline at several points along the curve) and visually — the bottom now matches the top's round-over shape. - Fix 3D click-to-select losing to a larger overlapping/coplanar object: two root causes. (1) The Figma-style selection handles/edges added in the previous release had no raycast exclusion, so a click landing on one (which has no layerId) silently no-op'd instead of reaching the mesh behind it. (2) Raw depth-sorted raycasting has no tiebreaker for two meshes at the exact same distance (e.g. two shapes sitting flush at the same Z, before Auto-Stack separates them) — R3F's default "closest hit" resolution ends up stably favoring whichever mesh the scene graph happens to traverse first, which in practice is almost always the oldest/background layer. Selection now inspects every intersection within a small depth-tie band and, only among those, prefers the one painted latest — mirroring how the 2D canvas already resolves overlapping clicks (later-added shapes sit on top) — while distinctly-different depths still resolve by real distance as before. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC * Add hole/negative-space shapes with real 3D boolean subtraction Adds a "Use as hole" toggle to any shape layer: instead of printing as its own solid, its extruded volume is subtracted (a genuine 3D boolean difference, via three-bvh-csg) from every solid it overlaps — for magnet wells, screw holes, and similar cut-in features. The hole's own Z position and depth are independent of the solid's, so it can be a shallow pocket or punch all the way through. - New `isHole` flag on ShapeLayer, with a real switch toggle in the Inspector's new "Negative space" section (reusing the toggle-switch component from the toolbar, now shared via ToggleSwitch.tsx). - Core cutting logic lives in geometry/holeSubtraction.ts: after the normal scene graph is built, every solid mesh overlapping (by world-space bounding box) a hole mesh has the hole's volume subtracted via three-bvh-csg's Evaluator, chaining multiple overlapping holes against the same solid in sequence. The CSG library's own frame-of-reference convention (the result comes back in brush A's frame) means the result can be dropped in with the solid's exact original world transform, no extra local/world conversion needed. - Hole shapes are shown as a translucent red overlay in the 3D preview (both solid-shaded and, distinctly colored, in wireframe) so the negative space stays visible while editing, and as a dashed red-translucent overlay in the 2D canvas — but the overlay is preview only: STL export always omits it, since by then its volume has already been cut from whatever it overlapped. Verified: a plate with a hole shows a real cavity with visible depth (not just a flat colored circle) in the 3D preview; wireframe shows the hole in a clearly distinct color; the exported STL's Z bounding box tops out at the plate's own depth (not the taller hole cutter), confirming the hole never appears as separate printable geometry while the real cut is baked into the solid's mesh (768 triangles, zero NaN vertices, valid binary STL). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC --------- Co-authored-by: Claude <noreply@anthropic.com>
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 2, 2026 23:07
0fcfa46 to
eb29d98
Compare
#9) Four requested additions: - Align: Figma-style left/center/right and top/middle/bottom align buttons in the Inspector. A single selection aligns to the artboard; several align to each other's combined bounding box. Implemented as a pure translation on top of the existing world-transform math, so nested/ rotated/scaled layers still land correctly. - Group Selection (Cmd/Ctrl+G), distinct from the existing Merge/flatten (Cmd/Ctrl+E): nests the selection under a new group while keeping every shape independently editable, unlike Merge which destroys individual shape identity. Ungroup (Cmd/Ctrl+Shift+G) is the inverse. Both rebase each reparented layer's transform (position, rotation, and non-uniform scale) so nothing visibly moves — verified with an identical before/ after screenshot. - Units: the Units dropdown was previously disabled/mm-only. It's now a real display/input conversion layer (mm <-> cm/in) applied to every physical-length field (position, bed size, extrusion depth, corner radius, bevel amounts) — Scale and Rotation are untouched since they aren't lengths. All internal storage and geometry math (extrusion, STL export) stay in mm exactly as before; verified the mm<->in round-trip is exact (10in -> 254mm). - Curve smoothness: corner-radius fillets were using only ~4 straight segments for a 90° corner (ARC_SEGMENTS_PER_HALF_TURN=8), which is genuinely low-poly in the exported STL too, not just on screen — an STL is a real polygon mesh, so an under-tessellated curve prints faceted. Raised to 32 (~16 segments per 90°) and bumped the shape-tool circle from 48 to 64 segments; verified visually that curves are now smooth even zoomed in close, matching what will actually come off the printer. Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC Co-authored-by: Claude <noreply@anthropic.com>
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 3, 2026 05:40
eb29d98 to
c269ed3
Compare
Holes (e.g. for embedding magnets) are usually sunk almost to the bed with just a thin floor left, not punched straight through. Adds a "Snap to recessed pocket" action that repositions a hole to leave a configurable floor thickness above whatever it overlaps (defaulting to the ~0.6mm/3-layer recommendation) while still cutting fully through the top, plus diameter/thickness presets (common magnet sizes, ISO 273 screw clearance holes) so users can pick a standard size instead of hand-tuning scale and depth each time. Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC Co-authored-by: Claude <noreply@anthropic.com>
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 3, 2026 08:06
c269ed3 to
9a3d145
Compare
…ion (#11) Any "apply to all" edit — the multi-select Color swatch, or any property edited while a group is selected (Color, Extrusion, Corners, Negative space, the new hole presets/snap) — called its store setter once per shape via forEach, and each call was its own tracked history entry. A single Ctrl+Z then only reverted the last shape touched, and since the Inspector's fields mirror the first target rather than the last, that one undo usually looked like it did nothing at all. Wraps every such forEach in a shared applyToAll() helper that reuses the existing pause/mutate/resume gesture (already used for slider drags) so the whole batch collapses into exactly one undo step, matching how a single-shape edit already behaves. The three range sliders (corner radius, bevel top/bottom) are left untouched — they already bundle correctly via their own pointerdown/pointerup gesture. Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC Co-authored-by: Claude <noreply@anthropic.com>
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 3, 2026 09:43
9a3d145 to
11c5dda
Compare
1. Color swatch drag still broke undo: the native color picker fires many `input` events while you drag inside it (React's onChange is bound to that, not the once-only `change` event), and each one was still its own tracked history entry even after the earlier batch-undo fix. A single Ctrl+Z only undid the last, usually-imperceptible increment, which is exactly what looked like "undo doesn't work" for a whole-group recolor. Added ColorSwatchInput, which bundles the entire pick gesture (pointerdown through the native `change` event) into one undo step, the same way the range sliders already do for a drag. 2. Added a Figma-style cursor/select tool icon to the bottom toolbar (highlighted whenever nothing is selected), and real marquee (rubber-band) selection: dragging over empty canvas now draws a selection box and selects every top-level shape/group it overlaps, instead of only ever being able to select multiple objects one shift-click at a time. Since drag-on-empty-canvas is now marquee select, panning moved to Space+drag (matching the 3D viewport's own convention) alongside the existing scroll/trackpad-swipe pan. 3. Ctrl/Cmd+scroll and pinch-zoom sensitivity was tuned so low a single wheel notch or a full pinch gesture barely changed the zoom level. Raised it 3x, and added Figma's Z (zoom in) / Option+Z (zoom out) keyboard shortcuts alongside the universal +/- convention. Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC Co-authored-by: Claude <noreply@anthropic.com>
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 3, 2026 11:02
11c5dda to
739626d
Compare
1. Selection outline was invisible for any plain, unrotated shape: its 1px non-scaling-stroke sat exactly coincident with the shape's own fill edge (an accent-blue outline against an indigo fill), so it anti-aliased into nothing. A circle's square bounding box happened to show because its corners poke past the round fill into bare background — which is what made marquee-selecting two shapes look like "only one object selected." Outset the outline a hair past the shape's edge and widened it to 2px so it's unambiguously visible regardless of what's under it. 2. The hex color text field committed every raw keystroke straight to the layer, so clearing it to retype, or a partial/invalid hex typed along the way, applied garbage colors mid-type. HexColorInput now buffers the raw typed text (matching NumberField's existing pattern) and only commits once it's a complete, valid hex color, reconciling back to the real value on blur. 3. Z-zoom had two real bugs: the keydown handler had no e.repeat guard, so just holding Z let the OS's own key-repeat fire dozens of zoom steps a second, compounding into a runaway zoom in well under a second. Replaced it with Figma's actual behavior — holding Z (Option +Z to zoom out) swaps the cursor to a magnifying glass and does nothing by itself; a click on the artboard zooms one step centered on exactly where you clicked. (Found and fixed a second bug in the same change: a click landing on a shape bubbled the event up to the canvas's own handler too, applying the zoom twice.) Also added Cmd/Ctrl+0 to reset to a true 100%. Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC Co-authored-by: Claude <noreply@anthropic.com>
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 3, 2026 11:29
739626d to
a3735d7
Compare
…ets (#14) 1. Dragging a shape that's inside a currently-selected group moved just that one shape, silently narrowing the selection down from the whole group — every canvas drag started from the raw shape under the cursor, and a group was never itself a draggable target. Clicking any member of a group now resolves to its outermost group ancestor first (new getTopLevelId), so the whole group selects and moves together, matching how Figma treats a click on a group's member as a click on the group unless you've drilled in. 2. The Z-zoom tool could get stuck armed (magnifier cursor + zoom-on -click) even after both keys were released. Root cause: the keyup handler matched e.key === "z"/"Z", but macOS remaps the character a held Option produces (e.g. to "Ω") — so releasing Z while Option was still down never matched, and zoomToolArmed never cleared. Switched to e.code (the physical key, immune to modifier remapping), the same fix already used for Space elsewhere in this file. Also fixed a layout bug (`right: 0` doesn't right-align in this container) that the hue slider crossed while investigating. 3. The hole diameter/thickness preset dropdowns always snapped back to "Common sizes…" after picking one, giving no visible confirmation of what was actually applied. They now show the picked size. 4. Replaced the OS's native color-wheel dialog with a real in-app Figma-style picker: a saturation/value square, a hue strip, and a hex field in a popover anchored off the swatch. Positioned as position: fixed with the trigger's screen rect measured and clamped to the viewport on open — a plain CSS-anchored popover either spilled off the browser edge or got clipped by the Inspector panel's own scroll clipping, depending on which swatch (the single-shape Color row vs. the multi-select "apply to all" row) opened it. The whole open-to -close session still collapses into one undo step via the existing gesture pattern. Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC Co-authored-by: Claude <noreply@anthropic.com>
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 3, 2026 11:53
a3735d7 to
b2873fb
Compare
…ts (#15) 1. Restored the format-switcher dropdown (Hex/RGB/CSS/HSL/HSB) in the color picker that the previous Figma-style rebuild had dropped in favor of just Hex — the request was for the full Figma dropdown, not a subset. Editing any format's fields updates the same underlying color and stays inside the picker's existing one-undo-step-per-session gesture. No alpha field: shape colors are plain opaque hex, there's no opacity channel in the data model to edit. 2. Clicking on a group's member now steps one level deeper into nested sub-groups each time you click it again (through as many levels as there are, down to the leaf shape) — Figma's "click to select the group, click again to work on what's inside it," generalized past a single level. Getting this right without breaking last round's "drag moves the whole group" fix took deferring the decision to pointerup: a click that lands within the already-selected group/shape keeps that selection as-is for the gesture, and ONLY resolves as a "step deeper" click if the pointer turns out not to have moved at all — otherwise a click that turned into a drag would drill in immediately instead of moving the whole thing, reintroducing the exact bug fixed last round. 3. Added the Bambu Lab models missing from the bed-size presets: X2D, P2S, A2L, and H2C, alongside the existing X1/X1E/P1S/P1P/A1/A1 mini/ H2D (kept for anyone with existing documents sized to them). Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC Co-authored-by: Claude <noreply@anthropic.com>
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 3, 2026 12:09
b2873fb to
99eb115
Compare
… groups (#16) 1. Merging shapes inside a group that itself had any position/rotation/ scale bakes each source's points into world/absolute space for the union math, then stores the result with an identity transform of its own — correct only when the merged shape has no parent. As a CHILD of the group, that same parent transform gets applied a second time at render, visibly shifting the merged shape away from where the sources actually were. Added invertTransform2D (the inverse of the existing applyTransform2D) and, when the merge target has a parent, re-express the unioned points relative to that parent instead of leaving them in raw world space — the same "world space for the math, parent-local for storage" pattern rebaseWorldToParent already uses for reparenting. 2. Shift-click always resolved to the outermost group (added by the "drag moves the whole group together" fix a couple of rounds back), so shift-clicking two individual children of the same group to build a custom multi-selection just toggled the parent group twice instead. rawId in this handler is always an individual leaf shape's own id already — only shapes have a pointer handler, a group is an invisible <g> wrapper — so shift-click now toggles exactly that shape, no top-level resolution at all (unlike a plain click, which still climbs to the group and drills back in on repeat clicks). Also guards against the resulting selection ever holding both a group and one of its own descendants at once (an ancestor already selected is dropped in favor of the more specific child), the same invariant merge/group/duplicate already enforce — that combination would double-apply a drag to the descendant. Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC Co-authored-by: Claude <noreply@anthropic.com>
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 3, 2026 13:46
99eb115 to
1c38af3
Compare
…, hole styling (#17) * Add 3D multi-drag, toast feedback, copy/paste shortcuts, align guides, hole styling - Viewport3D: shapes (single, multi-select, or grouped) can now be dragged in the 3D view without requiring grouping first, bundled into one undo step, using a Y=0 plane raycast mapped back to document x/y. - Canvas2D: the always-on control hint is now a small info icon next to the zoom indicator, shown on demand instead of eating canvas space. - New toast notifications (bottom-center, auto-dismissing) confirm merge, group/ungroup, duplicate, delete, copy/paste, import, and export actions. - Global Cmd/Ctrl+C and Cmd/Ctrl+V shortcuts copy/paste the current selection from anywhere, not just the layer panel's duplicate button. - Canvas2D: dragging shapes now shows dashed pink alignment guides when an edge or center lines up with another shape, distinct from the blue selection outline. - LayerPanel: hole/negative-space layers are now color-coded red, matching their canvas styling. - ShapeToolbar: a new hole tool button creates a shape pre-marked as negative space, matching the existing Inspector hole toggle's behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC * Add 3MF export with per-shape colors for slicer compatibility STL is pure geometry with no color channel, so Bambu Studio (or any slicer) has no way to know a shape's color from an STL export. Add a 3MF export option alongside STL: one file containing a separate object per shape, each carrying its own color via the 3MF core spec's basematerials/pindex, with every object's real position preserved (no manual re-assembly needed like exporting several STLs would require). Uses three.js's bundled fflate (already used internally by USDZExporter) to build the ZIP container, so no new dependency is needed. The top toolbar's single Export button is now a small dropdown offering STL or 3MF. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC --------- Co-authored-by: Claude <noreply@anthropic.com>
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 3, 2026 14:48
b9c2d48 to
17f13e2
Compare
…tor (#18) 1. Canvas2D: a plain click-drag on a member of an existing multi-selection (built via shift-click or marquee) fell through to a fallback that always collapsed the selection down to just the clicked shape before the drag even started, so dragging one of several selected shapes silently deselected the rest instead of moving them together. Generalized the existing single-selection "click within the already-selected group keeps the selection for the drag" check to any selection size, matching what 3D already did. 2. autoStackLayers used a bounding-box overlap test as a proxy for "does this shape rest on that one," which false-positives whenever two shapes' rectangular bounds touch but their real outlines don't (e.g. two circles near the same corner) or only partially overlap — lifting a shape to sit on something it isn't actually resting on, leaving part of it floating in mid-air with nothing underneath. Switched to real polygon intersection (via polygon-clipping, already a dependency) for both the overlap test and a new support-coverage check: if a shape ends up raised but its own footprint isn't ~fully covered by whatever raised it, a toast now names it as possibly floating. 3. threemf.ts's color export never worked in Bambu Studio: its foreign-3MF color importer (per BambuStudio's own bbs_3mf.cpp source) only reads the Materials Extension's <m:colorgroup>/<m:color>, not the 3MF core spec's <basematerials>/displaycolor this exporter was emitting. Switched to m:colorgroup so colors actually carry over. 4. Inspector: the Corners and Edge bevel sections now collapse to just their title + a small "+" when unset (matching Figma's own pattern for optional properties), only expanding to their full slider/field rows once added (or if already non-zero) — clicking "+" sets a real starting value, "−" resets to 0 and collapses again — freeing vertical space so more of the panel fits without scrolling. Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC Co-authored-by: Claude <noreply@anthropic.com>
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 3, 2026 18:56
17f13e2 to
6bc8b52
Compare
… add boolean ops and bed-preset pin (#19) - LayerPanel: dragging one of several selected layers into a folder only moved that single row, and drop-target feedback was a uniform highlighted border regardless of whether you'd land above/below/inside a row. Added moveLayers (replacing moveLayer) so the whole current selection moves together and reparenting preserves each shape's world position (it never did), plus distinct above/below/inside drop indicators (an insertion line vs. a filled row) matching Figma/VS Code. - deleteSelection's toast counted only top-level selected items, so deleting one folder with several shapes inside it reported "Deleted 1 layer". Now counts every layer actually removed, descendants included. - Root-caused corner radius silently not rounding on imported SVG shapes: a closed SVG path stores an explicit point at both its start and end (our own contour representation already treats the list as an implicit cycle), and that leftover duplicate makes the corner at that index look like a zero-length edge, silently skipping it — hand-drawn shapes never had this duplicate, which is why only imported ones were affected. roundContour now dedupes consecutive coincident points (including the wraparound pair) before rounding. - Edge bevel's per-vertex offset gives a sharp polygon corner exactly one mitered point to sweep the whole round-over curve through, instead of a real arc (three.js's own ExtrudeGeometry bevel has this same limitation). Pre-rounds each corner into a short arc (sized off the bevel amount, via the same roundContour used for the shape's own corner-radius) before computing bevel movement vectors, so corners get several offset directions to sweep through instead of one. - threemf.ts now filters degenerate/zero-area triangles and non-finite vertices instead of exporting them verbatim, and skips an object entirely rather than emitting an empty (spec-invalid) one — hardening against the geometry edge cases (bevel/CSG seams) that could produce a 3MF a stricter slicer parser rejects. - Auto-Stack's floating-shape warning now auto-selects the affected shapes (visible immediately on canvas) and posts as a persistent, click-to -dismiss toast instead of a 2.6s auto-vanishing one. - Added a Figma-style Boolean ops dropdown (Union/Subtract/Intersect/ Exclude) next to Group Selection for multi-selections, backed by real polygon-clipping difference/intersection/xor operations alongside the existing union-based Merge. - Added a bookmark toggle next to the bed-size preset picker to pin a default Bambu printer bed size that new projects (and the app itself, on load) always open with, instead of always defaulting to the X1 Carbon. Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC Co-authored-by: Claude <noreply@anthropic.com>
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 3, 2026 19:41
6bc8b52 to
45c9989
Compare
… floating-shape warning (#20) - Bed preset dropdown was matching by width/depth, and six Bambu presets share identical 256x256x256 dimensions, so it always snapped back to whichever came first (X1 Carbon). Match on bed.name first instead. - Selecting a bed preset now auto-matches the artboard to it in one step; removed the now-redundant "Match print bed size" button. - Reordering layers in the layer panel bubbled its drop event up to the app-level OS-file-drop handler, popping a spurious "Drop a .svg file to import it" error. Stop propagation on the row's own drop handler. - Space+drag now pans even when the pointer comes down on a shape (the shape's own pointerdown handler ran first and never deferred to the canvas-level pan check). - Removed "Fit artboard to selection" (both instances). - Boolean ops (Union/Subtract/Intersect/Exclude) are now an always-visible 2x2 button row instead of a dropdown. - Added a persistent, debounced banner that flags shapes with no real support underneath them (independent of auto-stack) with Select/Fix actions; fixing respects layer stacking order so raising a background shape doesn't invert it on top of what it's supposed to support. Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC Co-authored-by: Claude <noreply@anthropic.com>
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 3, 2026 20:20
45c9989 to
b3ff24c
Compare
) defaultDocument() correctly set document.bed to the pinned preset, but widthMM/heightMM were still hardcoded to the plain 100x100 default — so opening a fresh project carried the right printer but the wrong-size artboard, only "half" honoring the pin. Size the artboard to the pinned preset's dimensions too, same as any other bed-preset selection does. Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC Co-authored-by: Claude <noreply@anthropic.com>
A project can now hold several print-bed "plates" instead of just one artboard's worth of objects — for when a set of pieces don't fit together on one plate, or should just print as separate jobs, without having to start a whole new project for the overflow. - Plate tabs (bottom-left of the canvas) to switch, add, rename, and delete plates. All plates share the project's single bed/printer size. - The 2D canvas, 3D viewport, and layer panel now only show the active plate's objects; drag a row from the layer panel onto a tab to move it there. - Auto-Stack, the floating-shape warning, and Select All are scoped to the active plate only — a different plate is a different physical bed, so its objects have nothing to do with these. - Export STL/3MF now export the current plate; a new "Export All Plates" zips one file per non-empty plate. - New objects (import, paste, draw, and anything a boolean op/merge/ group/ungroup produces at the top level) land on whichever plate is currently active; deleting a plate deletes its objects with it (confirmed first when it isn't empty), and undo/redo covers plate structure the same as any other document edit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC
ayhanars
force-pushed
the
claude/svg-3d-print-designer-motvtz
branch
from
September 3, 2026 22:35
b3ff24c to
05721e9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Standalone browser app under svg3d-designer/, built with Vite + React + TypeScript + three.js/R3F. Implements the SVG -> 3D -> STL Phase 1 workflow: a Figma-style shell (layer panel, 2D/3D canvas, inspector), SVG import that preserves group hierarchy as nested layers (holes and compound paths resolved correctly), 2D pan/zoom/select/move, per-layer color and independent extrusion depth synced live between the 2D and 3D views, a configurable print bed, and binary STL export built from the same scene graph shown on screen.
Lives alongside the existing Ascensor.js jQuery plugin without touching it.
Claude-Session: https://claude.ai/code/session_01TiHJref4EjcyprLsYyGBvC