aec4aa2b5a346a4d3f40ff67bf5d3e19e304682d
376
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ab76686b8b |
editor: improve floorplan modes and annotations (#549)
* Add roof surface placement support for items Items (e.g. solar panels) can now be placed on sloped roof surfaces. The placement system computes euler rotation from the roof surface normal so items sit flush on the slope instead of going inside. - Add roofStrategy to placement-strategies with enter/move/click/leave - Wire roof:enter/move/click/leave events in the placement coordinator - Add calculateRoofRotation in placement-math using surface normals - Support full 3D cursor rotation for sloped surfaces - Items on roofs are parented to the level with world-space rotation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fixed conflict * fix(editor): anchor floorplan cursor to snapped point * fix(nodes): preview floorplan edits through live overrides * feat(editor): add context-aware floorplan modes * fix(nodes): render crisp wall selection hatching * fix(editor): cap floorplan handles at extreme zoom * fix(editor): keep zone labels upright after rotation * refactor(editor): make referenced annotations registry-driven * refactor(nodes): colocate contextual dimension builders * fix(editor): use mode-driven angle snapping * fix(nodes): use mode-driven move snapping * chore(editor): update react scan tooling * refactor(floorplan): streamline construction documentation * refactor(floorplan): remove wall assembly roadmap * fix(floorplan): migrate retired scene data --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
77442861d9 |
editor: complete floorplan construction documentation (#531)
* Add roof surface placement support for items Items (e.g. solar panels) can now be placed on sloped roof surfaces. The placement system computes euler rotation from the roof surface normal so items sit flush on the slope instead of going inside. - Add roofStrategy to placement-strategies with enter/move/click/leave - Wire roof:enter/move/click/leave events in the placement coordinator - Add calculateRoofRotation in placement-math using surface normals - Support full 3D cursor rotation for sloped surfaces - Items on roofs are parented to the level with world-space rotation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fixed conflict * feat(floorplan): add construction dimension strings * feat(floorplan): coordinate opening dimensions * feat(floorplan): add opening documentation * feat(floorplan): add construction dimensions and notes * feat(floorplan): add interior dimensions and curved note leaders * feat(floorplan): improve construction dimensions and document plan * feat(floorplan): harden construction document output * feat(floorplan): add annotation collision diagnostics * feat(floorplan): size export annotations in paper space * feat(floorplan): automatically separate overlapping labels * fix(floorplan): resolve dense label overlaps * fix(floorplan): remove stale collision warning overlays * fix(floorplan): treat mark pills as collision obstacles * feat(floorplan): place short dimension values outside * fix(floorplan): preserve dimension string order * fix(floorplan): avoid architectural geometry in label layout * feat(floorplan): add dimension side fallback leaders * docs(floorplan): update chapter 17 implementation status * fix(floorplan): dimension subdivided interior walls * feat: add associative floor plan dimensions * feat: add continuous construction dimension strings * feat: add structural floor plan grids * feat: coordinate columns with structural grids Snap column placement and movement to structural axes and intersections, derive associative grid references, and preserve floor-plan rotation by allowing secondary-button pointer moves through the grid drafting layer. * feat: add architectural room documentation Add room-role metadata, editable documentation fields, centered room labels, and persisted live/PDF visibility while preserving generic zone behavior. * feat: generate architectural room schedules Add registry-driven room schedule rows with unit-aware areas and heights, natural room ordering, enclosure resolution, and document-quality warnings. * feat: add reliable room clear dimensions Derive unit-aware clear dimensions from proven modeled inside wall faces for straight rectangular rooms, including rotated and split-wall enclosures, while suppressing unproven datums. * feat: add architectural stair documentation Add level-aware UP/DN graphics, derived flight and rail notes, plan break and overhead conventions, linked destination-level projection, and persisted live/PDF visibility. * feat: add typed specialty construction notes Add schema-validated specialty payloads, standardized plan notation, contract-scope metadata, configurable overhead outlines, and editor authoring controls. * feat: add curved and circular dimensions Add associative radius, diameter, center, chord, arc-length, angular, and coordinate modes with unit-aware notation, repeated-feature labels, 2D authoring, and document controls. * feat: coordinate floor plan drawing types Add persistent floor, foundation, reflected-ceiling, roof, and site plan views with per-dimension show, omit, reference, and foundation-controller behavior across live and PDF output. * feat: add associative curved wall dimensions Bind radius, center, chord, arc-length, and angular construction dimensions directly to curved wall geometry so annotations update when the host curve changes. * fix: render automatic curved wall dimensions The wall floor-plan builder explicitly skipped curved walls, leaving the associative authoring workflow as the only dimension path. Render a concentric arc-length dimension automatically and keep it governed by automatic-dimension visibility. * fix: use radius callout for curved walls Replace the automatic arc-length annotation with the source-standard radius method: computed center mark, radial leader, curve arrow, and R value. Keep adjacent linear strings responsible for locating the curve tangencies and depth. * Implement construction dimension string editing * Add construction dimension standards controls * Apply drawing standards to automatic dimensions * Add floorplan overhead and reference visibility controls * Add view-specific dimension segment suppression * Add persistent drawing sheet model * Plot floorplan exports at fixed scale * Apply paper-space annotation profiles * Compose floorplan PDF sheets * Support sheet paper sizes and preflight * Persist pinned annotation layout overrides * Expand annotation collision obstacles * Add floorplan annotation preflight surface * Add reusable drawing sheet general notes * Add drawing sheet keyed note instances * Add drawing sheet document markers * Expand construction note leader terminators * Add wall assembly layer model * Resolve wall assembly datum references * Add wall assembly floorplan graphics * Add opening documentation dimension policies * Add finish-face room clear dimensions * Extend room clear dimensions to rectilinear rooms * Add construction module advisories * Add clearance advisory profiles * Add dimension completeness audit * Expand dimension completeness audit * Include preflight issues in completeness audit * feat: complete floorplan construction documentation * refactor: remove construction note node * feat: refine floorplan documentation and unit display * fix(editor): improve floorplan PDF dimensions * fix(floorplan): refresh annotation collision layout * fix(floorplan): keep annotations clear and restore registry boundaries * feat(floorplan): refine construction dimension references * fix(floorplan): align documentation tools with architecture --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
de13119f86 |
docs(wiki): vertical-model architecture page; drop stale SlabSystem rows (#528)
Documents the shipped vertical building model (#526): stored truth table, resolution helpers, clamp rules, pointer-decided placement, the load migration that lives in migrateNodes indefinitely, and the gotchas. The systems tables stop listing the deleted SlabSystem and point at the registry geometry path instead. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
59a67e68ca |
chore: add a verified Bun devcontainer (#520)
* add devcontainer setup and change default port to 3002 * fix(devcontainer): enforce frozen install --------- Co-authored-by: victor <victor@elalemanyo.de> |
||
|
|
ae87ca5475 |
feat: add production measurement tools (#505)
* feat: add persistent measurement tools * feat: make measurements associative * fix: finish measurements with Escape * feat: improve measurement snapping guides * feat: clarify measurement axis feedback * feat: smart measure lens, zone reports, and direct measurement editing - Smart measurement lens: registry-owned wall/slab/zone hover reports with a single top-center HUD, click-to-pin, latest-event back pressure, and no scene writes - Conservative derived zone quantities (footprint, perimeter, proven enclosure, gross wall/floor surface, flat-room volume) with the selected-zone blueprint panel - Direct editing of committed measurements via selected-only 2D/3D vertex affordances with midpoint insertion, cancellation, and one-write history - Shared measurement surface-query session; 2D tracing joins the slab/ceiling magnetic pipeline with registered-corner snapping - Angle arcs on the smaller angle, indigo active/black resting hierarchy, screen-sized normal-aligned contact rings Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: make measurement snapping always magnetic Measurement drafting and committed-edit paths gated wall, semantic, and axis magnetism on isMagneticSnapActive(), which is only true in the 'lines' snapping mode — in the default 'grid' mode corners and wall intersections barely attracted (3D association fell to the 0.012 m verify tolerance, 2D wall radii to the 0.05 m connect stick). Measurement is an analysis tool whose anchors exist to bind real geometry, so its snapping no longer consults the construction snapping-mode chip: 2D/3D drafting and committed vertex edits are always magnetic, Alt is the temporary bypass in both views (releasing the axis pull, wall magnetism, and the 2D projected-geometry pull, and shrinking association to contact tolerance). A discrete 2D wall snap (endpoint / midpoint / crossing) now outranks the locked axis pull, and committed 2D edits route the fallback through the raw pointer so free drags no longer quantize to the construction grid. Volume extrusion height keeps its mode-driven grid quantize. Codex adversarial review confirmed the diagnosis and plumbing; its 2D Alt-depth and grid-quantize findings are applied. New surface-plan-snap tests pin the magnetic override seam. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(measurement): stabilize area surface intent --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9ca3eaa7fe | feat: add project plugin management (#501) | ||
|
|
a590747748 |
Adjustments pass: three 0.185, undo/cancel semantics, guide & panel fixes (#496)
* fix(editor): only start handle drags on primary button Right-click over a rotation/move/resize handle started the gesture and stopPropagation()'d, fighting the camera orbit. Guard every gesture starter (shared useHandleDrag, group rotate gizmo, wall endpoint/height/ move, fence move, roof trim) with event.button !== 0 before it swallows the event. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(viewer): default units from timezone/locale until user picks Derive the metric/imperial default from the IANA timezone (US, Liberia, Myanmar zones -> imperial; anything else -> metric), falling back to an explicit locale region subtag only when no timezone resolves. Timezone tracks actual location, unlike navigator.language where en-US is a common default far outside the US. The unit is only persisted once the user explicitly sets it, so an untouched preference keeps tracking location; existing persisted values are treated as explicit and left alone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(viewer): toggle shadows via renderer.shadowMap.enabled, not castShadow Flipping a light's castShadow at runtime crashes three r184's WebGPU renderer: toggling off disposes the shadow map's GPU texture, but the node builder cache evicts with the post-toggle key, so the shadows-on entry survives still referencing the destroyed texture. Re-enabling reuses that stale state and every frame submit fails with GPUValidationError ("Invalid CommandBuffer from CommandEncoder"). Keep castShadow static and drive the user-facing toggle through the Canvas shadows prop (renderer.shadowMap.enabled), which rebuilds materials without disposing shadow resources. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(core,editor): floor undo history at scene load Undo could step back past the scene load into the pre-load (empty) state, wiping the whole project — which autosave would then persist. Two defects: clearSceneHistory() had zero call sites, so every load left the empty pre-load state in zundo's pastStates; and setScene wrote the store twice, recording a half-normalized intermediate as a second undo target. - applySceneGraphToEditor, JSON import, and reset-to-default now clear history so the loaded scene is the undo floor - setScene collapses to a single tracked write (final state identical) - clearSceneHistory also resumes tracking so a load landing inside a pause window can't strand undo recording off Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * build(three): upgrade runtime to 0.185.1, pin @types/three at 0.184 r185 renames directionToColor/colorToDirection to packNormalToRGB/ unpackRGBToNormal and splits SSGI's packed rgba output into separate AO (getAONode, single channel) and GI (getGINode) textures; wind-node's positionLocal reads become positionGeometry. @types/three stays at 0.184.1: the 0.185 typings send tsgo's inference into unbounded allocation (microsoft/typescript-go#2125 class — it ate ~70GB/90s and OOM-killed the machine). viewer/lib/tsl-compat.ts bridges the two renamed TSL exports with 0.184-typed signatures; drop it and the pin together once tsgo copes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(viewer): gate .ktx2 preset-texture loads on detectSupport KTX2Loader.load throws before detectSupport has run, and materials created while a standalone capture canvas's renderer was still initializing cached themselves permanently texture-less — fabric slots rendered white in item thumbnails. .ktx2 loads now await whenKtx2Ready() (resolved by the first successful ensureKtx2Support), which is exported so hosts with standalone canvases can arm it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(viewer): accept host-supplied country for the unit default applyCountryUnitDefault lets the host app feed an authoritative IP-derived country (e.g. Vercel's x-vercel-ip-country) into the unit default. Stronger signal than the timezone heuristic applied at store creation, still never overrides an explicit user choice. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): box select starts over locked guide images A locked guide's hit-rect swallowed pointer-down via stopPropagation, so marquee selection couldn't start on top of it. Locked guides now let the event bubble to the svg root; click-to-select and the unlock affordance still work because a non-drag release fires onClick as before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): live rotation readout while rotating a guide image Rotating a guide with the 2D handles gave no angle feedback. Reuse the registry layer's RotationAngleOverlay (wedge + degree chip) for guide rotate drags: sweeps from the grabbed corner's bearing at grab to its current snapped bearing, suppressed under ~0.5deg so a fresh grab doesn't flash a sliver. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): lingo free-text input in the guide set-scale dialog The real-length field accepts natural measurement text via @pascal-app/lingo — 5'11", 180cm, 1m80, 12ft — parsed in the dropdown's unit (a bare number still means that unit, a typed unit wins). A faint '= 1.80 m' hint previews non-trivial input, unparseable text gets a clear error, and the odd onBlur force-reset to 0.0001 is gone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): cmd+z mid-interaction cancels the gesture instead of undoing Undo pressed while the mouse is mid-action (moving, drawing, dragging a handle) used to history-jump under the live pointer — stale carry, half gestures committing against a rewound scene. Now it reads as 'abort this action', exactly like Escape: - the global undo/redo arms first route through the tool:cancel path (covers build drafts, placement ghosts, move tools) and skip the history jump when anything was in flight (consumed, scope-active, or inputDragging); - pointer drags that only knew pointercancel (generic handle drags, group rotate, wall side/height handles, roof trim) gain the same capture-phase Escape/cmd+z keydown the group-move drags already had — fixing Escape for them too; - the existing capture-phase handlers (3D/2D group move, 2D registry move overlay) additionally accept cmd+z as cancel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): box select arms over any guide image that won't drag Follow-up to cd830279, which only let LOCKED guides bubble pointer-down. An unlocked, unselected guide also swallowed the event for nothing (no translate drag starts), so marquee selection could never start on top of it. Now only the one case that uses the event consumes it — selected + unlocked → translate drag — and everything else bubbles to the svg root. Click-to-select still works: a non-drag release never crosses the box-select threshold, so the trailing click fires the guide's onClick. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): cmd+z during draft placement cancels instead of undoing The preset/item placement flow (useDraftNode + placement coordinator) registers no interaction scope and holds no pointer, so the cmd+z cancel guard from c699d74e saw it as idle and history-jumped mid-placement. Paused scene history is the universal tell — the draft cycle (and every adopted-move session) keeps temporal paused for the whole gesture, and an undo against a paused store lands on a stale baseline anyway. Treat !isTracking as in-flight. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): cmd+z mid-placement completes the cancel, not just skips undo 4ffda723 stopped the history jump during preset/item placement but left the draft alive: the item tool passes no coordinator onCancel — it is Escape's fall-through (switch to select, unmount the tool) that actually destroys the draft. Extract that fall-through and run it from the cmd+z path too whenever a gesture is live and nothing consumed tool:cancel, so cmd+z now behaves exactly like Escape end to end. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): node selection clears a lingering reference selection Selecting a guide clears the node selection (handleGuideSelect), but the reverse was never wired: clicking a wall with a floorplan reference selected left selectedReferenceId set, and the panel manager's reference-first priority kept showing the floorplan panel until it was closed by hand. PanelManager now drops the stale reference the moment a scene selection (nodes or zone) appears. Also: the inspector's expanded state is shared across panel swaps by design, but it survived close/reopen too — deselecting everything now resets it, so a fresh selection opens the panel collapsed again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(plugin-trees): wind displacement reads positionLocal, not positionGeometry The three-0.185 migration renamed positionLocal to positionGeometry in the wind nodes, but positionLocal was never removed in r185 — and the two are not interchangeable here. NodeMaterial.setupPosition applies the instance transform by mutating positionLocal, then overwrites it with positionNode's output; reading raw positionGeometry therefore discarded every instance matrix — leaf cards rendered unscaled at tree-local coordinates (a giant canopy filling the sky) and grass/flower instances collapsed invisibly. Reading positionLocal (instance transform included) restores r184 behavior exactly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c45808840c |
nodes: add modular cabinets and wall trim controls (#461)
* Add roof surface placement support for items Items (e.g. solar panels) can now be placed on sloped roof surfaces. The placement system computes euler rotation from the roof surface normal so items sit flush on the slope instead of going inside. - Add roofStrategy to placement-strategies with enter/move/click/leave - Wire roof:enter/move/click/leave events in the placement coordinator - Add calculateRoofRotation in placement-math using surface normals - Support full 3D cursor rotation for sloped surfaces - Items on roofs are parented to the level with world-space rotation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fixed conflict * Add modular cabinet node * Add modular cabinet run and tall cabinet workflows * Add cabinet paint slots and material groups * Improve modular cabinet presets and wall snapping * Improve cabinet wall snapping * Fix cabinet wall snapping and countertop joins * Improve cabinet appliance compartments * Improve fridge visuals and smooth cabinet animations Fridge exterior is now brushed silver with brass accents and the interior is all white. Door/drawer animation no longer rebuilds the full cabinet geometry per frame — animated parts carry pose metadata that a new per-frame cabinet system applies directly to transforms. Co-Authored-By: Claude <noreply@anthropic.com> * Improve gas hob flames and cooktop knob controls Port the curved-flame look from reference gas-burner photos: each burner gets a ring of vertex-coloured tube flames (blue body, orange-yellow tips) whose spines breathe and flicker per frame at ~30fps, plus a flat ignition glow and a faded heat halo. Fix the knob pointer notch to rotate with the knob instead of drifting sideways. Also includes cooktop compartment presets/panels and cabinet selection/move affordance work. Co-Authored-By: Claude <noreply@anthropic.com> * Fix cabinet UVs and registry actions * Fix registry move snap typecheck * Fix cabinet knob animation concurrency * Fix cabinet material disposal, undo flood, and 2D move parity - Flag shared cabinet appliance materials (and the viewer's cached material factories) as cached so geometry rebuilds no longer dispose materials still referenced by other nodes, forcing scene-wide shader recompiles. - Route the door/drawer open animation through useLiveNodeOverrides with a single final commit, so one play is one undo step instead of ~20. - Add a cabinet-module floorplanMoveTarget: 2D drags now convert through planToLocal + magneticSnap like the 3D path, instead of writing plan coords into the run-local position (teleporting modules on rotated runs). - Re-key sibling runs (cabinetAdjacencyRevision) when a run's neighbor-affecting inputs change, so countertop overhang joins re-trim when a neighbor moves/resizes/deletes. - Narrow the cabinet panel's scene subscriptions (useShallow module selector) so it stops re-rendering on every scene mutation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Restructure cabinet schema and centralize run math/mutations Prepares the cabinet node for the queued specialty units (corner L-shape, sink base, appliance gap, open shelving): - Add a `moduleKind` discriminator (default 'standard') on CabinetModuleNode so new unit types extend an enum instead of overloading the stack, and split CabinetCompartment into a z.discriminatedUnion so invalid field combos (drawer with rack style, fridge with burner state) are unrepresentable. Shared box fields now come from one `cabinetBoxFields` object so run/module schemas can't drift. - Extract the straight-line run assumption (sort-by-x, edges, adjacency, spans, side-insert, reflow, frame transforms) into run-layout.ts — previously spread across definition/geometry/quick-actions/stack/ move-frame, so corner support would have meant five parallel edits. - Consolidate the run mutations (add module, wall cabinet/hood above, base↔tall switch, layout-revision bump) into run-ops.ts on SceneApi. Panel and quick-actions had drifted copies: the panel's add-module skipped gap checks, revision-bump scope differed per surface. - Remove dead code: node-level doorStyle (with a migrateNodes entry — geometry only ever read per-compartment doorType), the unreachable slot-handle mesh block, the no-op handlePosition 'edge' enum value, wallLocalX, and duplicate totalCabinetHeight definitions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add cabinet snap tests and split the cabinet panel - New tests: cabinetModuleParentFrame.magneticSnap (flush snap, threshold, Z alignment, nearest-edge) and wall-snap's resolveCabinetWallFaceOffset / collectCabinetWallSnapNeighbors (straight face, miter taper, ray-miss fallback, yaw/face/parent filtering) — 87 cabinet tests, up from 71. - De-brittle geometry test lookups: coordinate-encoded mesh names are now matched by pattern, so dimension-default changes don't break them. - Split panel.tsx (1,541 → 679 lines): CompartmentCard + option constants into compartment-card.tsx, CabinetRunPanel + reflow wiring into run-panel.tsx, and the compartment type-transition tables into a pure resolveCompartmentTransition in stack-transitions.ts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Split cabinet geometry.ts into per-appliance modules Pure code motion: geometry.ts (4,728 lines) becomes a 336-line orchestrator dispatching into geometry/ — shared helpers + cached appliance materials (shared.ts), CSG fronts/handles/doors/drawers (fronts.ts), run spans + countertop (run.ts), and self-contained hood/fridge/cooktop/dishwasher/ oven-microwave/pantry builders. No mesh names, userData stamps, materials, or math changed; the './geometry' import path is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add undermount sink compartment with CSG-cut countertops New 'sink' compartment type (single / double / 60-40 bowl layouts) following the cooktop pattern: zero stack height, countertop-plane geometry, panel card with a bowl-layout selector, and a Sink Base preset. Bowl openings are subtracted from both module- and run-owned countertop slabs via three-bvh-csg; the basin shells, drain plumbing, and a spec-sheet-proportioned gooseneck faucet (Grohe Minta-style pin lever) render beneath the cut. Sink modules skip the carcass top panel and the deck under the sink row so the basin hangs through. Also scope the viewer mock in roof-surface-placement-guides.test.ts to only stub useViewer — the bare module stub leaked into later suites and broke real CSG imports. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Update ifc-converter next-env reference to build-mode route types Regenerated by `next typegen` during check-types; points at .next/types instead of the dev-mode .next/dev/types path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Draw cabinet floor plans with kitchen drafting symbols Replace the per-module box soup with NKBA/architectural-convention symbols: the run draws one heavier countertop outline per span (extended by the overhang), base modules draw their front edge plus compartment symbols — rounded bowl rects + faucet dot for sinks, burner/zone rings for cooktops (reusing the 3D layout tables so 2D and 3D always agree) — and appliance modules carry standard upright labels (DW / REF / OV / MW / PAN). Nested wall cabinets and hood-only modules draw as dashed open outlines per the above-cut-plane convention. * Play cabinet open/close animation on the E interaction key Add an `e` slot to registry KeyboardActions and dispatch it in the keyboard hook ahead of the legacy door/window arms, so kinds opt into the E interaction on their NodeDefinition. Cabinets register it: E on a module eases its doors/drawers open or closed, E on a run swings every child module together, and hood-only modules fall through. The panel's rAF animator moves into cabinet/interaction.ts (live-override frames, single undo commit) so the Play button and E share one animation, and the shortcuts dialog now documents E. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add kitchen island and bar counter support to cabinet runs Islands: run-level countertopBackOverhang (seating side) and withFinishedBack (decorative back panel), plus an I-key island placement mode in the cabinet tool that skips wall snap. Bar counters: optional barLedge {edge, height, depth} drawing a knee wall and raised slab along the back or either run end, superseding the seating overhang on its edge. withWaterfall drops slab-material panels to the floor on exposed run ends. 2D floorplan outlines, selection bounds, geometry keys, and run panel controls updated in parity; schemaVersion 3 -> 6. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Merge quick-action arrow and plus into one directional add glyph The add-left/add-right buttons showed two unrelated icons; a single plus-on-tail arrow reads as "add on this side". Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Extend plugin discovery instead of replacing the host source Also track the ifc-converter next-env routes reference update. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add L-corner runs to modular cabinets with run-scoped grouping An "L Left/Right" quick action spawns a perpendicular base leg, wall leg, and wall bridge as metadata-linked runs parented to the SOURCE RUN, so the run stays the single group: selecting a base module selects only it, and the clicked module never becomes a container. Corner runs re-anchor when the source module resizes or moves (new MovableParentFrame.onCommit hook, mirrored in the 2D floorplan commit), run bounds fold in child leg runs, and deleting one corner member removes only that node while unlink patches keep the survivors' link metadata consistent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Delete emptied cabinet run groups in the same delete gesture Removing a run's last child (module or derived corner leg) left an empty group node in the scene graph and persisted data. onDeleteCascade now receives the gesture's pending delete ids so multi-select deletes count siblings as gone, and both cabinet kinds cascade the orphaned parent run away in one undo step. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Remove filler front gap treatment * Apply architecture review fixes across cabinet, plugin-trees, and framework Move cabinet semantics out of packages/editor behind registry capabilities (def.tree, selectionProxy.bypassDirectPick, IconRef quick-action icons, catalog tool field), route cabinet selection through a core selection-proxy helper, and make PanelWorkspace an opaque host tag. Fix the cabinet placement undo flood, dirtyNodes.add bypasses, normalized UVs on paintable slots, plugin-trees ghost layers/stale level transforms/untyped find-sync event, and cross-file store-mock pollution plus orphaned dist tests that broke combined test runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Propagate front styling to L-corner runs even when re-layout bails Group style changes rode entirely on syncDerivedCornerRun, which bails silently when a later-drawn wall blocks the corner layout or a leg run gained modules outside the derived-run spec — leaving the L legs styled stale. Apply the style patch directly to every linked corner run before attempting the geometric re-layout. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Improve editor viewer integration * Refactor plugin panels and floorplan invalidation * UV-unwrap and material-assign roof-segment trim planes * Fix cabinet placement snap feedback * Add cabinet wall snap bypass * Fix cabinet continuous placement flow * Implement wall band material slots * Add corner addition preview functionality and related tests * Fix cabinet and wall band behavior * Fix architecture review blockers * Fix post-merge quality check * fix cursor review feedback * Fix cabinet placement type and quick action history * Improve cabinet placement feedback * Make wall bands configurable * Add wall trim profiles and default materials * Fix architecture review issues * Fix cabinet rotation and wall band visibility * Fix sink appliance paint persistence * Bake cabinet animations and preserve wall band material * Fix cabinet group duplication and module snapping * Fix cabinet duplication architecture * Fix cabinet floorplan test and make placement-type hint a live chip - floorplan.test.ts: pass the required liveTransforms map to cabinetFloorplanSiblingOverrides (the runtime callers always supply it) - ToolHint gains an optional generic `chip` contract (subscribe/value/ cycle/labels) so a kind can render a tool hint as a live mode chip — like the snapping/continuation chips — without the editor knowing the kind's store - cabinet: the I hint now shows the current value (Type: Cabinet / Type: Island) and is clickable to toggle Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Wassim SAMAD <wass08@gmail.com> |
||
|
|
717c2c5c0a |
fix: bug pass — legacy window crash, plant exports, zone snapping/visibility, HDR (#473)
* fix(core): apply window schema defaults on scene load Windows saved before a schema field existed (columnRatios/rowRatios/ frameThickness/…) loaded with those fields missing; the window mesh builder reads them unconditionally and threw every frame, crashing the viewer on legacy scenes. Zod-parse windows on load like doors so the schema defaults land. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): mount plant geometry during client GLB/STL/OBJ export The client export never set isExporting, so instanced kinds (trees/ flowers/grass) kept their colorWrite:false raycast collider mounted and exported it as an opaque white box, while the real geometry (which only mounts while exporting) was never captured. Reuse BakeExporter's flag + frame-wait dance in ExportManager, and harden isRenderableMesh to drop colorWrite:false materials from exports entirely. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): route zone drawing through the shared surface snap The zone tool quantized later vertices by distance along a free ray instead of snapping to the grid, and never joined the magnetic wall-corner/midpoint/crossing + alignment-guide pipeline that walls, slabs and ceilings use. Give zone the slab treatment in the 3D tool and both 2D floorplan branches (move + click + double-click commit). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(viewer): self-host the scene environment HDR drei's preset="sunset" fetches venice_sunset_1k.hdr from raw.githack.com, which intermittently fails ("Could not load venice_sunset_1k.hdr: Failed to fetch"). Point Environment at /hdri/venice_sunset_1k.hdr and ship the file in the app's public/ — same mirroring convention as /audios/sfx; consuming apps must carry the file too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): unmount zones entirely outside the zones layer Zone labels showed in every editing mode (visual noise), and each zone kept a drei <Html> mounted at opacity 0 — an <Html> costs per-frame matrix work and live DOM even when invisible. A new viewer presentation flag (showZones, default true) lets the editor unmount zone meshes and labels whenever the structure layer isn't 'zones' (and during snapshot capture); the registered group stays so zones keep their scene identity for selection and GLB export. Preview / first-person / viewer surfaces are untouched. Raycast-disable moved from a one-shot group flag to per-frame on the meshes, which now remount on layer toggles. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5c071ecad5 |
feat(plugins): plugin contract + first-party Nature pack (trees/flowers/grass) (#457)
* feat(plugins): trees tracer-bullet plugin + minimal host panel surface
Ship one real first-party plugin and only the host surface it forces
into existence, proving the plugin contribution paths every future
plugin (mint.gg generator, Home Assistant, environments, room volume)
will reuse.
Host surface (core + editor):
- core: Plugin.panels + observable panelRegistry; loadPlugin routes
panels (namespaced by plugin id, dup-throws like nodes).
- editor: AppSidebar merges panelRegistry into the icon rail via
useSyncExternalStore; each plugin panel lazy-loaded behind an error
boundary; host extraPanels keep precedence.
- editor: widen Tool to `KnownTool | (string & {})` so plugin tool ids
(e.g. 'trees:tree') typecheck; dispatch already registry-first.
Trees plugin (packages/plugin-trees), structurally a third-party pack
(peer-deps on @pascal-app/*):
- trees:tree node — procedural low-poly geometry (oak/pine/birch/palm),
free parametric inspector (preset/height/seed + Randomize), placement
tool + ghost preview built from public primitives only.
- presets rail panel — panel -> plugin store -> def.tool -> SceneApi ->
scene -> reactive read-back ("N planted").
Loading + docs:
- apps/editor: setPluginDiscovery([treesPlugin]) in bootstrap;
transpilePackages + dep.
- wiki/architecture/plugin-authoring.md: panels field, error-boundary
contract, styling, tool-from-panel note.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(plugins): merge plugin panels into the v2 sidebar layout too
The community shell renders <Editor layoutVersion="v2" sidebarTabs={...}>,
a separate rail from AppSidebar. Merge registry panels into the v2
tabMap + tab bar (and thus mobile) so plugin panels show up there as
well, not only in the v1 AppSidebar.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(plugin-trees): ez-tree geometry + instanced rendering + snapping
Replace the placeholder low-poly geometry with dgreenheck/ez-tree
(self-contained — bark/leaf textures inlined as base64, no assets to
host) and render the forest with true GPU instancing.
Rendering:
- def.system (system.tsx): one collective renderer that groups every
trees:tree node by (preset, seed) variant and draws each variant as
one InstancedMesh per ez-tree sub-mesh, composing the parent level's
world matrix into per-instance matrices. ~1 draw call per variant.
- def.renderer (proxy-renderer.tsx): an invisible, raycastable per-node
proxy so the host's existing selection / outline / zone machinery
keeps working — no instanceId bookkeeping. (Outline highlights the
proxy bbox; documented.)
- geometry per variant generated once by ez-tree and cached; seeds drawn
from a bounded pool so trees actually share variants.
Presets remapped to ez-tree built-ins (oak/pine/aspen/ash/bush).
Placement now respects the active snap mode — isGridSnapActive() +
gridSnapStep + snapPointToGrid, like the built-in item/shelf tools.
transpilePackages += @dgreenheck/ez-tree in the editor app.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(plugin-trees): true-silhouette hover/select + richer presets panel
Selection/hover now outlines the real tree shape, not the bbox. The
proxy splits into an outer group (stable invisible box collider +
pointer handlers, the raycast target) and an inner registered group that
mounts the real ez-tree geometry (invisible, non-raycasting) only while
the node is hovered or selected. The host outline pass reads the
registered inner group, so it traces the true silhouette; picking stays
on the steady box. Geometry for the highlight reuses the cached variant.
Panel: redesigned cards with gradient swatches + selected state, a
"planted" count chip, and a height slider that seeds the next tree's
height (a per-instance scale — never touches placed trees or instancing).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(plugin-trees): curated tree params + procedural flowers (sibling kind)
Tree params: expose foliageDensity, trunkThickness, and a leafless toggle
in the inspector and the panel brush. Each is folded into the instancing
variant key and mapped onto ez-tree options (radius scale, leaf count),
so editing one tree only re-buckets that tree — instancing degrades
gracefully, never worse than per-node.
Flowers: add a `trees:flower` sibling kind — simple procedural geometry
(stem + petals + center, merged per material), presets daisy / tulip /
lavender, instanced + selectable exactly like trees.
Refactor: extract the instanced renderer + selection proxy into a generic
`instanced.tsx` (InstancedKindSystem + KindProxy) and the snap/level/grid
placement wiring into `placement.tsx` (usePlacement). Trees and flowers
are now thin bindings — the template for future plant kinds.
Panel: a Trees / Flowers toggle, gradient preset cards, per-kind planted
count, and brush sliders.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(plugin-trees): native inspector controls in panel + preset thumbnails + ez-tree credit
- swap the panel's custom slider/checkbox/toggle for the host's exported
SliderControl/ToggleControl/SegmentedControl so the brush matches the
right-hand inspector pixel-for-pixel
- replaceable preset thumbnails (inline SVG data URIs, no asset hosting) render
as <img> cards instead of gradient swatches
- credit footer linking @dgreenheck/ez-tree (Daniel Greenheck, MIT)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(plugin-trees): all ez-tree presets + type + leaf/branch colours, flower petal colour, grass kind
- tree presets now cover all of ez-tree's built-ins via species × size
(Small/Medium/Large + Bush 1/2/3 + Trellis), plus a Deciduous/Evergreen type
- edit-only leaf & branch colour tints (leaves.tint / bark.tint), all folded
into the instancing variant key
- flowers gain a per-flower petal colour (baked from preset at placement)
- new trees:grass instanced kind — procedural blade tufts (meadow/fescue/reed)
with a per-tuft blade colour, reusing the generic instanced + placement core
- panel: Trees/Flowers/Grass segmented switch, size + type controls, native
host controls throughout; credit links Daniel Greenheck's X
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(plugin-trees): tolerate nodes persisted before new fields existed
treeSpecOf/flowerPetalColor now default every geometry field, and hexToInt
guards undefined — trees/flowers placed before size/type/colour fields were
added load with neutral defaults instead of crashing the instanced renderer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(plugin-trees): shared always-on wind + pin credits to panel bottom
- wind.ts injects a sway into each material's begin_vertex driven by one shared
uTime uniform (advanced per frame in the instanced system); USE_INSTANCING
guards keep the same material valid for the non-instanced placement ghost.
Applied to tree, flower, and grass materials — a whole scene sways like ez-tree.
- credits footer is now sticky to the bottom of the panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(plugin-trees): rename rail panel Trees -> Nature (covers trees/flowers/grass)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(plugin-trees): rename panel header Plant -> Nature to match the rail
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: bake-exportable instanced plants + renderer-agnostic wind
Bake (GLB export): add a transient useViewer.isExporting flag. BakeExporter
flips it, waits for the commit, then exports. KindProxy watches it and, during
export, emits each plant's REAL visible geometry under scene-renderer (which the
exporter clones) instead of the invisible collider — so instanced kinds
(def.system) that live outside that subtree are captured. The collider box is
dropped during export so it doesn't bake as a phantom solid.
Wind: the previous onBeforeCompile wind was a no-op under the editor's WebGPU
renderer (WebGL-only hook). Replace it with a renderer-agnostic per-instance
base-pivot tilt animated in useFrame (which FrameLimiter drives continuously).
Reads as wind under any renderer; removes the dead wind.ts + applyWind calls.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(plugin-trees): TSL vertex-bend wind (WebGPU), replacing CPU tilt
wind-node.ts adds a TSL positionNode that bends each plant proportional to
height above its base, phased per-instance (instanceIndex) and per-vertex, so
tips sway and roots stay planted — animated on the GPU via the renderer's time
node. ez-tree is untouched: its materials are copied into MeshStandardNodeMaterial
(toWindMaterial); flowers/grass build node materials directly (windStandardMaterial).
Instance matrices go back to static.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(plugin-trees): tree materials render black under wind (Phong not Standard)
ez-tree's bark/leaves are MeshPhongMaterial; copying them into a
MeshStandardNodeMaterial swapped the shading model (specular/shininess ->
roughness/metalness) and rendered black. Convert into the matching node
material type (Phong->Phong, Lambert->Lambert, else Standard) so map/color/
alphaTest/side are preserved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(plugin-trees): trees still black — build node material from map/color explicitly
Material.copy() doesn't transfer a classic material's map/color onto a node
material, so textured trees rendered black (flowers/grass were fine — they carry
no map). Re-create the ez-tree materials as MeshStandardNodeMaterial with map,
alphaMap, color, side, alphaTest passed explicitly in the constructor — the same
proven path the flower/grass materials use.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(plugin-trees): tree wind — per-leaf flutter (ez-tree style), static trunk
The whole-tree height-based bend was a rigid rotation about the base ('rotating
in place'). Replicate ez-tree's actual approach: scale sway by the leaf card's
uv.y and apply it ONLY to the 'leaves' material (multi-frequency wave, phased per
instance + per leaf) so leaves flutter from their attachment while bark/branches
stay static. Flowers/grass keep the gentle whole-plant stem bend.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(plugins): Plugin.onSceneLoad hook + viewer application; trees wind via it
Adds a broad, plugin-declared post-load scene hook so effects survive GLB export:
- core: Plugin.onSceneLoad (lazy, three-free) + observable sceneHookRegistry + loadPlugin routing.
- viewer: applyPluginSceneHooks util; PluginSceneHooksSystem re-runs it on the live
scene as nodes change; GlbScene runs it on each loaded baked GLB.
- plugin-trees: declare onSceneLoad; wind is no longer welded into the materials —
the hook re-attaches LEAF_FLUTTER/STEM_BEND by material name (leaves / flower-* /
grass-*), so it now animates in the baked /viewer too, not just live.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(plugin-trees): scale-invariant leaf wind (÷ modelScale) so baked GLB matches editor
The bake's quantiser renormalises each plant mesh to [-1,1] and moves real size
into a large node scale (~3 on trees). LEAF_FLUTTER added a constant local offset,
so in the baked GLB it was multiplied by that scale → ~3x over-swing. Divide the
sway by modelScale.x to keep a constant world-space amplitude; editor (scale 1)
is unchanged, baked now matches.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* revert: onSceneLoad wind approach → back to wind-in-materials
Reverts the Plugin.onSceneLoad hook + viewer application + modelScale compensation
(commits 893ca839, 6c9ee003). Superseded by the bake-policy 'replace' model
(plans/editor-plugin-trees-example.md Part D): trees render live in the viewer via
their own path, so wind is a plain positionNode on the material again — no hook,
no baked-geometry decoration, no coordinate-space fights.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(bake): per-kind bake policy (static/strip/replace)
Adds NodeDefinition.bake ('static' default | 'strip' | 'replace') + registry
helpers bakePolicyOf/kindsWithBakePolicy in core. Generalises the previously
hardcoded scan/guide handling:
- glb-export strips kinds with bake==='strip' from the artifact (was a
scan/guide type check); 'replace' kinds stay baked (portable static snapshot).
- glb-reference-nodes selects rebuild candidates by policy instead of type.
- scan/guide declare bake:'strip'; trees/flower/grass declare bake:'replace'.
Viewer-side 'replace' swap (strip baked meshes + live-rebuild) is the next slice.
Part of plans/editor-plugin-trees-example.md Part D.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(bake): viewer 'replace' swap — hide baked meshes, re-render live
Adds NodeDefinition.bakeReplaceRenderer (falls back to renderer). The baked
/viewer now, for kinds with bake:'replace' and a loaded plugin:
- hides the static baked meshes (bakePolicyOf(kind)==='replace') in GlbScene's
identity pass — capability-gated, so with no plugin the meshes stay;
- re-renders each node live via buildGlbReplaceNodes + the shared portal-into-
baked-level path (GlbReferenceNode now prefers bakeReplaceRenderer), so trees
ride level stacking automatically and wind runs in real coordinate space.
plugin-trees ships a hookless KindStatic (real geometry + wind materials, no
collider/selection) and per-kind static-renderer binds; tree/flower/grass point
bakeReplaceRenderer at them. Completes plans/editor-plugin-trees-example.md Part D.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf(viewer): memoize GlbReferenceNode(s) — skip forest re-render on camera move
GlbScene re-renders per frame during camera movement (hover raycast / walkthrough
HUD). The rebuilt nodes reconciled every frame — fine for 1-2 scans/guides, but a
bake:'replace' forest puts dozens of nodes here (profiler: KindStatic x56 6ms,
GlbReferenceNode x57 4ms per frame). memo at the node boundary skips the whole
subtree (incl. the plugin renderer) when (node, anchor) are unchanged; both props
are stable refs, so it short-circuits cleanly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf(plugin-trees): NO_RAYCAST on KindStatic meshes — cheap hover over a forest
The baked scene's <primitive> has pointer handlers, so R3F recursively raycasts
every descendant on each pointer move (incl. orbit drags). The live replace trees
had fully-raycastable dense ez-tree geometry, so hover cost ~400ms/frame over a
forest (profiler: 'JavaScript' 413ms during camera move). They carry no pascalId
(a hit resolves to the level, not the tree), so they're scenery, not pick targets
— mirror KindProxy, which already NO_RAYCASTs its real geometry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(bake): viewer 'replace' renders instanced per level (was per-node)
Replaces the per-node KindStatic viewer path with a collective instanced one,
mirroring the editor's system instead of diverging from it:
- extract InstancedNodes from InstancedKindSystem (shared by both) with a
localSpace flag: editor folds parent world matrix (root instances); viewer uses
level-local matrices + NO_RAYCAST, portaled into the baked level.
- bakeReplaceRenderer is now a collective renderer (BakeReplaceRenderer<N>,
receives {nodes}); GlbReplaceInstances groups replace nodes by (level, kind) and
portals each kind's instanced renderer into that baked level. GlbReferenceNode
reverts to per-node renderer (strip kinds only).
Fixes: forest hover cost (100+ tree meshes → a few instanced draws, R3F walks far
fewer objects) and per-tree wind phase (instanceIndex varies again; per-node
KindStatic gave every tree phase 0 → unison sway). Trees still ride level stacking
(portaled) and stay static in a plain glTF viewer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf(viewer): BVH the baked scene — accelerate hover/pick raycasts
The baked /viewer runs with useBvh={false}, so GlbScene's hover/pick raycasts
against the baked building were brute-force triangle intersection (profiler:
_computeIntersections + intersectTriangle ~30%). The parametric viewer wraps its
scene in <SceneBvh>; the baked one never did. SceneBvh's effect is one-shot on
mount and can't catch the async-loaded GLB, so compute a per-mesh BVH inside
GlbScene keyed on gltf.scene instead. 'replace' instances are NO_RAYCAST, so the
raycast===Mesh.prototype.raycast guard skips them (mirrors SceneBvh).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(plugin-trees): inherit ez-tree preset by default (seed/type/tints)
Our node defaults (seed:1, treeType:deciduous, colors:#ffffff) were applied as
*overrides* on top of loadPreset, discarding each preset's tuned seed, growth
model, and tints — so our trees looked nothing like eztree.dev (pine grew
deciduous, foliage washed white, every silhouette off).
Mirror the flower petalColor pattern: seed/treeType/leafColor/branchColor are now
optional; generateTree only overrides an ez-tree option when the node set it,
otherwise loadPreset's value stands. Placement stores none of them (fresh tree =
pure preset; all same-preset trees share one instancing variant); the deciduous/
evergreen brush toggle is gone (growth model comes from the preset). The inspector
keeps them as per-tree overrides, and Randomize still varies the seed.
Verified against ez-tree: fresh Oak Medium → seed 35729 + preset tints; Pine
Medium → evergreen + seed 13977; overrides apply when set.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(plugin-trees): bundle real preset artwork (webp, in-package)
Replaces the placeholder SVG thumbnails + lucide panel icon with the real nature
art. 13 webp (~126KB total, 256px cards / 128px icon) live in src/assets and are
imported via art.ts — both consumers are Next, so transpilePackages runs them
through the image pipeline (hashed, cached /_next/static/media URLs). No CDN, no
per-app public/ mirroring; the assets travel with the package.
- art.ts: central webp imports → TREE_ART / FLOWER_ART / GRASS_ART / NATURE_ICON
- presets/flower-presets/grass-presets: thumbnail ← bundled art (was *Thumbnail())
- index.ts: Nature panel icon ← NATURE_ICON (was lucide:leaf)
- assets.d.ts: ambient *.webp decl (no next type dep)
- remove thumbnails.ts (placeholder generators, now dead)
Verified on the running dev server: webp emitted + served (HTTP 200, image/webp).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(plugin-trees): square preset cards on light-gray flattened thumbnails
Regenerate the 12 thumbnails with the transparency flattened onto gray-100
(#f3f4f6) and render the card square (aspect-square, was h-16 crop). The
nature-icon keeps its transparency for the icon rail.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(plugin-trees): correct move-drag + outline behavior for instanced plants
Two selection defects, one root cause each:
1. Outline moved mirror-wise during a move drag. The host move tool drives the
registered Object3D imperatively with absolute level-local positions (and
mirrors them via useLiveTransforms) — the contract ParametricNodeRenderer
satisfies by putting position + registration on the same group. KindProxy
registered a child nested inside the transform-carrying group, so drag
positions landed in the node's rotated frame (placement gives every plant a
random Y rotation → deltas rotated up to 180°). Restructure: the registered
group now carries position/rotation (live-transform aware); the box collider
is a positioned sibling, staying out of the outline mask.
2. Outline froze while the mesh swayed. The outline mask pass renders with a
shared override material, so it can never follow the material positionNode
wind. Instead, while hovered/selected the collective system skips the node
and the proxy mounts the real geometry with static twins of the wind
materials (toStaticMaterial — explicit property transfer; node-material
clone() drops map/color). Outlined mesh == visible mesh, both still.
Bonus from the same restructure: a move drag now animates the actual plant in
realtime (the proxy is what the tool drives), not just the drag box.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(plugin-trees): 2D plan symbols + trunk-sized footprint
- def.floorplan for the three kinds — the registry floor-plan layer renders any
kind that provides one, so this is all plugin nodes need to appear in 2D.
Tree: dashed canopy ring (dashed = overhead element) in the preset swatch +
solid trunk dot; ring is pointer-events:stroke so the large disc doesn't
steal clicks from what's under the canopy. Flower/grass: small colour dots.
Selected → palette stroke + move-handle; hovered → hover stroke.
- Tree floorPlaced.footprint is trunk-sized (treeTrunkRadius) instead of
canopy-sized, so the move/placement drag box hugs where the tree actually
plants instead of spanning the crown. The invisible hover collider keeps its
larger radius — only the displayed box shrinks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(plugin-trees): trunk-sized drag box, 45°-aligned rotation, rotate gizmo
- The move drag box ignored floorPlaced.footprint: with collides:false the tool
auto-measures the rendered mesh — which, since the selection swap-out, is the
whole canopy. Declare capabilities.dragBounds (trunk-sized, node-local) so the
box hugs the trunk and rotates with the node instead of wrapping the crown as
a world AABB.
- Placement wrote a fully random Y rotation, so the box/gizmo never sat on a 45°
step. Snap the random rotation to 45° increments (variety preserved, alignment
restored) and widen rotatable.snapAngles to 8×45° on all three kinds.
- Add the standard rotate gizmo (def.handles, shelf-style arc handle): ring
around the trunk near the ground — not the canopy, which would put the handle
meters out on a large oak.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(plugin-trees): live rotate preview + ground-level rotate ring
- KindProxy now folds useLiveNodeOverrides into its transform (mirrors
ParametricNodeRenderer) — the rotate/resize gizmos publish mid-drag patches
there, so the plant turns in realtime instead of snapping on commit. The
snap-on-commit (with the arc delta wrapping to ±180°) is also what made long
drags land 'the wrong way'; with live feedback the direction reads correctly
and matches the item gizmo pipeline exactly.
- Rotate ring drops from mid-trunk to 0.25m — a floor affordance like the item
gizmo, not a waist-height hoop.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(registry): panelForKind — 'find in catalog' opens a plugin kind's panel
loadPlugin now associates every node kind with its plugin's first (namespaced)
panel id; panelRegistry.panelForKind(kind) exposes it. A host's find-node
handler can open the right panel for any plugin kind with zero per-plugin
knowledge.
plugin-trees uses it end-to-end: a module-level selection:find-node listener
(find-sync.ts, imported by the manifest so it's live from plugin load) points
the panel store at the found node's section + preset — panel section state
moved from panel-local useState into the store to make it addressable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(registry): plugin panels declare their workspaces (default edit-only)
Plugin panels rode into the studio rail: the host swaps its own sidebar tabs by
workspaceMode, but usePluginPanels appended registry panels unconditionally.
PluginPanel gains workspaces?: readonly ('edit'|'studio')[] — manifest metadata,
default ['edit'] (an authoring panel has no business in the clean render
workspace; a plugin shipping studio tooling opts in explicitly). usePluginPanels
filters by the current workspaceMode, covering both the v1 AppSidebar and v2 tab
bar paths. Nature declares nothing and disappears from studio via the default;
the v2 layout's existing active-tab fallback handles a panel vanishing mid-use.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: link plugin-authoring guide from README + CONTRIBUTING, fix stale wiki path
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(plugin-trees): keep ez-tree off the eager SSR path
ez-tree loads its inlined textures at module scope (needs document), so any
eager import chain reaching geometry.ts crashed Next prerender
(ReferenceError: document is not defined on /_not-found). Move the pure
helpers (mulberry32, naturalHeight) to variant-utils.ts so the
flower/grass builders and floorplan no longer pull ez-tree, and drop the
generateTree re-export from the package barrel (no external consumers).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
bf25af6add |
viewer: Fix Dutch roof trim artifacts (#452)
* Add roof surface placement support for items Items (e.g. solar panels) can now be placed on sloped roof surfaces. The placement system computes euler rotation from the roof surface normal so items sit flush on the slope instead of going inside. - Add roofStrategy to placement-strategies with enter/move/click/leave - Wire roof:enter/move/click/leave events in the placement coordinator - Add calculateRoofRotation in placement-math using surface normals - Support full 3D cursor rotation for sloped surfaces - Items on roofs are parented to the level with world-space rotation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fixed conflict * Fix spiral stair openings and fence handle arrows * Implement roof trim planes and ridge vent clipping * Fix mansard roof and ridge vent placement * Fix mansard merged roof cutouts * Fix Dutch roof gable overhang * Refactor roof segment, ridge vent, and surface geometry Remove Dutch ridge axis abstraction and rework roof edit system, ridge vent clipping geometry, and roof surface placement. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Simplify Dutch roof shape * Add Dutch roof gable top geometry controls * Fix Dutch roof slope material slots * Render dutch roof tops as double-sided faces * Add auto ridge vent toggle to roof segments Track ridge vent auto-generation via an `autoRidgeVent` metadata flag so geometry changes only regenerate default vents when enabled, treating legacy segments with generated vents as auto-enabled for back-compat. Expose a panel toggle to opt in/out per segment. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Snap new walls to the floor below Feed the walls of the level directly beneath the active one into the draft snap pipeline as extra references, so a new wall can align with the floor below. They share the same local XZ origin, and the list is kept separate from the current-level walls so the measurement HUD and wall splitting only act on the active level. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Set Dutch roof shape defaults on type switch Seed the Dutch shape parameters (waist width/height/length, top rake thickness/length) with sensible defaults whenever a segment is created as or switched to Dutch, so the gablet is well-formed regardless of leftover values from the previous roof type. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Use green accent for corner and endpoint snap markers Color the corner/endpoint snap markers and the vertical cursor pillar green across the 2D floorplan beacon, the 3D alignment guide dots, and the wall snap beacon so snap targets read as a consistent accent. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Add magnetic wall snapping to the roof tool Snap roof draft corners onto wall corners, midpoints, crossings, and bodies on the active level and the floor below, reusing the wall tool's snap pipeline so the beacon and coloring match. The cursor's ground dot/ring is hidden while a wall snap is active to avoid overlapping the beacon glyph. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Update auto-generated Next.js route types path Regenerated next-env.d.ts now references ./.next/dev/types/routes.d.ts. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Show cutaway outline while dragging roof trim Slice an untrimmed segment volume generated from the live node instead of the registry mesh, whose CSG rebuild lags a few frames behind the drag and may still hold placeholder geometry — so the section outline now renders deterministically. Use LineBasicNodeMaterial so the outline draws under the WebGPU pipeline, and export generateRoofSegmentGeometry for the slice source. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Fill and clip roof trim cutaway, gate it to active drag Add a violet silhouette fill behind the cutaway outline, extend the section slicing to angled diagonal/corner trims via a generic vertical cut plane, and clip each slice to its footprint span so the infinite plane no longer sprouts stray lines across the rest of the roof. The cutaway now renders only while a trim handle is being dragged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Separate and extend Dutch roof end slopes Pull the Dutch hip end slopes out of the watertight shingle shell into their own slab wedge so they can be reshaped independently, and extend each end slope inward up its own hip plane until the top edge meets the gablet's inner triangle. Refactor roof-segment shape geometry into a shared roof-segment-shape module. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Render roof trim cutaway as a material-only section cut Replace the triangle-mesh slicer with a CSG intersection of a thin slab against the untrimmed roof shell, so the cutaway shows red only on real material (wall + deck bands) and leaves the hollow attic empty. Add an analytic surface-edge outline, style both solid red like a SketchUp section, and make the cutaway persist whenever a segment is trimmed. Keep the merged roof shell visible during trim editing (re-trimmed live from each segment's drag override) instead of swapping in the per-segment meshes, whose abutting end-cap faces showed as stray white planes the commit never had. Extend each slab past free cut-line ends only — trimmed ends clamp to the cut line — so the red section stays inside the trim box. Re-export INTERSECTION from the viewer CSG surface for the editor. * Outline roof cutaway by fill silhouette, restyle to destructive red Derive the section-cut outline from the fill geometry's edges (EdgesGeometry) so it traces the real cut shape — wall/deck band boundaries and the hollow-attic edge — instead of just the top surface line. Drop the fill to 85% opacity and recolor both fill and outline to the app's destructive red, matching the delete/destructive UI. * Include roof accessories in trim clipping and red cutaway Roof accessories (chimney, vents, skylight, dormer, gutter, downspout, solar-panel, cupola) now slice at the trim plane like the roof shell and appear in the red section-cut while dragging a trim handle: - Export clipGeometryBySegmentTrim from the viewer as a reusable segment-local trim-clip primitive. - Add a shared useSegmentTrimClippedGeometry hook + TrimClippedMesh wrapper (nodes) that slice accessory geometry by the host segment's live trim override, so the cut tracks the drag. - Wire the clip into all 11 accessory renderers, including skylight glass panes and dormer window glass/frame/sill. - Feed every hosted accessory mesh into the editor's red cutaway, welding triangle-soup geometry (e.g. ridge vent) so CSG INTERSECTION yields a cross-section. - Register skylight in the scene-graph tree-node map so it shows in the outliner when placed on a roof. Co-Authored-By: Claude <noreply@anthropic.com> * Add smooth spline fences with editable curve handles Fences can now be drawn as one continuous Catmull-Rom/Bezier curve via an optional `path` (+ per-point `tangents`), selectable in a Straight/Curved mode toggle. Selected spline fences expose draggable control-point dots (hexagon) and symmetric tangent handles (circle) joined by a violet line, editable in both 2D plan and 3D. Side-move arrows are dropped for splines. Co-Authored-By: Claude <noreply@anthropic.com> * Fix dutch roof ridge vent handling * Fix Dutch ridge vent placement and support * Fix Dutch roof trim artifacts * Fix Dutch roof trim preview geometry * Tag roof trim overlay meshes with EDITOR_LAYER Child meshes relied on a parent group's layer, which three.js does not propagate, so the trim section/rail/plane overlays rendered on the scene layer — getting inked/SSGI-darkened and leaking into thumbnail exports. Co-Authored-By: Claude <noreply@anthropic.com> * Apply Biome cleanup * fix(core): address Dutch roof review feedback * chore: apply biome check cleanup * fix(core): relax Dutch roof surface helper input * fix * Fix biome checks and dev verification * fixes * Remove unsupported Biome noShadow override * Improve roof interactions and fence editing * Fix fence drag and ridge vent default handling * editor: drop wall-snap debug log, gate curved-fence finish hint on draft start Remove the leftover TEMP DIAGNOSTIC console.log in the wall tool's onMove hot path. Curved fences commit on a closing gesture (double-click / Enter) rather than per-click, so surface a 'Finish curve' hint in the fence HUD — but only once a point has been placed and a curve is actually in flight. The draft point count is published from SplineFenceDraft into a small ephemeral editor store (useFenceCurveDraft) that the contextual helper reads, mirroring the existing useSegmentDraftChain pattern. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Wassim SAMAD <wass08@gmail.com> |
||
|
|
db35af9a05 | Merge remote-tracking branch 'origin/main' into fix/bake-door-window-anims | ||
|
|
43fa2b1253 |
Merge origin/main into feat/placement-interaction-overhaul
Resolve 7 conflicts keeping our snapping migration + floorplan perf work as source of truth, combined with main's MEP run-continuation / Alt-detach / latch handles. Rebuilt two import blocks the auto-merge silently truncated (node-arrow-handles.tsx, duct-fitting/move-tool.tsx). Verified: tsc clean across core/viewer/editor/nodes/mcp, 451 tests pass, biome clean. Floorplan view-transform re-render storm confirmed pre-existing (not introduced by this merge). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
69c483b5f2 |
chore(items): refresh ceiling-fan model (On clip + base paint slot)
The bundled ceiling-fan model.glb was stale (no animation clip, two slots). Replace it with the variant matching production storage: an `On` animation clip and a third `slot_base` paint slot. Same dimensions/offset, so no catalog metadata change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1b4c656901 |
chore(ts7): adopt the TypeScript 7 native compiler (tsgo)
Make the editor packages compatible with, and benefit from, the native Go
compiler (TypeScript 7 / tsgo) while keeping the npm publish path on stable tsc.
- Add @typescript/native-preview (pinned 7.0.0-dev.20260624.1). tsgo coexists
with typescript@6, which Next typegen and the IDE plugin still need until the
TS 7.1 programmatic API ships.
- Fix two react-three-fiber JSX augmentations the native checker rejects but
tsc tolerated:
- viewer: map only LineBasicNodeMaterial (the one webgpu node material used as
a JSX tag) instead of the whole three/webgpu namespace (TS2320/TS2590).
- plane-box-select-tool: drop the redundant `IntrinsicElements extends
ThreeElements` block — it duplicated R3F's global augmentation and, by
referencing @react-three/fiber's ThreeElements directly, hit a bun peer-dep
variant-directory duplicate under tsgo (TS2320). r3f.d.ts already covers
those JSX intrinsics. Types-only — no runtime change.
- check-types -> tsgo --noEmit (editor, @repo/ui, editor app, ifc-converter app)
- emit-package dev watch -> tsgo --build --watch
- build/publish stay tsc --build (prepublishOnly + release.yml unchanged;
emitted .d.ts is byte-identical to tsc output)
- bump next 16.2.6 -> 16.2.9
tsc remains the source of truth and fallback. Published artifacts unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
f69b1935a9 |
docs: fix editor-v2 stale folder name in apps/editor/README.md (#311)
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> |
||
|
|
f773e6b8c5 |
feat(editor): placement & interaction overhaul — FSM spine, bug tracks, perf
Implements plans/editor-placement-interaction-overhaul.md: an authoritative interaction-scope state machine plus the catalogued placement/interaction fixes, and split-view floor-plan performance. - Interaction-scope spine (lib/interaction/* + store/use-interaction-scope), driven from central useEditor setters; overlay scoping (zone labels, context badges, floating action menu) reads resolveOverlayPolicy. - Bug tracks A/B/D/E/F/G/H: handle/cutout raycast, footprint validity, auto-slab loop, ceiling hosting, B-key tool desync, 2D drop offset, per-frame jank. - Snapping modes (grid/lines/angles/off) + contextual HUD chips; modifier model (Shift=cycle, Alt=free place, Ctrl=grid step). - Item move now tracks the cursor 1:1 (was a laggy per-frame lerp); handle rig hides during a whole-node move; rotate gizmo advertises Shift=free rotation in the HUD and hides the move cross while rotating. - Floor-plan perf: pause live reactivity while in 3D-only view; per-node geometry cache so only changed nodes rebuild on a drag; hoist wall miters to a once-per-pass ctx.levelData (O(N^2) -> O(N) on wall/opening drags). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a71de82ccb |
nodes: add MEP movement controls and DWV parity (#438)
* Add roof surface placement support for items Items (e.g. solar panels) can now be placed on sloped roof surfaces. The placement system computes euler rotation from the roof surface normal so items sit flush on the slope instead of going inside. - Add roofStrategy to placement-strategies with enter/move/click/leave - Wire roof:enter/move/click/leave events in the placement coordinator - Add calculateRoofRotation in placement-math using surface normals - Support full 3D cursor rotation for sloped surfaces - Items on roofs are parented to the level with world-space rotation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fixed conflict * feat(duct): ceiling-snap drawing + connected-joint endpoint move Duct draw tool's ceiling mode now hangs each path point just below the ceiling actually covering it (per-room heights tracked), with a translucent surface highlight and a plumb line to the floor so the in-flight point reads clearly from any angle. Dragging a duct corner that sits on a fitting now carries the fitting's other ducts along (port-connectivity second hop), so the joint moves together instead of tearing apart. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(mep): detach + vertical modifiers for duct/pipe joint editing Alt detaches a dragged duct/pipe endpoint or fitting from its connected joint (no elbow re-aim, no connectivity follow); Ctrl/Cmd drives vertical riser movement on the fitting move. Behavioral parity across 2D and 3D. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(mep): full DWV pipe parity for joint editing Bring pipe-segment endpoint drags and pipe-fitting moves to parity with duct: free-drag endpoints, Alt-detach, Ctrl/Cmd-vertical riser, elbow re-aim, and connectivity follow. Generalizes the shared elbow-reaim and auto-fitting helpers to dispatch by run kind so 2D and 3D share one path. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(mep): wall-style arrow handles for duct fittings + segments Add violet directional arrow affordances to duct-fitting selection (height, move cross, rotate arc) mirroring the duct-segment rig: portaled into the parent frame to stay out of the selection outline, rendered via the shared HandleArrow, and carrying mated-run connectivity through the single-undo dance. The move cross engages press-drag-release (placementDragMode) the same way the floating drag does, so the markup hit-areas go inert and the fitting move tool commits on pointer-up. Also re-export the HandleArrow primitives from @pascal-app/editor and extend the duct-segment side-move/floorplan affordances. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(mep): click-to-latch cube handles for duct + fitting editing Replace the hover-reveal / multi-handle selection rigs with a single click-to-latch cube that opens a directional cluster, shared between duct segments and fittings via a new selection-handles module (HandleCube / MoveChevron / RotateArc, all sized to the roof pitch cube). - Duct segment: per-vertex + run-center cubes reveal axis-locked move chevrons (down arrow always shown), plus a roll arc at the run center. - Duct fitting: center cube reveals six ±XYZ move arrows and three per-axis rotation arcs (oriented in place), replacing the old height/move/rotate trio with axis-cycling. - Rotation (fitting arcs + duct roll) snaps to 45° steps; Shift = smooth. - thin chevron profile + press-drag-release commit retained. * fix(mep): orient duct roll arc consistently + drop Ctrl-vertical drag Build a fully-determined basis for the duct roll gizmo so the curved arrow always seats at the top-outer 45° corner regardless of run direction, instead of an arbitrary apex from a single setFromUnitVectors. The selection-rig ±Y arrows now own vertical movement, so the redundant Ctrl-modifier riser drag is removed from the fitting move tool. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(mep): run-aligned duct handles, swing snapping, elbow flatten Align the duct run-center cube + horizontal arrows to the run axis (matching the per-vertex handles) while keeping whole-run translate. Endpoint side / up-down swing arrows now follow grid snap points and port-snap onto nearby collars (Shift sweeps smoothly). Relax elbow realign + fitting schemas to flatten to a straight 0° coupling. Surface HVAC-specific hints in the select-mode helper panel for duct / fitting. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(mep): per-segment linesets/liquid-lines with joint-follow editing Linesets and liquid lines now commit one independent two-point node per drawn segment instead of folding into a single mitered polyline, so each line selects and deletes on its own. Endpoint caps fill shared-coordinate joints so connected segments still read as continuous pipe. Dragging a shared endpoint carries mated segments along via port connectivity (Alt detaches), so a run still edits as one welded piece. Liquid-line follow mode traces the whole connected lineset run, laying a per-segment parallel line down its full assembled length. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(mep): vertical-offset auto-routing on duct center-cube ±Y drag Lifting/lowering a connected run with the run-center cube now keeps each connected end welded to its stationary partner instead of dragging the whole network. Run-to-run ends get the classic S/Z offset (two elbows + plumb riser, partner trimmed back one leg); elbow-connected ends form a clean L — the existing elbow stays put and re-aims its collar vertical, with one new top elbow + riser reconnecting to the lifted endpoint. The offset is ghosted live and minted as a single undo step on release. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Add roof accessory placement guides Measure roof accessory placement against the active roof face using visible surface bounds and preview geometry footprints. Add dormer-local guides and special linear handling for ridge vents and gutters. * Improve duct and placement routing * Fix duct vertical movement routing * Fix duct vertical offsets and roof accessory movement * Add DWV movement parity and line endpoint controls * Fix MEP handle review issues * Fix chimney placement and duct offset cleanup * Use snapped targets for roof accessory commits * fix(nodes): repair MEP movement review issues - auto-fitting: tee branch now follows the drawn lateral angle; update the stale square-tee test + doc comment that contradicted the rewrite - duct-segment: re-enable the vertical auto-offset rewind (the disabled stub left mintedIds empty, so re-dragging a tagged duct stranded old elbows/risers and stacked duplicates); remove the dead stub - duct-segment: strip the stale auto-offset tag on manual corner/roll commits so the horizontal-move path no longer trusts an out-of-date base - chimney: resume history before mutating segment children arrays so a cross-segment move reparents in one tracked transaction (undo stays consistent) - dormer: align schema test with the new windowSill=false default * fix: address mep movement review issues * fix: address follow-up mep review comments * fix: address additional mep review comments --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: pascal-open-bot <open@pascal.app> |
||
|
|
9bfbcc9f01 |
editor: add resize SFX triggered while dragging resize handles
Register a `resize` sound effect (three round-robin variations with pitch/pan jitter and an 80ms gap) and emit it from the linear/radial resize drag handler whenever the snapped+clamped value steps, so width / length / depth / radius / height drags tick as the size changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
bf9246cd36 |
feat(sfx): play paint_apply sound when a material is painted
Registers a paintApply SFX (with rate/volume jitter + a 60ms gap so rapid multi-face painting doesn't machine-gun), wires the sfx:paint-apply bus event, and emits it from the material-paint click chokepoint in the selection manager (fires on apply, not hover/preview). Adds the audio asset to the editor app's public resources. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
bfcb039757 |
chore(items): pull authored item GLBs from prod
Replace 89 bundled item model.glb files with the authored/optimized versions currently served from prod storage (items/system/<slug>/model.glb), so the committed source of truth matches what live projects load and a catalog re-seed no longer clobbers authored geometry. Items with no prod object (404) are left as-is. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c4b6e74d65 |
Merge remote-tracking branch 'origin/main' into feat/paint-slots
# Conflicts: # packages/core/src/store/use-scene.ts # packages/editor/src/components/editor/index.tsx |
||
|
|
bba0cc5f68 |
feat(paint-slots): prepared-drywall finish + fence 4 slots matching build options
- Add 'concrete-drywall' (Prepared Drywall) catalog finish + 512 KTX2/webp runtime assets; default walls (WALL_SLOT_DEFAULT interior+exterior) and the roof wall/trim band to it so roofs read continuous with walls. - fence: replace the 2-slot panel/rail split with 4 slots that match the panel's build options — posts / infill (showInfill) / base (grounded only) / rail — each its own mesh with userData.slotId; conditional slots track the build state. Defaults: posts/infill/base charcoal, rail wood-finewood27. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4c27558a4f |
chore(paint-slots): drop 50 unreferenced editor material maps
Remove height/displacement/mask/mortar/joint texture files the material schema never samples (it reads only albedo/normal/roughness/ao/metalness). Computed as present-on-disk minus catalog-referenced in material-library.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
267dac7b1a |
perf(editor): convert UI icons from PNG to WebP
The editor's /icons assets were ~10MB of oversized PNGs (a single toolbar icon up to 1.4MB). Convert every non-PWA icon to WebP (quality 92) and repoint all /icons/*.png references to .webp across packages/editor, packages/nodes, and apps/editor. PWA/platform icons (apple-touch-icon, icon-192, icon-512) stay PNG. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
737c4e9d1d |
feat(paint-slots): concrete-plate finish + try defaults (wall=concrete, slab=wood plank 48, ceiling=soft white)
- material-library: add 'concrete-plate' KTX2 finish (512, fabric/leather-style pipeline) + editor-app texture mirror. - viewer: shared resolveSlotDefaultMaterial(colour|library ref) so a kind's slot default can be a catalog finish, not just a flat colour. - wall default -> library:concrete-plate (interior + exterior). - slab default -> library:wood-woodplank48 (slab geometry resolves it via the shared helper). - ceiling default -> soft white #f2eee6 (ceiling renders flat-tinted). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
54a24e4c5c |
fix: address review findings from PR #402 MEP systems
- core: fix layer violation in level-height.ts — extract sceneRegistry
import, replace with optional WallBaseYResolver callback so core stays
pure (no Three.js mesh state); viewer callers pass resolver, headless
callers (MCP/tests) get deterministic node-data-only result
- core: generalise port-connectivity service from duct-only to all
distribution families — match partners by distributionRole ('run' →
endpoint stretch, 'fitting' → rigid follow) instead of hard-coded
duct-segment/duct-fitting type names; add system-compat guard so
cross-system ports (e.g. supply duct vs waste pipe) don't fuse
- editor: fix port-snap rotation bug in move tool — pass preview node
at live rotation into resolvePortSnap so own-port positions reflect
any mid-drag R/T rotation before computing the snap delta
- editor: wire pipe-trap into UI — add to StructureTool union,
MepToolKind, MEP_ITEMS Build-tab tile, and structure-tools action menu
- test: add port-connectivity-pipe.test.ts — 2 tests covering
pipe-fitting → pipe-segment endpoint drag and cross-system isolation
- test: fix stale pipe-auto-fitting.test.ts wye expectation — author
deliberately chose square sanitary-tee for DWV side-taps (documented
in PR description and PipeFittingNode schema); update the one test
that still expected wye to match the implemented behaviour
- nit: fix optional-chain biome warning in validate-dwv.ts
|
||
|
|
5551500d98 |
feat: HVAC ductwork + DWV plumbing systems (#402)
Adds two new MEP node families (HVAC ductwork, DWV plumbing) built on a shared port-connectivity model. Co-authored by @sudhir9297. |
||
|
|
042f855586 |
feat(paint-slots): KTX2 finish library — fabric/leather/concrete/metal (phase 4)
Wire KTX2 into the catalog finish path and add 15 textured finishes across four new families, all as GPU-compressed KTX2 (512px) + a webp picker thumbnail. - Shared `ktx2-loader.ts`: one KTX2Loader (Basis transcoder) used by both the GLB loader and catalog textures; `ensureKtx2Support(renderer)` runs once at viewer init (GPUDeviceWatcher) so `.ktx2` finishes load even with no GLB in the scene. `use-gltf-ktx2` now reuses the shared instance. - `materials.ts`: texture loaders pick the KTX2 loader for `.ktx2` urls, the image loader otherwise (all three load paths). - `material-library.ts`: 15 entries (fabric ×6, leather ×2, concrete ×4, metal ×3). Neutral albedo for tinting, `flipY: false` (compressed textures can't flip), `repeat` per real-world tile size, metals `metalness: 1`. Normals encoded UASTC, data maps ETC1S/linear. Assets generated from raw sources via the new community `scripts/build-material-textures.ts`. 1024/256 tiers + raws kept out of git. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
aa3b0ef758 |
refactor: release-review cleanup for roof wall openings
Dual review pass (Claude multi-angle + Codex release-quality). One correctness fix and the agreed do-now cleanups: - fix: clone-scene-graph remaps roofSegmentId like wallId in both clone paths — duplicated scenes/levels kept pointing roof-hosted children at the original segments. - extract the settled, stateless roof target/cursor math shared by the four door/window tools into shared/roof-wall-opening-placement.ts (resolveRoofWallOpeningTarget + getRoofWallOpeningCursorPose + worldToSelectedBuildingLocal); tools keep the stateful lifecycle (drafts, undo/temporal, commit field lists). −199 net lines. - rename host-generic state: currentWallId→currentHostId, markWallDirty→markHostDirty (they hold segment ids too); capability cascadesViaHostSegment→dirtyHandledByOwnSystem (behavior-facing, before the public API hardens). - drop getRoofAccessoryKinds from core's public API — its only caller was the standalone Build tab, which now enumerates the registry inline with its app-specific filter. - window move-tool uses the shared stripPlacementMetadataFlags; stale "segment-local" comment fixed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b8dfa90762 | Merge remote-tracking branch 'origin/main' into feat/editor-ux-rendering-placement | ||
|
|
3a1ba56246 | Polish editor interaction behavior | ||
|
|
bdfee058bd |
feat: doors and windows on roof-segment wall faces
Openings now host on the walls a roof segment generates — the base walls under the roof and the coplanar gable/shed/gambrel end faces, so a window can sit in a gable pediment. - core: roof-segment-walls.ts models the four vertical faces as 2D frames (u along face, v height) with convex profile polygons that mirror the wall volume getRoofSegmentBrushes builds; rect-in-profile clamping and anchored resize limits via half-plane algebra. - schemas: optional roofSegmentId on door/window; position is the segment-local wall mid-plane center, rotation[1] the face yaw. - cut: reuses capabilities.roofAccessory.buildCut; new cutScope: 'wall' subtracts from the wall brush only. cascadesViaHostSegment keeps the roof-merge loop from consuming door/window dirty marks (their own systems cascade via parentId). - tools: roof:* handlers in door/window tool + move-tool (the Build-tab preset path), with roofSegmentId cleared/restored across every roof<->wall re-anchor and revert; shared hit resolver normalizes normals through world space (merged mesh vs painted segment frames). - fix: RoofSystem no longer rebuilds per-segment CSG in accessory-reveal mode — the uncut rebuild used to draw over the merged shell's fresh opening until deselect. - fix: the walkthrough collider world now prunes by renderer-effective visibility; stale uncut segment CSG inside the hidden segments-wrapper blocked the player at openings the merged shell had cut through. Known gap: painted segments render per-segment CSG without accessory cuts (pre-existing, also affects skylight/dormer). Twice Codex-reviewed; details in private-editor plans/editor-roof-wall-openings.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
478f0910f7 |
feat(editor): roof features in standalone Build tab + thumbnail flag fix
- registry: getRoofAccessoryKinds() enumerates kinds declaring the roofAccessory capability, in deterministic builtin-list order (mirrors getSelectableKinds); exported from the registry barrel. - apps/editor build-tab: a "Features" group under the Roof tile, discovered from the registry (no DB) and activating each kind's roof-attach tool — parity with the community editor's roof features. - site-boundary-editor: hide the flag handle group around thumbnail captures (they render on SCENE_LAYER so the thumbnail camera can't layer-filter them), matching handle-arrow.tsx — keeps flags out of preset/snapshot thumbnails. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c57901e7d9 |
chore(deps): review-pass fixes — pin next 16.2.6, drop dead dep, dedupe three
- next 16.2.7 -> 16.2.6 in both apps. 16.2.7 regresses Turbopack dev route matching for API routes nested below a dynamic segment (probed in the private repo: /api/items/[id]/fork, /api/scenes/[id]/events 404 with the router's HTML not-found; production builds unaffected; bisected 16.2.6 good / 16.2.7 bad). 16.2.6 carries all the 16.2.5/16.2.6 security fixes — only the bugfix backports are forgone. - Remove @react-three/uikit-lucide from @pascal-app/editor dependencies: zero imports in this repo or private-editor; dead weight in every npm consumer's install. - Pin @visual-json/react "latest" -> "^0.4.0" (current resolution; "latest" in a published package's deps is unreproducible and was inconsistent with private-editor's ^0.4.0). - Add root override three: 0.184.0, mirroring private-editor's dedupe — drops the nested stats-gl three@0.170.0 so the lockfile resolves a single three (the stated single-instance invariant now holds here too). Verified: build, check-types, biome check, 931 package tests green; lockfile resolves one next (16.2.6) and one three (0.184.0); oxide/lightningcss optionalDependency pins still match resolutions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9d24e260c3 |
chore(deps): June 2026 dependency refresh
Range edits: - next 16.2.1 -> 16.2.7 in apps/editor + apps/ifc-converter. 16.2.5/16.2.6 fixed six high-severity advisories (middleware bypass, DoS, SSRF, XSS, RSC cache poisoning); 16.2.7 is bugfix backports. No API/config changes. - typescript 6.0.2 -> 6.0.3 everywhere, including aligning the 5.9.3 stragglers (@pascal-app/mcp, @repo/ui, @repo/eslint-config) that date from the original scaffold and were never intentionally held back. - @types/node in @pascal-app/mcp: ^25.5.0 -> ^22.19.20 — mcp was the only workspace typed against non-LTS node 25; 22 matches the release workflow's node and every other workspace. - @number-flow/react ^0.5.14 -> ^0.6.0 (packages/editor, apps/editor); the only breaking change (removed --number-flow-char-height CSS var) is unused here. - agentation ^2.3.2 -> ^3.0.2 (apps/editor devtool; v3 is a drop-in for the props-less <Agentation /> usage, additions are opt-in). - Root overrides: @types/react 19.2.14 -> 19.2.17, @types/three 0.184.0 -> 0.184.1 (types-only fixes). Lockfile refresh within existing ranges picks up react 19.2.7 (pairs with next 16.2.7 — 19.2.6 had a server-action FormData regression), motion 12.40.0, three-mesh-bvh 0.9.10, @react-three/uikit-lucide 1.0.73, the June radix wave, lucide-react 1.17.0, zustand 5.0.14, tailwind-merge 3.6.0, geist 1.7.2 (fixes Geist Mono ligature regression), react-grab 0.1.44, biome 2.4.16 + ultracite 7.8.2, turbo 2.9.17. Held: three stays 0.184.0 (npm latest; single-instance constraint), tailwindcss 4.3.0 + lightningcss 1.32.0 already match the pinned optionalDependencies native binaries, eslint 10 major not taken (@repo/ui is unconsumed legacy scaffold). Verified: turbo build, check-types, biome check, and 931 package tests green; bun.lock stays lockfileVersion 1 (CI bun 1.3.0 compatible); single next/three resolution confirmed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
555cf06c12 |
editor: building rotation, alt-click single wall, world-grid alignment, tap-to-engage move (#388)
* Add roof surface placement support for items Items (e.g. solar panels) can now be placed on sloped roof surfaces. The placement system computes euler rotation from the roof surface normal so items sit flush on the slope instead of going inside. - Add roofStrategy to placement-strategies with enter/move/click/leave - Wire roof:enter/move/click/leave events in the placement coordinator - Add calculateRoofRotation in placement-math using surface normals - Support full 3D cursor rotation for sloped surfaces - Items on roofs are parented to the level with world-space rotation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fixed conflict * Snapshot: ridge vent locks to segment ridge during placement, free sliders after Placement tool clamps the cursor to the segment's ridge line for all roof types (gable, hip, shed, gambrel, dutch, mansard; flat rejected). Renderer re-derives Y from the live surface plus a small lift, and treats stored position[1] / position[2] as user offsets so the inspector sliders move the vent after placement. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(roof): contribute outer silhouette to alignment-guide candidates Roof has no centred-box footprint (it's the union of its `roof-segment` children) so the capability bridge needs the resolved AABB directly. We build it from the children's corners in roof-local space, then transform to world coords. Roofs only contribute as static candidates — the move-roof tool drives them by origin, so the relocatable-box path never applies. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(floorplan): upright text geometry that stays horizontal under scene rotation Floor-plan text labels were rotating with the 90° default scene rotation, making zone names read sideways. Add an `upright` flag to text geometry: when set, the registry layer counter-rotates the label by sceneRotationDeg around its anchor so it reads horizontally on screen. Zone labels opt in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(editor): redesign material-paint cursor badge and gate floor-plan paint mode Paint cursor swaps the inline icon+label chip for a stacked badge — a glowing accent stem pointing down to the hit point with the paint icon above — so the cue reads as "pointer" rather than "tooltip". Switches the accent to indigo. The 2D floor-plan now also shows the paint-icon overlay and routes pointer interactions to the painter when in material-paint mode. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(editor): 90° rotate action on the floating building menu Adds a Rotate button to the building action menu (and a generic `onRotate` slot to NodeActionMenu). Each click rotates the building 90° CW around its world-bbox center, mirroring the offset compensation `MoveBuildingContent` uses during R/T-rotate drags — so the building spins in place instead of orbiting its (often off-centre) origin. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(column,shelf): show cursor sphere during placement preview Matches the placement affordance other floor-placed tools already have so users see the snap point as a discrete dot alongside the ghost mesh. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(ifc-converter): update next-env types path to .next/dev/types Next.js moved generated route types under `.next/dev/types/` — regenerate the reference so the type-check resolves them again. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ridge-vent): track parent segment's live overrides for real-time follow While dragging a roof-segment resize handle (width / depth / pitch / wallHeight / rotation), the new dimensions stream through useLiveNodeOverrides and only flush to the store on release — so the ridge vent was snapping to the new ridge only after the drag ended. Merge segment overrides into the renderer's segment view so the vent rides the resize in real time. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(editor): step building rotation in 15° increments Cuts the floating building menu's rotate-step from 90° to 15° so each click nudges the building rather than slamming it to the next cardinal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(editor): world-frame alignment guides and grid snap under building rotation Brings the 2D floor-plan alignment + grid behaviour in line with 3D so guides and snap follow the world XZ grid regardless of how the active building is rotated. Core: adds resolveAlignmentInBuildingWorld + snapWorldXZToBuildingLocal, plus a shared BuildingPose type, so callers can resolve in world space while staying in building-local coords downstream. Editor: introduces packages/editor/src/lib/world-grid-snap.ts as the single entry point for tools that need world-frame alignment / grid snap (resolveAlignmentForActiveBuilding, getActiveBuildingPose, snapWorldXZForActiveBuilding, snapBuildingLocalToWorldGrid). Honours useLiveTransforms and resolves the active building via level.parentId first so the helper stays in sync with the floor-plan panel. Alignment store unification: applyFloorplanAlignment and the registry move overlay now publish guides in world XZ — the same frame the 3D tools already use — so the shared useAlignmentGuides store carries one consistent coordinate system. The 2D layer renders world XZ via the fixed world → SVG transform (a 90° rotation around the building's world position; independent of building rotation), so guides come out parallel to the world-axis-aligned floor-plan grid in every case. Drafting + move sessions across walls, fences, slabs, ceilings, columns, items, shelves, roofs, stairs, zones and elevators are re-routed through the new helpers so cursor placement, grid snap, draft endpoints, and move commits all land on the world XZ grid even when the building has been rotated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * revert(editor): drop building drag-rotate and grid building-XZ chase Two behaviours added recently were causing the world grid to slide around during building edits. Reverts both, keeping the rest of the world-frame alignment work intact. - floating-building-action-menu / node-action-menu: roll the rotate button back to a single click that increments rotation by 15° around the bbox center (state at 3eb02fde). The drag-rotate from b50c0d5a streamed live position updates per frame, which the floor plan and grid both followed. - grid: stop chasing the active building's world XZ each frame (pre-c00a2469 behaviour). The grid stays anchored at world (0, 0) so it doesn't visibly sweep when the building origin moves — during a drag, an R/T rotation in the move tool, or a click rotate that re-anchors the origin to the bbox-pivot. Y still lerps to the active level's floor height. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(editor): consistent building rotation pivot + 2D position sync Several connected fixes to building rotate/move so the 3D mesh, the 2D floor plan, and alignment guides all stay visually consistent across rotations and drags. Rotation-invariant pivot - New `getBuildingLocalBboxCenter` helper walks descendant mesh geometry bounds in the building's LOCAL frame, returning a centroid that doesn't depend on the building's current world rotation. The previous approach derived the pivot from the world AABB at the current rotation, which made the local pivot subtly drift each time the building was rotated. - Floating menu click-rotate and move-tool R/T rotation now seed their pivot from this helper, so successive rotations stay pinned at the same world point. Move-tool stability - R/T rotation always pivots around the current world bbox center, even before the cursor has moved onto the grid (previously it fell back to rotating around the origin in that case). - Commit (`onGridClick`) now writes the mesh's actual on-screen pose instead of recomputing it from the click event's grid coords, removing a one-cell snap on release. Alignment resolver - Tie-break is now perp-first, primary-second. The previous order was reliable when wall faces shared an exact axis-aligned coord (pre-rotation), but post-rotation float deltas meant primary never tied and the resolver could lock onto the far corner of a candidate. Perp-first picks the visually closest point. 2D ↔ 3D sync - The floor-plan SVG scene group now applies a live `translate(...)` derived from `(committedBuildingPosition → live buildingPosition)`, in addition to the existing live rotation. Building-local content slides in lockstep with the 3D mesh during a drag. - The alignment-guide layer projects from world XZ using the COMMITTED building position, so guides stay locked to their world coordinates while the building slides past them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Merge remote-tracking branch 'origin/main' into fix/fir-5-june * feat(wall): alt-click commits a single wall instead of chaining Holding Alt on the click that completes a segment now stops drafting instead of starting a new segment from the just-placed endpoint. Same behaviour wired in both the 3D wall tool and the 2D floor-plan placement hook (which already passed `singleWall: event.altKey` — the 2D handler was ignoring the flag). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * revert(editor): drop 15° rotate from floating building action menu Removes the rotate button on the floating building menu and the `getBuildingLocalBboxCenter` pivot helper that only existed to support it. The selected-group rotate handle remains the way to rotate a building. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(ifc-converter): point next-env at .next/types instead of .next/dev/types Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(editor): unify 3D move grip with floating-menu Move via tap-to-engage The translate gizmo on the 3D bounding box now hands the node to the registry move tool (same path as the floating action menu's Move button) instead of running its own plane-drag. One move flow — green bounding box, cursor follow, alignment guides, R/T rotation, click-to-commit — drives both entry points. Adds a `dragBounds` capability on the node registry so kinds whose rendered mesh contains extras (per-level landing assemblies on an elevator, etc.) can declare the box the user thinks of as "the thing being dragged." Elevator and stair opt in; everything else still auto-measures. `MoveRoofTool` now shows the green box for whole-stair and whole-roof moves, and aligns roofs by their footprint corners (previously only stairs did). Slab floor-plan moves clear `autoFromWalls` on commit so space-detection doesn't snap the slab back, via a new `extraCommitFields` hook on the polygon-centroid move target. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(editor): 2D alignment guides in building-local frame + world-grid wall snap The 2D floor-plan alignment layer now mounts inside the rotated scene <g> and reads from an editor-local `useAlignmentGuides` store — guides are stored in building-local meters and follow the SVG transform that already carries the rest of the floor-plan geometry. Pill labels are counter-rotated by the scene rotation so they stay upright under building rotation. The 3D pipeline keeps its own world-frame store; the two surfaces no longer share frames. Wall drafting picks up an optional `gridSnap` override so the 2D floor-plan can snap drafts onto the world-XZ grid even when the active building is rotated — without it, a rotated building's drafted wall would chase the local grid and miss the visible lines. `snapPointTo45Degrees` and `snapWallDraftPointDetailed` route through the override when supplied; otherwise the prior local-axis grid snap is unchanged. The floor-plan panel wires `snapBuildingLocalToWorldGrid` through the placement hook and exports `WALL_GRID_STEP` to support it. Also fixes the minor/major grid stroke palette swap so minor lines render with the minor palette and majors with the major palette. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(editor): drop dead onRotate slot, memoize dragBounds snapshot Removes the unreachable `onRotate` prop, `RotateCw` import, and rotate button from `NodeActionMenu` — its only caller was the floating building menu's 15° rotate, which was removed in d65fd5fe. Memoizes `MoveRegistryNodeTool`'s `dragBounds` lookup so it captures the scene snapshot once per node instead of re-reading `useScene.getState().nodes` on every render. Bounds depend only on `node` (locked for the tool's lifetime) and start-time sibling state (elevator shaft height from the level set), so a one-shot snapshot is the right semantics — and it avoids implying a live subscription that isn't there. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: biome check --write (import order, formatting, unused import) Auto-fixes from `bun check:fix`: - import-order normalization in editor/nodes/core - unused `snapToGrid` / `useLayoutEffect` imports dropped - minor whitespace/wrapping tweaks No behavioural change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
b33059ef81 | feat: add persistent site boundary handles | ||
|
|
ce6f999310 |
arch: enforce layer boundaries — ceiling dispatch, store relocation, shared helper (#382)
* Add roof surface placement support for items Items (e.g. solar panels) can now be placed on sloped roof surfaces. The placement system computes euler rotation from the roof surface normal so items sit flush on the slope instead of going inside. - Add roofStrategy to placement-strategies with enter/move/click/leave - Wire roof:enter/move/click/leave events in the placement coordinator - Add calculateRoofRotation in placement-math using surface normals - Support full 3D cursor rotation for sloped surfaces - Items on roofs are parented to the level with world-space rotation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fixed conflict * editor: per-door-type floor-plan symbols Render a distinct, static plan symbol for each door type in the registry floor-plan builder (`packages/nodes/src/door/floorplan.ts`), independent of the door's live open/close animation: - single / hinged: fixed 90° swing with a dashed quarter-circle arc - double / french: two mirrored half-width leaves + dashed arcs - folding / bifold: static zigzag accordion (~80% span) on the wall face - sliding: bypass — two overlapping panels on parallel tracks + arrow - pocket: thin white leaf, ~60% closed, sliding into the solid wall - barn: surface-mounted panel parked over the wall, dashed closed-ghost + slide arrow The swing arc is dashed in screen-pixel units (the renderer uses non-scaling-stroke). Symbols are oriented by hingesSide / swingDirection / slideDirection as appropriate. Also includes pre-existing working-tree changes unrelated to the door symbols: group move/rotate transform and box-select tweaks, and a regenerated ifc-converter next-env.d.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Fix recessed ceiling fixtures and draw safety * feat(editor): magnetic wall-snap with per-kind beacon (2D + 3D) Snap the wall draft / endpoint-move point onto existing wall geometry — corners, midpoints, wall–wall intersections, and along-wall edges — and show a beacon at the snap point whose glyph encodes what it caught (square = corner, triangle = midpoint, ✕ = intersection, circle = edge). - Pure snap geometry extracted to wall-snap-geometry.ts (unit-tested). - Ephemeral useWallSnapIndicator store drives a 3D pillar+glyph beacon and a 2D SVG glyph beacon, both indigo to match the alignment guides. - Gated by a new persisted "Magnetic snap" toggle in the Display menu (useEditor); honored by draw + commit + endpoint-move in both views. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * editor: garage and open-doorway floor-plan symbols Extend the per-door-type plan symbols in the registry floor-plan builder (packages/nodes/src/door/floorplan.ts): - open doorway (openingKind === 'opening'): bare gap, no leaf/arc/panel (mirrors the 3D system, which renders only the cutout for openings) - garage sectional: closed leaf + side tracks into the garage + dashed parked ghost at the inner end - garage roll-up: closed leaf + coil barrel (capsule) with a coil hint - garage tilt-up: closed leaf + dashed parked panel + dashed curved up-and-over swing path - gate the swing arc to actual swing doors (hinged/double/french) so other types fall back to the plain footprint Garage mechanisms sit on the interior (door-local -z) side to match the 3D garage builders, independent of swingDirection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * arch: enforce layer boundaries — registry dispatch, store relocation, shared helper Three architectural fixes to bring the branch into full compliance: 1. **ceiling-system kind check → CeilingCutCapability** Replace `child.type === 'item'` branch in `ceiling-system` with registry dispatch. Add `CeilingCutCapability` type to `packages/core` registry types, implement `buildCeilingHole` on `itemDefinition`, and rewrite `collectRecessedItemHoles` → `collectCeilingHoles` to dispatch through `nodeRegistry` — viewer never again inspects a node's kind directly. 2. **useAlignmentGuides + useWallSnapIndicator → packages/editor** These stores are editor-only UI (snap beacons, alignment guides). Move them from `packages/core/src/store/` to `packages/editor/src/store/`, re-export from `packages/editor`, and update all 34 consumer files across `packages/editor` and `packages/nodes` to import from `@pascal-app/editor`. 3. **findLevelAncestorId extracted to core** `item-light-system` had a private `resolveNodeLevelId` that duplicated level-ancestor traversal logic. Extract it as `findLevelAncestorId` in `packages/core` (spatial-grid-sync), export it, and replace the local copy. All four packages typecheck cleanly (zero errors). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: fix lint, untrack .claude/launch.json Run bun check --write to clear 8 Biome errors (formatting + import order + one unused import). Untrack .claude/launch.json and add it plus .claude/settings.local.json to .gitignore so local IDE/agent configs stop landing in commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
b2e7a090d6 |
chore(editor): update structure_delete SFX asset (#381)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
dd0215643b |
fix(editor): shelf deletion plays item-delete SFX; refresh delete sounds (#379)
* fix(editor): shelf deletion plays item-delete SFX; refresh delete sounds Shelves are furniture-like placeable objects, so deleting one should use the lighter item-delete cue instead of the structure-delete one. Add a shared emitDeleteSFX(nodeType) helper that maps item-like node types (item, shelf) to item-delete and everything else to structure-delete, and route the three delete sites (sledgehammer, floating action menu, keyboard) through it. Also refresh the item_delete/structure_delete audio assets and reduce their pan jitter for a steadier delete cue. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(editor): update item_delete SFX asset Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
1419709131 |
editor: structure build-start/end cues + menu hover/click SFX (#378)
* wip sfx * wip audios * feat(editor): structure build-start/end cues + menu hover/click SFX Sound design pass for the build/items experience: - Split structure-build into a start cue (draft begins) and end cue (commit). Wall/fence fire start on the first click; slab/ceiling/zone tick start on every non-closing vertex; roof on the first corner. - Doors and windows now use the structure-end cue on place + move; shelves use item-place instead. - New menu_hover (round-robin retired in favour of a single sample) and menu_click cues wired to the Build/Items main categories, item-catalog tiles, the icon rail / tab bar, and the bottom action menu (via the shared ActionButton). - Refresh item_pick / item_place / item_rotate samples; drop the now-unused numbered menu_hover variations. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(editor): silence forEach return lint in updateSFXVolumes Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(editor): convert SFX wavs to mp3 to shrink assets Re-encode all SFX samples as 128kbps mp3 (~94% smaller, transparent for short UI cues) and point the player at the .mp3 sources. Drops the .wav originals; the freshly converted item_pick/place/rotate.mp3 override the older committed mp3s. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
8f2e5678e8 |
ci: add lint and typecheck workflow (closes #147) (#369)
* fix: resolve lint and type errors to enable CI - Remove stale biome-ignore suppressions in bootstrap.ts, r3f.d.ts, and parametric-node-renderer.tsx (rules no longer fire). - Replace forEach callbacks that return values with for...of loops in node-actions.ts and build-collider-world.ts (lint/suspicious/useIterableCallbackReturn). - Rewrite assign-in-expression guards in ceiling/tool.tsx to explicit if-statements (lint/suspicious/noAssignInExpressions). - Hoist useMemo/useCallback above early return in chimney/panel.tsx (lint/correctness/useHookAtTopLevel). - Add `^build` to turbo check-types dependsOn so packages/core dist is up-to-date before type checking — this resolves all 32 type errors which were caused by stale dist, not missing symbols. * ci: add lint and typecheck workflow on push/PR Adds a quality gate that runs `bun run check` (Biome) and `bun run check-types` (TypeScript) on every push and PR to main. Uses concurrency groups to cancel stale runs. Closes #147. --------- Co-authored-by: Pascal <open@pascal.app> |
||
|
|
d1b40aa98d |
editor: level-scoped alignment, reference-floor symbols & registry slab tool (#373)
* feat(editor): level-scoped alignment, reference-floor registry symbols, registry slab tool - Scope alignment candidates to the active level so a node directly below on another floor no longer snaps (alignment is XZ-only); building-scoped nodes like elevator shafts stay in the pool across floors. - Derive the elevator alignment footprint from its outer shaft (not the inset cab), so its guide actually surfaces within the snap threshold. - Extract shared stair footprint geometry (rotateXZ, segment transforms, stairFootprintAABB) so opening-sync and alignment anchors derive the chain identically; stairs now contribute plan bbox anchors. - Render reference-floor stairs/roofs/elevators/shelves/spawns through their registry floorplan builders for pixel-identical symbols. - Move slab creation to the registry-driven slab tool (parity with ceiling); 2D handlers only maintain draft state. - Lift the 3D alignment guide ribbon to the active level's Y each frame. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(core): registry-drive elevator/stair alignment footprints Address architecture review of the level-scoped alignment work: the core anchor bridge (`alignment-anchors.ts`) hardcoded `if (node.type === 'elevator')` and `if (node.type === 'stair')` branches to derive their plan footprints. Move that onto the kinds themselves via a new `alignmentFootprint` capability so the bridge dispatches generically — matching the registry composition model. - Add `Capabilities.alignmentFootprint`: returns a `box` (rotatable rect centred on position, relocatable for movable kinds) or an `aabb` (already resolved, for non-rectangular plan shapes). Elevator uses `box` (its outer shaft, and it's movable); stair uses `aabb` (segment chain / annular sector, moves by origin). - Drop both hardcoded branches; the bridge now consults the capability via `floorFootprint` (box) and a unified `alignmentAABB` (box ∪ aabb). - Export `stairFootprintAABB` from core so the stair definition consumes it. - Tests register synthetic defs carrying the capability (the bridge no longer knows elevator/stair by name), reproducing the production glue from the same core helpers. - Document why `REFERENCE_REGISTRY_KINDS` is a deliberate editor-local curation, not an auto-derived set (most floorplan-builder kinds shouldn't appear as standalone reference symbols, and "reference floor" is an editor concept core must not know). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(editor): hide node arrow handles during thumbnail capture The arrow-handle rig lives on SCENE_LAYER (so its chevrons read as proper 3D plates), but the thumbnail camera only filters EDITOR_LAYER + GRID_LAYER — so a node selected at capture time would leak its arrows into the snapshot. Hide the rig on `thumbnail:before-capture` and restore it on `thumbnail:after-capture`, the same emitter handshake SelectionManager uses. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
46f94b97b3 |
editor: alignment guides + floor-plan move/placement parity (#372)
* Add roof surface placement support for items Items (e.g. solar panels) can now be placed on sloped roof surfaces. The placement system computes euler rotation from the roof surface normal so items sit flush on the slope instead of going inside. - Add roofStrategy to placement-strategies with enter/move/click/leave - Wire roof:enter/move/click/leave events in the placement coordinator - Add calculateRoofRotation in placement-math using surface normals - Support full 3D cursor rotation for sloped surfaces - Items on roofs are parented to the level with world-space rotation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fixed conflict * feat(editor): 3D alignment guides for item/wall/fence move + placement Bring Figma-style alignment guides into the 3D editor, reusing the shared pure resolver (`resolveAlignment`) and ephemeral guide store (`useAlignmentGuides`) that previously only drove the 2D floor plan. Core: - `alignment-anchors.ts`: node→anchor adapters (footprint AABBs, corner anchors, wall/fence segment anchors) + `refineGuidesToGap` so a guide's line and distance read to the candidate's nearest edge, not the far side. - `bboxCornerAnchors` + corner-only footprint anchors so alignment locks to item edges, never centrelines. - `resolvePointSnap` (point-coincidence variant; kept for future use). Editor: - `Alignment3DGuideLayer`: dashed ribbon + flat floor dots + distance pill, in the project's indigo accent, mounted inside ToolManager's building-local group so guides render in the cursor's frame. - Producers wired in the item move tool, item placement coordinator, and the wall + fence endpoint tools; walls and fences cross-align. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(editor): align guides snap to nearest real anchor, drop bbox re-span The 3D alignment guide could place its end dot in empty space: a diagonal wall (or any rotated / non-rectangular object) has bounding-box corners that don't lie on the object, and `refineGuidesToGap` re-spanned the guide to exactly those AABB edges — so the dot floated "along the coordinate" rather than on the item. - `resolveAlignment` now tie-breaks to the candidate anchor NEAREST on the perpendicular axis (after the tightest axis match). Anchors are real points (corners / endpoints / midpoints), so the guide always connects to the closest actual point — which also yields the facing-edge gap distance. - All four producers (item move, item placement, wall + fence endpoints) now publish the raw resolver guides; the AABB nearest-edge re-span is gone. - Removed the now-dead `refineGuidesToGap` and `resolvePointSnap` helpers (and their tests / exports). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(editor): group move handle + shared group-transform core, move-tool polish Add a group-move gizmo alongside the existing group-rotate handle, both driven by a new shared `group-transform-shared` module (participant classification, group-box + corner math, connected wall/fence component expansion so attached structure transforms rigidly as one piece). - core: refactor alignment-anchors collection + tests, extend handle registry - editor: group-move-handle, group-transform-shared; rotate handle reuses them; node-arrow-handles gains click-swallow guard; box-select + placement tweaks - nodes: move-tool updates across ceiling/column/slab/roof/registry; door math and panel adjustments; item definition cleanup - nodes(fence): play `sfx:grid-snap` ticker on endpoint move, matching the wall endpoint tool (fixes missing audio feedback on fence side drag) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(editor): keep autosave alive across page unload The autosave debounces writes by 1s and relies on a `beforeunload` flush for anything still pending. That flush fired a plain `fetch` PUT, which the browser cancels the instant the page unloads — so refreshing right after an edit (e.g. painting a roof material) silently dropped the change and the reload showed the last persisted scene. Thread a `{ keepalive }` option through the save callback and set it on the unload flush so the request survives the unload. Also listen for `pagehide` (fires where `beforeunload` does not, e.g. mobile Safari / bfcache) and clear the dirty flag up front so the two listeners don't double-send. Normal debounced saves omit `keepalive` (its 64KB body cap only constrains the best-effort unload flush, not regular saves). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(editor): paint eraser + reset-all, drop roof cross-role bleed Material paint gains an eraser (clear a surface back to its default) and a "Reset all" action that defaults every painted surface on a node — for a roof that includes each child segment — via a generic `buildResetSurfaceMaterialUpdates` that nulls catch-all and role-specific material fields without per-kind knowledge. Also stop a single painted roof surface from bleeding onto the others: `getEffectiveRoofSurfaceMaterial`, `getRoofMaterialArray`, and the segment renderer no longer cross-fall-back between top/edge/wall. An unset role resolves only to the legacy catch-all (back-compat) or the theme default, so painting just the shingle, trim, or soffit stays on that surface. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(editor): alignment guides + drag bounding box across tools Extend the Figma-style 3D alignment guides to the placement and move tools for columns, elevators, roofs, stairs, ceilings, slabs, fences, walls, doors, and windows: each collects alignment anchors from the scene, resolves a snap within the shared threshold, and drives the `useAlignmentGuides` overlay. Wall openings (doors/windows) only snap along their host wall via the new `wall-opening-alignment` helper. Add a shared `DragBoundingBox` overlay (exported from the editor barrel) that renders the dragged object's bounds during a move, wired into the column move tool alongside the alignment snap. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(editor): axis-stable resize-arrow drag plane + slimmer gizmo handles Build the linear resize-arrow's drag plane so it always contains the handle's axis (view direction minus its along-axis component) instead of a plane that merely faces the camera. The old camera-facing normal collapsed when the axis pointed toward the viewer — screen motion barely changed the axis component, so the resize crawled or stopped tracking the cursor. Also slim the extruded arrow/handle geometry (shared by the node arrows, wall side handles, and polygon editor) for a lighter gizmo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(editor): fill-block wall opening highlight + selected frameless openings Draw the selected-wall opening highlight as a translucent block filling the cutout volume (front-side culled) instead of a single vertical pane, so it reads as an occupied slot from any angle — including a top-down floorplan view where an edge-on pane was invisible. Also highlight a directly-selected frameless opening (a `door` with openingKind `'opening'`), which otherwise renders no geometry of its own, and reflect live drag overrides via `useLiveNodeOverrides`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(viewer): double-side slab hole side-walls Build the slab in 3D rather than via ExtrudeGeometry so each hole-wall quad is emitted twice with opposite winding. The slab material is forced to FrontSide (DoubleSide poisons the MRT scene pass), under which ExtrudeGeometry's single-sided hole walls get back-face culled and you see straight through the cut. The doubled quads keep the cut's inner thickness visible from any angle without z-fighting. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(editor): box-select building-scoped nodes like elevators Building-scoped selectable nodes (e.g. elevators) are children of the building, not the active level, so the level walk never reached them. Also walk the level's building children and box-test any registry- selectable kind by its rendered bounds, matching the column/stair/shelf path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(editor): shelf placement alignment + column placement ghost Shelf placement now snaps to Figma-style alignment guides by its footprint edges (layered on grid snap, Alt bypasses), matching the existing 3D move tool. Guides refresh after each drop and clear on teardown. Column placement migrates to the registry `def.tool` path so it can render a translucent column ghost at the cursor (like the shelf build tool) instead of a bare cursor sphere — the editor package can't import the column geometry, so the tool now lives in packages/nodes: - extract `ColumnBody` from the renderer and add a `ColumnPreview` (cloned translucent material, raycast disabled, origin-positioned) - new `column/tool.tsx` registry placement tool with the same footprint-edge alignment as shelf / column move - wire `def.tool` + tool hints; drop the now-unreachable legacy editor-side `ColumnTool` and its dead file Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(editor): floor-plan alignment, pivot moves & placement ghosts Bring the 3D editor's Figma-style alignment experience to the 2D floor plan across every node kind, fix pivot semantics on move, and add 2D placement ghosts. Alignment - Wall anchors now include ±thickness/2 face corners so columns/items/etc. snap flush to wall faces (fixes pillar↔wall); shared by 2D and 3D. - Shared apply-alignment helper (applyFloorplanAlignment / alignFloorplanDraftPoint, with excludeIds) used by move sessions, structural drafting (wall/fence/slab/zone/ceiling/roof), and wall/fence endpoint drags. - Door/window/wall-item moves get along-wall edge-to-edge snapping. - Generic free-translate move path aligns by edges (corner anchors). Pivot moves (2D) - Polygon kinds (slab/ceiling/zone) move by centroid→cursor via a shared polygon-centroid mover; stair moves by origin→cursor; matching 3D. - Shelf/column move targets write position directly (single source of truth) so the 3D group no longer sticks on commit. Placement ghosts (2D) - usePlacementPreview store + FloorplanPlacementPreviewLayer render a kind's def.floorplan footprint following the cursor; wired for column and elevator. Fixes - Elevator placement no longer deselects the active floor plan (preserve levelId through setSelection's hierarchy guard). - Guides clear on every commit/cancel/unmount path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(editor): address architecture review for floor-plan work - Move usePlacementPreview store from core to editor: placement ghosts are an editor/tool concern the read-only viewer never needs. Rewire the column tool (via the @pascal-app/editor public surface) and the editor-internal elevator tool + preview layer (relative imports). - FloorplanPlacementPreviewLayer: read scene lazily in ctx.resolve instead of bulk-reading the nodes map during render. - wiki/architecture/tools.md: refresh the stale useLiveTransforms-per-kind note to reflect item/shelf/column (world-plan) + slab/ceiling/zone (delta). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(nodes): recreate window draft on wall:move when null after placement After a successful click-to-place, the click handler deletes the transient draft and relies on the wall-rebuild → R3F pointer-enter cascade to create a fresh draft for the next placement. If that cascade doesn't fire synchronously (e.g. async geometry rebuild) the next wall:move receives a null draftRef and bails — requiring leave/re-enter to place again. Fix: in onWallMove, when draftRef.current is null but we're hovering a valid wall, recreate the draft immediately (same WindowNode.parse + createNode path as onWallEnter). This is idempotent: if wall:enter does fire first, destroyDraft() in onWallEnter cleans up cleanly. Preserves parity with door multi-place behaviour, matching #367's intent. * fix(nodes): recreate door draft on wall:move when null after placement Mirror of the window fix one commit back: after click-to-place the DoorTool deletes its transient draft and relies on the wall-rebuild \u2192 R3F pointer-enter cascade to spawn a fresh draft for the next placement. When that cascade doesn't fire synchronously, the next wall:move sees a null draftRef and bails \u2014 forcing a leave/re-enter to place again. Recreate the draft in onWallMove when null and over a valid wall on the current level. Idempotent with onWallEnter (destroyDraft cleans up if both fire). --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Pascal <open@pascal.app> |
||
|
|
86db5decb8 |
feat(editor): fold canvas display controls into one Display dropdown (#370)
Replace the seven separate display-control buttons in the canvas toolbar (render, theme, edges, grid, shadows, units, camera) with a single "Display" dropdown, matching the simplification already shipped in the hosted editor. Grid, shadows, camera and units are direct toggles that keep the menu open; render, edges and theme are submenus. Portal DropdownMenuSubContent so the side-opening submenus escape the parent content's overflow-x-hidden clip, and re-export the submenu primitives (Sub/SubTrigger/SubContent/Separator) from @pascal-app/editor. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ee98c55b65 |
Polish: window preset preview + smooth shelf item placement + Shelf build tool (#367)
* fix(nodes,viewer): window preset live wall preview + shaped-cutout move tracking MoveWindowTool marked the moving window `isTransient` unconditionally, but WindowSystem only rebuilds the host wall's cutout for non-transient windows — so a window *preset* (isNew) showed no live hole on the wall and couldn't be placed consecutively without leaving/re-entering. Guard the transient mark on `!isNew`, matching MoveDoorTool. Separately, shaped openings (arch / rounded / `opening`) rebuild their cutout brush from `node.position`, which a same-wall move doesn't write (it mutates the mesh directly and publishes to `useLiveTransforms`). The wall-system's `getEffectiveNode` only merges `useLiveNodeOverrides` (resize arrows), so shaped cutouts lagged the move while rectangular ones (rebuilt from the live mesh matrixWorld) tracked. Fold `useLiveTransforms` into door/window children before collecting cutouts so shaped holes follow the live move too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(editor,viewer,nodes): smooth item placement on shelves Three issues made hosting an item on a shelf janky (item-on-item was already smooth because plain items have no `def.geometry` to rebuild): - Jitter: the draft mesh intercepted the cursor ray, so the shelf-row hit was re-derived from the moving item each frame. Disable raycasting on the draft during placement (incl. async GLB children, reconciled per frame) so the ray passes through to the surface beneath — mirrors MoveRegistryNodeTool. - Reparent/vanish at the edges: `onShelfLeave` flipped state to floor without reparenting the draft off the shelf, so the floor strategy's level-local position rendered compounded with the shelf transform. The grid handler now owns the shelf→floor transition. - In/out oscillation: reparenting the draft onto the shelf dirtied the shelf, and GeometrySystem disposed+rebuilt its boards, making r3f fire a spurious shelf:leave→enter that thrashed placement between the row and the floor. Add an opt-in `def.geometryKey` so GeometrySystem skips the rebuild when geometry inputs are unchanged (shelf boards don't depend on hosted children). Keep the item sticky by testing the cursor ray against the shelf's bounding box: a ray that slips through a gap and lands on the floor behind the shelf still counts as "on the shelf"; only a ray that misses the shelf detaches to the floor. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(editor-app): add Shelf to the standalone editor Build tab The shelf kind is fully wired (def.tool, presentation icon, StructureTool id) but was absent from the standalone editor's Build palette. Add it between Column and Spawn Point. Community has its own build-tab and is left untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(mcp): biome-format coordinate-conventions-demo.json The committed example violated Biome formatting (expanded polygon arrays), which broke the `mcp-ci` Biome check on main (#356) and every branch since. Format it so CI is green. Pure formatting — no content change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
e450d8b474 |
feat(editor): build sidebar + slimmed items panel for standalone editor (#361)
Bring the open-source standalone editor closer to the community editor's v2 sidebar without pulling in any preset/catalog infrastructure. - Add a preset-less Build tab (apps/editor) that mirrors the community Build sidebar: wall, fence, slab, ceiling, roof, stair, elevator, door, window, column, spawn, plus the material-paint panel. Clicking a type activates the raw structure tool drawn with the kind's defaults. - Wire the Build tab into both editor mount points (local + saved scene) and give the left rail proper image icons (Scene/Build/Items/Settings) instead of letter fallbacks. - Gate the ItemsPanel Library/Community/Mine source chips and tag filter rows behind `showSourceFilter` / `showTagFilters` props (default true, so community and external consumers are unchanged). The standalone editor passes both off, leaving plain category tabs + full-width search. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
c00a2469fd |
editor: in-world selection UI across wall / door / window / stair (#334)
* Add roof surface placement support for items Items (e.g. solar panels) can now be placed on sloped roof surfaces. The placement system computes euler rotation from the roof surface normal so items sit flush on the slope instead of going inside. - Add roofStrategy to placement-strategies with enter/move/click/leave - Wire roof:enter/move/click/leave events in the placement coordinator - Add calculateRoofRotation in placement-math using surface normals - Support full 3D cursor rotation for sloped surfaces - Items on roofs are parented to the level with world-space rotation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fixed conflict * wall: 2D floor-plan move, side arrows, drag-to-move endpoints Bundles the in-progress wall editing work on this branch: - Wall corner endpoint drag in 3D (`floating-action-menu.tsx`, `wall/move-endpoint-tool.tsx`): press-and-drag on the floating endpoint button or the new 3D corner sphere, release to commit. Replaces the prior click-to-arm / click-to-place flow. - New 2D move side arrows on selected walls via a new `move-arrow` floor-plan geometry kind (core type + registry-layer renderer + wall floor-plan builder emission), mirroring the 3D `WallMoveSideHandles`. - 2D wall body move: new `wallFloorplanMoveTarget` translates the moving wall and cascades shared endpoints onto linked walls so L-corners stay connected through the drag. - `MoveWallTool` cleanup gains an external-commit guard so a 2D commit doesn't get clobbered by the 3D mover's cleanup restore. - HMR-safe `bootstrap.ts` no longer re-registers builtin kinds whose registry entry survived the closure reset. - Misc 2D polish: floor-plan auto-fit measures the painted scene via `getBBox`, wall dimension offset bumped, swallow-click guard in `handleSelect` so registry-driven selection holds through the post-pointerdown re-render. Floor-plan move-target / move-arrow code still carries diagnostic console logs for the cascade flow; keeping for debug on this branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * wall: 2D floor-plan move adopts 3D junction planner 2D wall drag now produces the same scene topology as 3D — linked corners cascade per `planWallMoveJunctions`, off-axis branches stay rectilinear with a bridge wall inserted between the original and new corner, and same-direction consumed walls collapse and delete. Previously the 2D handler did a naive endpoint-stretch cascade with no bridges or collapses, so dragging an L-corner in 2D vs 3D yielded different scenes. `FloorplanMoveTargetSession` gains an optional `commit` hook. The default overlay path snapshots affected nodes and writes a diff back on release — fine for kinds whose commit is a pure position update, but insufficient when commit needs to also create or delete nodes. When `commit` is present, the overlay reverts to baseline, resumes history, and delegates the atomic write; one Ctrl-Z rolls back the entire operation including bridge creates and collapsed deletes. Shared helpers (`planWallMoveJunctions` plan → updates, linked-wall snapshots, bridge synthesis) lifted to a new `packages/nodes/src/wall/ move-shared.ts` so both the 3D `MoveWallTool` and the 2D `wallFloorplanMoveTarget` import them. Net -163 LoC after dedup. Auto-slab live preview and ghost bridge previews mid-drag — visible in 3D today — remain 3D-only; 2D surfaces them at commit time through the normal scene reactions. Tracked as follow-up. Also drops three `// temp diagnostic` console.log blocks left over from the prior wall-move branch (2D setup, 2D canCommit, 3D cleanup). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * door/window: R flips side, E toggles open/close R previously toggled the open/closed state of operable doors and operable windows. It now flips the opening's side (front ↔ back, rotation += π) for both — same gesture as flipping a furniture item that knows about handedness. The open/close toggle moved to E, which was unbound for doors and windows before. T is now a no-op on doors and windows so it doesn't free-rotate a wall-bound node by π/4 (which made no architectural sense). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * door/window: keep 2D plan in sync during placement drag While drafting a door or window across the same host wall, the tool was bypassing the scene store and mutating the Three.js mesh directly. That kept 3D snappy but left the 2D floor plan reading the last committed position — drafts froze in place on the 2D side during a same-wall drag. Route same-wall moves back through \`updateNode\` so 2D and 3D both re-render from a single source. The reparent path (cross-wall drag) still uses \`updateNode\` with \`parentId\` and \`wallId\` — we only avoid forwarding those fields when the wall hasn't changed so the host wall's \`children\` array doesn't churn each tick and trigger a WebGPU "Vertex buffer slot 0 ... was not set" warning from the briefly re-rendered placeholder geometry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * floor-plan: live wall-draft measurement + opening placement event order Two changes to the floor-plan panel: 1. Length + angle labels render alongside the wall draft in 2D, matching the 3D \`WallTool\` feedback. Length sits at the segment midpoint with a plate that flips when its on-screen orientation would read upside-down; angle arcs anchor at each endpoint that meets an existing wall and label the deviation from that wall's direction. 2. The pointer-move handler ran the registry catch-all (\`isFloorplanGridInteractionActive\`) before the opening-placement branch. Door and window are registered kinds, so during their build mode the catch-all emitted \`grid:move\` and returned — starving the \`wall:enter\` / \`wall:move\` events the placement tools listen for. Reorder so opening placement runs first; the wall-build skip in the catch-all is preserved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * wall: pass geometry as prop on move arrow handles R3F's \`<primitive attach="geometry">\` path emits a \`Draw(0, 1, 0, 0)\` on the first frame because the host \`<mesh>\` briefly renders with the default empty \`BufferGeometry\` before the primitive child attaches. Combined with \`frustumCulled={false}\`, WebGPU flagged "Vertex buffer slot 0 ... was not set" every time a wall or fence was selected and the move arrows mounted. Pass \`arrowGeometry\` as a prop on the \`<mesh>\` so it's never mounted with the default placeholder. Same fix applied to both the wall and fence move-arrow handles. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ifc-converter: regenerate next-env.d.ts after Next route path move Next.js moved the generated routes typings from \`./.next/dev/types/routes.d.ts\` to \`./.next/types/routes.d.ts\` in the current version pinned by the workspace. Regenerated via \`next typegen\` so the project compiles against the right path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * move: track which view finalised so split-view cleanups don't race In split view, both the 2D move overlay and the 3D move tool mount for the same \`movingNode\` and each captures its own pre-drag snapshot. When one side finalises (commit or Esc), the other side unmounts because \`setMovingNode(null)\` propagates — and its effect cleanup had to *guess* whether the live scene was already-committed state (skip restore) or its own drag's uncommitted state (revert). Both cleanups did this via the same heuristic: diff snapshot fields against current scene state. Cheap, but it conflates "the other side committed" with "the user's apply() actually changed something" — and fails outright if a commit happens to land on the same numeric values as the snapshot. Replace the heuristic with an explicit \`movingNodeOrigin\` state field: '2d' | '3d' | null. The finalising side sets its origin before \`setMovingNode(null)\` runs; the other side's cleanup reads it. \`movingNodeOrigin\` is preserved across \`setMovingNode(null)\` (so it's still observable when the cleanup fires) and reset the next time a non-null \`setMovingNode\` starts a fresh drag. Wired on the wall move-tool (3D) and \`FloorplanRegistryMoveOverlay\` (2D) — the two real call sites today. Other 3D move tools can adopt the same flag incrementally as their own split-view races surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * wall: 2D drag publishes to useLiveNodeOverrides, zustand only on commit Side-arrow / corner-dot / curve-handle drags in the 2D floor plan now publish `{ start, end, curveOffset }` to `useLiveNodeOverrides` each tick instead of writing to `useScene`. WallSystem, the 2D registry layer, and the wall sidebar all merge the overrides in when reading endpoints, so the visual + slider preview tracks the cursor while zustand stays at the pre-drag values until pointer-up. Commit writes one tracked `applyNodeChanges` (junction-aware) and clears the overrides; Esc / pointercancel / mid-drag unmount also clear them. Also bundles the in-progress branch work this depends on: - FloorplanAffordanceSession gains optional `commit?()` mirror of the move-target hook; the dispatcher reverts → resumes → calls it when present (vs. its default snapshot-diff dance). - Selected wall body is now pointer-events-inert (polygon `pointerEvents: 'none'` + hit-line skipped) so only the arrows / endpoint dots / curve dot start a drag. - Move button removed from the 2D floating action menu and the wall sidebar inspector for walls — redundant with the side-arrows. - `useWallMoveGhosts` store + `FloorplanWallMoveGhostLayer` for the dashed bridge previews painted mid-drag. - WebGPU "Vertex buffer slot 0 ... was not set" fixes on grid + guide renderer + wall draft preview by passing geometry as a prop (same pattern as wall-move-side-handles). - Floor-plan wall-tool fallback: when the 3D wall tool's `grid:click` already committed the wall, treat `createWallOnCurrentLevel` returning null as "the 3D side handled it" and chain the next draft segment instead of clearing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * wall: new in-world selection UI — side arrows, height handle, corner pickers, ground menu 3D affordances for a selected wall, replacing the HTML floating pill: - side move arrows: thinner chevron+shaft silhouette (extruded, beveled); press-hold-drag-release commits on pointerup (MoveWallTool no longer uses grid:click) - height arrow above the wall midpoint, drags vertically against a camera-facing plane and updates wall.height live; new resizingWallHeight state gates camera orbit; commit plays sfx:item-place - corner picker per endpoint: billboarded hex disc at floor + dashed vertical leader cylinder; pointerdown routes to the existing movingWallEndpoint flow (works for 2D and 3D) - ground action menu (curve / duplicate / delete): three Lucide SVGs rendered as canvas-textured planes lying flat on the floor, anchored one wall thickness + clearance outside the camera-facing face; one rigid container moves them as a unit (auto-flips sides + rotates with the wall, on curved walls uses the t=0.5 curve frame) - floating action menu hidden for walls (replaced by the above) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * wall: smooth ground-menu side-flip with hysteresis + lerp The three floor icons appeared to "move one at a time" when orbiting: binary side decision flickered on grazing orbits, and the 180° rotation flip swapped curve/delete across each other while duplicate (offset 0) stayed put. Now lerps position+rotation toward target with a hysteresis dead-zone, so the menu swings around the wall as one unit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * wall: raise ground-action menu to 10 cm so icons clear floor textures Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * wall: anchor 2D action menu and 3D height arrow at curve apex 2D menu centres on getWallMidpointHandlePoint and stays horizontal 32 px above the wall; 3D height arrow uses getWallCurveFrameAt(0.5) so the apex+tangent match the side handles on curved walls. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * door: in-world selection UI — side width arrows, height arrow, ground menu Side arrows resize width anchored at the opposite edge; top arrow drags height anchored at the floor. Ground menu mirrors the wall pattern with move + duplicate + delete icons that flip to the camera side. Handles portal into the level (not the wall mesh) and wrap in a per-frame transform mirror so wall hover outline doesn't pick them up. New viewer flag handleDragging gates node pointer events during in-world drags; pointerup also swallows the follow-up synthetic click so the PointerMissedHandler doesn't deselect the active item on commit. Wall height arrow, wall move arrow, and fence move arrow all opt in. Scale chevron arrows down to 65 % across wall + door so the family reads as one. Panel type grids (door, window, column, skylight) get matched breathing room (px-3 py-2.5, gap-2) so labels stop hugging the borders. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * door: 2D width arrows + world-relative move dot, deterministic commits Side-arrow width drag in the 2D floor plan: doors now emit two width arrows at the wall-tangent edges when selected, routed through a new `resize-width` affordance that anchors at the opposite edge, clamps to wall bounds, and previews per-tick via scene writes so both the floor plan and the 3D viewer track the drag in real time. `move-arrow` kind gains optional `affordance` + `payload` so the same chevron primitive can route to either the move flow (walls) or an arbitrary affordance (door width-resize) without forking the renderer. Move-dot for the door is now world-anchored — it scales with zoom in place of the previous screen-constant size, matching the rest of the door's chrome. Both `doorWidthAffordance.commit()` and `doorFloorplanMoveTarget.commit()` own their atomic final write so the dispatchers take the deterministic revert → resume → commit path. The diff path was silently reverting when the post-apply state happened to match the snapshot. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * window: in-world selection UI, 2D width arrows, deterministic commits Bring window 3D + 2D selection chrome to parity with door. Selecting a window in 3D now emits two side width arrows, top + bottom height arrows (top anchors at the sill, bottom anchors at the lintel and clamps to the wall floor), and an in-world action menu that rides just below the bottom arrow's tip so the column moves with the sill. 2D plan adds two `resize-width` arrows at the start / end edges, routed through the new `windowWidthAffordance` — same anchored-edge + wall-bounds clamp + per-tick scene-write preview the door uses. `windowFloorplanMoveTarget.commit()` is now self-owned: `apply()` snapshots the last valid placement and `commit()` re-applies it, so the dispatcher takes the deterministic revert → resume → commit path instead of the diff path that silently reverts when the post-apply state happens to match the snapshot. Mirrors the door fix. The HTML floating-action-menu skips windows now that the in-world ground menu owns those actions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * stair: in-world selection UI — side/length/height arrows, ground menu Bring stair-segment selection chrome to parity with wall / door / window. Selecting a stair segment in 3D now emits two side width arrows (each slides the opposite edge anchor under the user), a length arrow at the back face that extends the run, and — for stair-type segments — a height arrow on top. A ground action menu (duplicate / delete) sits beside the segment and flips sides as the camera orbits, with hysteresis + lerp so it doesn't dither. The handles portal into the stair's PARENT object (level / building / scene root) rather than the stair group itself: StairRenderer attaches `useNodeEvents` to the stair group, so any descendant pointer-over would bubble up and set `hoveredId = stairId`, which then makes the post-processing outline traverse the entire stair group and stroke our icons. Mirrors the door fix. A two-layer transform mirror (`stairPoseRef` + `segmentPoseRef`) keeps the handles aligned with the chained per-segment pose that StairSystem writes imperatively each frame. Duplicate forces `attachmentSide: 'front'` on the clone so it continues the chain end cleanly instead of inheriting the original's side and U-turning. New `resizingStairSegment{Width,Length,Height}` editor state lets `CustomCameraControls` suppress orbit/zoom while an arrow is dragging, matching the wall/door/window handle pattern. The HTML floating-action-menu skips stair-segments now that the in-world ground menu owns those actions. Stair-segment panel swaps its bespoke fill-to-floor toggle for the shared `ToggleControl` so it looks like the other panels and groups with the thickness slider under one `space-y-3` block. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * stair: 2D move-revert fix, parent ground menu, curved/spiral in-world arrows - Route stair 2D moves through `floorplanMoveTarget` and honor `movingNodeOrigin === '2d'` in `MoveRoofTool` cleanup so the 3D tool's restore-from-snapshot no longer stomps the 2D commit. - Parent stair selection shows an in-world ground action menu (move / duplicate / delete) anchored beside the stair; the screen-space floating menu is suppressed for `type === 'stair'` to match door / window / segment. - Curved & spiral stairs gain in-world resize arrows: rise (centered on the pillar for spirals), width, inner radius, and two sweep handles (one per arc end) clustered beside the width arrow. - Camera controls pause during curved-stair drags. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * editor: reuse measurement-bar geometry; fix two post-merge dangling refs MeasurementBar was building a fresh BoxGeometry per render for every wall measurement bar, which the WebGPU backend flagged ("Vertex buffer slot N ... was not set") when walls moved. Hoist a unit cube and scale it instead. Two refs left dangling after the main-branch merge resolved its conflicts on GitHub: - floorplan-panel.tsx referenced a `theme` variable that no longer exists; the file already derives `isDark` from `getSceneTheme(state. sceneTheme).appearance === 'dark'` higher up. Use that. - grid.tsx applied `EDITOR_LAYER` but only imports `GRID_LAYER` (the new dedicated grid layer). Use the imported one. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * editor: pin building bbox center to cursor during move The floating drag button anchors at the building's bbox center, but the move tool was teleporting the building's origin to the cursor — so the moment a drag started the building jumped by `bbox_center - origin`. Capture the local-space offset from origin to bbox center at mount and apply it on every grid move, grid click, and R/T rotation, so the bbox center stays pinned to the cursor through the whole drag. Also seed the cursor sphere at the bbox center instead of the origin. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * stair: 2D resize affordances + dispose curved/spiral geometry on swap Floorplan view: emit `move-arrow` children alongside the existing chrome, mirroring the in-world arrows on selected stairs: - straight: per-segment side (left/right width) and front (length) - curved & spiral: width, inner-radius, and two sweep-end arrows Hidden during placement so they don't fight the cursor follow. Stroke widths on curved/spiral chrome converted to screen pixels (paired with `non-scaling-stroke`); the old world-metre values rendered as sub-pixel at every zoom. First step line is now also emphasised on curved stairs to match legacy chrome. Skip the straight-only direction-arrow polyline for curved/spiral — the arc-aligned arrow above already conveys "up" and `buildFloorplanStairArrow` produces a malformed polyline once the chain is wrapped around an arc. Renderer: extract `SpiralColumnMesh` and `SpiralStepSupportMesh` and add the same prop-+-dispose pattern used by `CurvedStepMesh` / guide/renderer.tsx. Without disposing the prior BufferGeometry on each resize tick, WebGPU keeps a stale pipeline reference and flags "Vertex buffer slot 0 ... was not set" mid-drag on Lambert. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * editor: revert in-world ground menus to HTML floating; align grid to building - Wall/door/window/stair/stair-segment selection menus return to the shared HTML floating menu; remove the in-world ground icons, SVG textures, hysteresis/lerp constants, and unused imports across wall/door/window/stair-segment handle files. - Drop Move from the floating menu (the in-world side arrows cover it); delete the now-unused handleMove. - Floating menu scales with camera zoom (ortho.zoom or 1/distance), clamped at MIN 0.5 / MAX 1 so zoom-in keeps the default pixel size and zoom-out shrinks to a readable floor. - Per-type y-offsets tuned: wall 0.5, opening 0.6, landing 0.5, flight 0.75, parent stair 0.2, structural 0.4, default 0.05. - Align wall/fence arrow materials with the door/window pattern (depthTest/depthWrite false, transparent: true) so they render on top of geometry consistently. - Grid cellSize now follows `gridSnapStep` via a small `SnapAwareGrid` wrapper, and the grid mesh anchors its world XZ to the active building's mesh — snapped wall endpoints (in building-local coords) now fall on visible grid lines instead of mid-cell. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * handles: registry-driven in-world resize arrows Adds a `handles?: HandleDescriptor[] | (node) => HandleDescriptor[]` field to `NodeDefinition` so each kind declares its in-world resize affordances as pure data instead of shipping a bespoke React component. - New `packages/core/src/registry/handles.ts` exposes a discriminated union: `linear-resize` (axis + center/min/max anchor), `radial-resize` (1:1 outward growth), plus stubs for `arc-resize` and `endpoint-move` for follow-up migrations. - New `packages/editor/src/components/editor/node-arrow-handles.tsx` reads `def.handles`, mounts arrows with shared drag plumbing (raycast plane, NDC, pointer listeners, SFX, history pause, handle-dragging guard). Portal modes: `'parent'` (column-like, single wrapper rides self pose) and `'grandparent'` (door/window-like, outer wrapper rides parent pose + inner group rides self pose so handles escape the parent's selection-outline traversal). `apply` receives the node-at-drag-start so edge-anchored resizes (door width re-centers position) compute their fixed anchor from pre-drag state. - Migrate column, door, window, stair-segment. Old per-kind handle files (`column-side-handles.tsx`, `door-side-handles.tsx`, `window-side-handles.tsx`) removed; `stair-segment-handles.tsx` retains `StairHandles` (parent stair curved/spiral arrows) pending the `arc-resize` migration. - Column: height + crossSection-aware footprint (radius / uniform width=depth / independent width+depth / brace width+depth for non-vertical supports). - Door / window: edge-anchored width (left + right) with wall-length max bound; bottom-anchored height (door) / top + bottom edges (window). - Stair-segment: width (chain auto-centers), length anchored at chain start, height for step flights only (landings skip it). Wall and parent-stair curved/spiral arrows stay on legacy components for now — they need `endpoint-move` + `arc-resize` descriptor variants and rotated-axis projection, which are their own focused sessions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * handles: migrate wall + parent stair to registry; add arc-resize Closes the wall + parent-stair gap on the registry-driven handle migration. Net −1177 lines (the per-kind handle files were 1500+ lines of duplicated drag plumbing; their replacements are ~50-line config blocks on each NodeDefinition). - `arc-resize` reworked to take a raw `delta` (radians) instead of `newValue` so two-field writes like curved-stair sweep (which updates `sweepAngle` AND `rotation` together to keep the non-dragged edge world-fixed) stay in the descriptor without awkward inverse-currentValue gymnastics. `currentValue` removed from arc-resize for the same reason — applies own their math. - New `ArcArrow` renderer in `node-arrow-handles.tsx`: raycasts a horizontal drag plane at the arrow's Y, measures the signed angle delta around the node's local origin (atan2 in world XZ, normalised to [-π, π] so wraparound doesn't flip mid-gesture), hands the delta to `descriptor.apply` along with the initial node. - Wall: height arrow migrated (linear-resize axis='y' anchor='min', placement uses curve apex for curved walls, chord midpoint for straight). Side-move arrows + corner pickers stay on the legacy `wall-move-side-handles.tsx` because they're tap-to-engage-mode affordances (move whole wall / move endpoint), not drag-resize — modelling them in the registry needs an editor-action descriptor variant which is a follow-up. - Parent stair: curved + spiral stairs declare 5 handles — rise (linear-resize axis='y' anchor='min'), width (linear-resize axis='x' anchor='min'), inner-radius (linear-resize that also writes width to keep outer rim fixed), and sweep start / end (arc-resize variants writing sweepAngle + rotation). Straight stairs declare nothing — their segment children own resize. - Old `stair-segment-handles.tsx` (1405 lines) deleted; all its arrows now flow through the registry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * handles: tap-action descriptor + EditorApi; finish wall + fence migration Closes the final gap in the registry-driven handle migration. Wall side- move + corner pickers + fence side-move were the last legacy handles because they're click-to-engage-mode affordances (hand the node to its move tool / start an endpoint drag), not drag-resize — `apply(node, value, sceneApi)` had no path to editor state. - New `EditorApi` interface in core (alongside `SceneApi`) exposes `engageMove(node)` + `engageEndpointMove(node, endpoint)`. Concrete implementation in `packages/editor/src/lib/editor-api.ts` casts through `useEditor`'s setters so the descriptor layer never imports editor internals. - New `TapActionHandle` descriptor variant: `placement` + `onActivate (node, sceneApi, editorApi)`. `shape` field picks the visual — defaults to the chevron arrow; `'corner-picker'` renders the dashed vertical leader + billboarded hex disc + ring (sized to `nodeHeight(node)`). - `TapActionArrow` renderer in `node-arrow-handles.tsx` wires up pointer-down → descriptor.onActivate. Pulled the chevron and corner visuals into `ArrowShape` / `CornerPickerShape` building blocks so future shapes can be added without touching the descriptor union. - Wall: front/back side-move (engageMove) + start/end corner pickers (engageEndpointMove). Joined by the existing height arrow on the same `def.handles` list. Old `wall-move-side-handles.tsx` (600 lines) deleted — wall now has zero per-kind handle component. - Fence: front/back side-move. The bespoke endpoint move buttons in the floating menu stay until they migrate to a tap-action too. Net for this commit: -620 +513. Combined with the prior two migration commits: -2287 +912 across the full registry migration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * wall: revert side-move arrows + corner pickers to legacy component The registry-driven tap-action path didn't render the four non-height wall handles, even with descriptors resolved and the wall mesh in sceneRegistry. Fence uses the same descriptor shape and renders fine, so the bug is wall-specific and not in the descriptor layer itself — left for a real diagnosis later. Restored the pre-5756f241 wall-move-side-handles.tsx (height arrow + front/back side-move + start/end corner leaders, 753 lines) and mounted it next to NodeArrowHandles in editor/index.tsx. Dropped the def.handles field on wallDefinition so the two paths don't race. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * editor: level naming helper + ambient floorplan render during building moves Two unrelated WIP fixes bundled: - Level names: extract \`getDefaultLevelName(n)\` / \`getLevelDisplayName(level)\` into \`packages/editor/src/lib/level-name.ts\` and swap in across rename inputs, command palette, floating selector, site panel, level-tree node, level-duplicate dialog, view toggles, and viewer-overlay breadcrumb. Default labels now read "Ground Floor" / "Floor N" / "Basement N" instead of the bare "Level N" string each caller was concatenating itself. - Building-move ambient floorplan: when a building is selected (or mid-move) without an explicit level, FloorplanRegistryLayer falls back to that building's level 0 (or lowest level) and renders it dimmed + non-interactive so the floor stays visible as context instead of disappearing. FloorplanPanel allows the SVG to mount in that case. MoveBuildingTool publishes per-frame pose to useLiveTransforms so the floor-plan follows the drag without reading from the Three.js mesh. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * handles: 3D resize arrows publish to useLiveNodeOverrides, commit on release Door / window / wall height-arrow drags now stage the patch in \`useLiveNodeOverrides\` each frame and write to zustand exactly once on pointerup. The kind's system reads via \`getEffectiveNode\` and rebuilds the mesh imperatively, so the React tree never re-renders mid-drag and undo isn't polluted by per-frame writes. - \`packages/core\`: shared \`getEffectiveNode<T>(node)\` helper exported from \`@pascal-app/core\`; spreads any override fields onto the input, returns it unchanged when none. Replaces the inline merge wall-system had as \`getEffectiveWall\`. - \`DoorSystem\` / \`WindowSystem\`: subscribe to \`useLiveNodeOverrides.overrides\` (so override-only ticks re-run the component and pick up the latest dirtyNodes), merge via \`getEffectiveNode\` before \`updateXMesh\`. Parent-wall dirty cascade uses the effective node's parentId. - \`WallSystem.updateWallGeometry\`: door / window children are merged through \`getEffectiveNode\` before being passed to \`generateExtrudedWall\`, so cutouts track the in-flight resize. - \`LinearArrow\` (registry handle): onMove → override + markDirty; onUp → one tracked \`sceneApi.update(lastPatch)\` + clear; onCancel → clear + markDirty to revert geometry. - Legacy \`WallHeightArrowHandle\` in wall-move-side-handles.tsx switched to the same pattern (was the only inline-drag handle in that file — side-move + corner pickers hand off to other tools). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * floating menu: enable Move icon for wall / door / window Widens the \`onMove\` gate on \`NodeActionMenu\` so wall, door, and window join column in showing the Move chevron. \`handleMove\` calls \`setMovingNode(node)\` which dispatches through the existing \`affordanceTools.move\` path on each kind's definition (already present for all three). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * handles: live drag, guide rings, dimension chips; floating-menu Move on stairs - Window bottom height arrow: flip Z rotation so chevron points down when placement Y < 0. Door / column height arrows unaffected (still above the node). - Floating menu: raise stair-segment offsets (segmentType is 'stair' | 'landing', so the legacy 'stair-flight' key was dead); enable the Move icon for parent stair and stair-segment. - HandleDecoration on LinearResizeHandle + RadialResizeHandle. Generic GuideRing renders at node-local (0, y, 0) in the XZ plane when the arrow is hovered or dragging. Curved/spiral stair width arrow gets an outer rim ring, inner-radius arrow gets an inner pillar ring, and column radius arrow gets a footprint ring on round / octagonal / sixteen-sided shafts. - ArcArrow migrated to the live-override pattern (sweepAngle + rotation). NodeArrowHandles subscribes to useLiveNodeOverrides for the selected node and merges into the effective node, so arrow positions, decorations, and dimension chips all track the in-flight drag instead of freezing at pre-drag values. - StairRenderer and ColumnRenderer subscribe narrowly to their own override entry and render against the merged effective node, so the curved/spiral mesh and the column body update per pointer move without zustand churn. - DimensionLabel chip (<Html>) rendered next to every linear-resize / radial-resize arrow on hover or drag. Format follows the wall / fence label recipe (metric / imperial via useViewer.unit). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fence + column: registry handles, brace spread arrows, per-style defaults - Fence: side-move arrows already on the registry path; add the height arrow (axis 'y' linear-resize, anchor min) + start/end corner pickers (tap-action, shape 'corner-picker' with dashed leader + billboarded hex). Move icon enabled on the floating menu; the menu floats above the height arrow via a fence-specific MENU_Y_OFFSET. Endpoint move buttons + Alt-detach plumbing removed from the floating menu — corner pickers cover that flow. Legacy wall-move-side-handles.tsx no longer branches into fence (dedupes the side-move arrows that were stacked). - Column: bottom + top spread arrows for non-vertical supports — anchor 'center' so dragging the right leg outward grows the full leg-to-leg span symmetrically. Conditionally added per supportStyle: - a-frame: both bottom and top spreads - y-frame / v-frame: top spread only Per-style preset map applied on supportStyle switch (panel.tsx) so every style snaps to its renderer's natural proportions (defaults lifted from each support's fall-through expressions); a customised A-frame switched to Y-frame no longer carries its 1.4 m bottom into state, and an X-brace gets equal parallel legs rather than inheriting A-frame's pinched 0.12 m top. - GeometrySystem: merge `getEffectiveNode(node)` before calling `def.geometry`. Smooths drags for every kind on the parametric path (fence, shelf, item, anything that ships `def.geometry`): live override mutates the mesh per pointer move, zustand only hears the commit. Mirrors WallSystem / DoorSystem / WindowSystem / StairRenderer / ColumnRenderer hookups. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * elevator + column: registry handles, rotation gizmo with curved arrow - Elevator: width / depth / cab-height arrows on the registry path (anchor='center' for width/depth so dragging outward grows the full span symmetrically; anchor='min' for cab-height with shaftTopY resolved through `resolveElevatorLevels` so the arrow lands above the full shaft on multi-level elevators, not just the cab top). Floating-menu Move icon enabled + a fence-style MENU_Y_OFFSET so the menu floats above the height arrow. - Whole-node rotation gizmo for both elevator and column. Uses arc-resize with `shape: 'rotate'` + a new `decoration` ring on ArcResizeHandle. Curved-arrow geometry is a two-headed icon (arc ribbon with chevron wings + tangential tip at each end), rendered in node-local XZ plane at mid-height. Guide ring traces the rotation circle (footprint-diagonal + small offset) on hover or drag. Position offsets along +Z only — sticks out the front of the node instead of diagonally at the corner. apply() negates the cursor angular delta (atan2(z,x) is opposite-handed from three.js Ry) so dragging CCW around the node rotates the node CCW. - ArcArrow renderer extended: tracks `isDragging` like LinearArrow, renders the optional ring decoration, and swaps geometry between the chevron (default, used by stair-sweep handles) and the new curved-arrow shape when `shape: 'rotate'` is set. - HandlePlacement.position / .rotationY now optionally take a `sceneApi` so descriptors that depend on cross-node state (the elevator's level-chain resolution) can compute placement against the live scene. SceneApi gains a `nodes()` accessor returning the full record. Test stubs in core (relations-resolver, drag-session, hosting) updated to satisfy the new shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * slab + ceiling + shelf: registry handles, live polygon preview, cursor polish - slab: per-edge resize chevrons in PolygonEditor (gated on `allowEdgeMove`, so site / zone editors are unaffected), height arrow via `def.handles`, and a floating-menu Move icon. Polygon drags now publish the in-flight polygon to `useLiveNodeOverrides` through a new `onPolygonPreview` prop; GeometrySystem rebuilds the slab mesh at pointer rate while the store stays untouched until the single commit on release. Hole editor wired the same way. Handle materials switched meshStandard → meshBasic so the blue corner / green midpoint cylinders read true colour instead of dimming in scene lighting. - ceiling: same Move icon, per-edge arrows, height arrow, and live preview through the boundary + hole editors. CeilingSystem now merges via `getEffectiveNode`, so polygon and height overrides flow through on every dirty tick. Height arrow placement is mesh-local (not `height + offset`) because CeilingSystem parks `mesh.position.y` on the height value. - shelf: width / depth / height arrows + a curved rotation gizmo with ring decoration. Move icon on the floating menu. Shelf stores rotation as a tuple, so the rotate `apply` reads back `[x, y, z]` and only mutates `y`. - LinearArrow: snapshot `rideObject.matrixWorld.invert()` at drag-start and reuse it in `onMove`. Kinds that park `mesh.position` on the field being dragged (ceiling `height`) used to chase a moving ride frame, so the local-Y delta collapsed and the value stalled / jittered. - ArcArrow: cursor is `'grab'` on hover and `'grabbing'` during the drag (was the misleading `'ew-resize'`); the `Cursor` type gains those two members. - ParametricNodeRenderer: merge `useLiveNodeOverrides` for position + rotation so the rotation gizmo shows live motion through the outer group — GeometrySystem already covered geometry-affecting fields. - floating menu offsets: slab 0.4 → 0.7, ceiling 0.4 → 1.0, shelf 0.6, so the menu floats above each kind's new height arrow. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * roof-segment: registry handles, live override path, analytical-normal fix - roof-segment: width / depth / wall-height / pitch / rotation arrows; pitch drag back-solves the angle from peak-height via the slope frame - roof-system: getEffectiveNode + useLiveNodeOverrides so drags rebuild the segment + merged shell live, commit-on-release stays a single write - floating menu: Move icon for roof-segment; uniform EXTRA_MENU_LIFT - skylight / solar-panel / box-vent ghost: fix analytical normal — shed sign flip, mansard / dutch +X face direction, gambrel + mansard tier awareness; one (dx·tan, 1, dz·tan) formula across all roof types Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * floorplan: resize/rotate arrows for column, shelf, elevator, fence; rotation handedness fix - Column / shelf / elevator: per-cross-section resize arrows in the 2D floor plan, matching the 3D handle set (width / depth / uniform / radius / brace dims), plus a corner rotate-arrow. Body move stays on the move-handle dot via the registry overlay's generic translate. - Fence: floor-plan curve sagitta handle + side move-arrows + a body-move target (`fenceFloorplanMoveTarget`) with linked-fence endpoint cascade and ALT-detach. Commit strips `isNew` metadata and re-selects so the chrome stays visible at the new position. - Roof-segment: floor-plan resize + rotate arrows wired through new affordances; `resolveSegmentFrame` aligns with the builder's transform so handles stay glued to the rendered footprint. - Stair: in-world rotate gizmo bow orientation derived from the gizmo's position (was a stray `-π/4` that read as "pointing outward" on the spiral). StairSystem now merges the live override before the slab-elevation spatial query, so dragging the rotate gizmo no longer drops the group's Y when a segment swings off its pre-drag footprint. - Rotation handedness: floor-plan now plots column / shelf / roof-segment at `-rotation` so SVG's CW-with-y-down `rotate` visually matches Three.js Y-rotation (CCW from top-down). Same `rotation` value rotates the same direction in both views, and the same cursor gesture writes the same sign — `- delta` in every rotate affordance, lined up with the 3D handles. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * registry: floorplanScope + movable capability; cursor focus under mouse - Registry: duplicate-kind throws in production, warns in dev (HMR). New `kindsWithFloorplanScope('building')` and `isRegistryMovable` helpers; `resolveBuildingForLevel` extracted into spatial-grid-sync. - Floorplan registry layer: building-scoped kinds (elevator today) now dispatched via `def.floorplanScope === 'building'` instead of a hardcoded `node.type === 'elevator'` arm. - FloatingActionMenu: Move button gated by `isRegistryMovable(kind)`, replacing the 13-arm `node?.type === '…'` chain so adding a movable kind no longer touches this file. - 2D cursor indicator: render at the raw mouse position in all modes (drop the snapped `cursorAnchorPosition` machinery) so the badge always sits under the cursor. - 3D grid reveal ring: the shader's `positionLocal.xy` is in grid-mesh-local space, but the cursor uniform was in world coords — so the ring drifted by the building's world XZ. Store the last world cursor and re-derive the local uniform every frame after the mesh's XZ lerp, so the ring stays locked under the mouse including during the catch-up frames after a building rotation commits. - Roof system: tighten the merged-shell filter's type predicate so TS narrows `n` before `hasSegmentMaterialOverride(n)`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * wall: corner billboard, perpendicular grid snap, drop 45° from move/draft - Corner picker discs (3D move + wall corner leader) now solve `parentWorld⁻¹ · cameraWorld` so they face the camera even when an ancestor building/level has a rotation; the old `camera.quaternion` copy silently broke under any parent rotation. - Side-handle wall move snaps the wall centre's *absolute* perpendicular projection to grid lines, so axis-aligned walls land on real grid positions regardless of where they started. - Wall draft + endpoint move (3D and 2D) drop the 45°-from-start angle snap. It was useful for picking a direction during the very first draft, but during a perpendicular endpoint drag it pulls the cursor onto a 45° ray from the fixed corner instead of tracking the grid. - Shift now selects the fine grid step (`WALL_FINE_GRID_STEP = 0.05`) for precision placement in every wall snap call site, replacing the former "Shift = bypass angle snap" semantics with a consistent "Shift = finer snap" convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fence: drop 45° angle snap from draft + endpoint move, Shift = fine step Mirrors the wall convention shipped in e89a8228: - `snapFenceDraftPoint` gains an optional `step` override. - Fence draft (3D `tool.tsx` and 2D `floorplan-panel.tsx`, `use-floorplan-background-placement.ts`) snaps to the active grid step only — no 45°-from-start snap. Shift switches to `WALL_FINE_GRID_STEP` for precision placement. - Fence endpoint move (3D `actions/move-endpoint.ts` and 2D `floorplan-affordances.ts`) drops `start`/`angleSnap` so a perpendicular drag tracks the grid instead of pulling onto a 45° ray from the fixed endpoint. Shift switches to the fine step. Also fixes the matching wall click path in `use-floorplan-background-placement.ts:215` that was missed in e89a8228, plus its locally-injected `snapWallDraftPoint` signature. Side-handle perpendicular slide (`fence/move-tool.tsx`) was already grid-snap-only without 45°, so it's untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * floorplan menu: show Move button for selected walls Removes the `node.type !== 'wall'` exclusion that hid the Move icon on the 2D floor-plan floating menu for walls. The dispatcher already has a working path for walls — `def.affordanceTools.move` routes to `MoveWallTool` (perpendicular slide + linked-wall cascade) — so the menu just needs to expose the button. The original opt-out called the menu entry "redundant" because walls also have side-arrow handles, but the user wants the same icon walls get the same affordance as every other selected element in the floating menu. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * floorplan wall move: axis-lock to wall normal, match 3D MoveWallTool The 2D `wallFloorplanMoveTarget` was applying the raw cursor delta in XZ, so dragging a selected wall in the floor plan let it free-float sideways and lengthwise. The 3D `MoveWallTool` constrains the same drag to the wall's perpendicular axis (sideways slide only) — this brings the 2D path into parity. - Captures the wall's centre and the `getPerpendicularWallMoveAxis` normal at session start. - Each tick, projects `originalCentre + rawDelta` onto the axis, snaps that absolute scalar to the active grid step, and translates the wall by `axis * perpDelta`. Same math as the 3D tool. - Shift bypasses snap (raw projection), matching the 3D convention. - Degenerate zero-length walls fall back to free XZ motion (rare; they're already destined for deletion via the junction planner). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * floorplan move overlay: commit at last pointermove, not pointer-up The overlay used to re-run \`session.apply\` with the pointer-up coordinates before committing, on the assumption that pointer-up might fire without a preceding pointermove. Side effect: when the pointer-up coord crossed a grid-snap boundary relative to the last pointermove, the snap flipped to a different cell and the moved node visibly jumped at release from where the drag had painted it. Trust the last pointermove instead — modern browsers reliably emit a final pointermove right before pointerup, and "what you saw is what gets committed" is the UX users expect. The previous sub-pixel drift fix loses to the visible boundary-jump it caused. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * floorplan: door wall-hit placement, fence/stair move fixes, wall auto-ceiling sync - Door / window placement: registry layer entries no longer swallow pointer events while a door / window tool is active, so clicking ON a wall now triggers placement (previously only clicks NEAR a wall worked — the wall's registry-entry `<g>` was stopping the pointer event before it reached the SVG background handler that emits `wall:click`). - Fence floor-plan move: 3D `MoveFenceTool` now respects `movingNodeOrigin === '2d'` on unmount. Without the guard, the 2D overlay's commit would call `setMovingNode(null)`, unmounting the 3D tool, whose cleanup then ran `restoreOriginal()` and reverted the just-committed positions — the "fence reverts on commit" symptom. Mirrors the wall move-tool's existing guard. - Stair floor-plan move: anchored, delta-based motion (was position-jumps-to-snapped-cursor), and reads `getWallGridStep()` instead of hard-coded 0.5 so the stair snaps to the editor's current grid step in real time. Matches the 3D `MoveRegistryNode` commit position. - Stair segment length arrow: drop the placement `rotationY` — `axis: 'z'` already auto-rotates the chevron by `-π/2`, stacking another `-π/2` spun the tip to `-X` (sideways) instead of `+Z` (forward off the run). Matches shelf / roof-segment. - Stair segment system: merge `useLiveNodeOverrides` when rebuilding geometry, chain transforms, merged mesh, and slab elevation, so width / length / height drags show the live value on the mesh and the store only gets the final tracked write on commit. - Stair length arrow position: offset 0.06 m past the front edge so the head clears the stair fill and reads as pointing forward off the run rather than lying across the edge. - Stair default railing mode: `'both'` for new placements (was `'right'`). - Wall floor-plan move: anchored at first cursor sample (was raw centre) so the floating-menu drag-icon offset doesn't jump the wall to a different snap cell on grab. - Wall move: live auto-slab + auto-ceiling preview via `useLiveNodeOverrides`. The store stays at pre-drag values during the drag; commit writes the final plan in one atomic `applyNodeChanges` (creates / updates / deletes deferred from per-tick to commit so undo rolls the whole topology change back in one step). Adds `planAutoCeilingsForLevel` + `AutoCeilingSyncPlan` exports mirroring the existing auto-slab planner. - Wall draft: expose `WALL_ENDPOINT_SNAP_RADIUS` (0.7 m) for endpoint snap intent — strongest user intent (closing polygons, attaching to corners) wants a more generous radius than the generic join snap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * open-pr skill: refresh existing PR description instead of bailing Adds a 3b branch to the open-pr skill: when gh pr view finds an existing PR, regenerate the body from current branch commits/diff while preserving Screenshots verbatim and the user's checklist tick state, then apply via gh pr edit. Previously the skill would print the URL and exit, leaving stale descriptions on long-lived feature branches. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * floorplan: capability/hook dispatch, generic resize + endpoint state Removes per-kind `node.type ===` arms from the floorplan layer (door/window opening placement, wall live-override merge, building ambient context) in favour of new NodeDefinition capabilities and hooks. Collapses 12 `resizing*` editor-store fields into one `activeHandleDrag`, the wall/fence endpoint-move dispatch into a kind-keyed table, and renames now-shared wall utilities to segment-generic names. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 3d wall measurement: hide label for selected walls Item measurements still render; the wall branch is left in place so re-enabling is a one-line gate flip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * editor: set EDITOR_LAYER on arrow handles to exclude from thumbnails Arrow meshes portaled into the 3D scene were missing EDITOR_LAYER, so ThumbnailGenerator's camera (which calls cam.layers.disable(EDITOR_LAYER)) would render selection handles into captures. Add a useEffect in NodeArrowHandlesForNode that traverses the portal root group and sets EDITOR_LAYER on every child. The effect re-runs whenever descriptors change so newly created meshes (e.g. when handle count changes for the same selected node) get the layer tag immediately. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: open-pascal <open@pascal.app> |
||
|
|
f5ff111708 |
fix(ifc-converter): broken Contributions GitHub URL (#335)
apps/ifc-converter/app/page.tsx pointed at github.com/pascalorg/editor/apps/ifc-converter which 404s. The canonical path includes /tree/main/. Same URL is reused by the forthcoming community /ifc page (in pascalorg/private-editor) so keeping them consistent matters. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |