77442861d9a7e44de64bb1e5fa4c690d8a3865b5
29
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bf89b5bcf2 |
feat: vertical building model — stored level heights, wall inversion, decks (#526)
* feat(core): stored storey heights groundwork — pure slab-support module + level height schema Extract pointInPolygon/computeWallSlabSupport and friends into a cycle-free packages/core/src/systems/slab/slab-support.ts (severs level-height -> spatial-grid-manager -> use-scene), add deriveLegacyLevelHeight as the pure mesh-free equivalent of the viewer's stacked level height, and add the optional LevelNode.height field plus the storey service (getStoredLevelHeight, getLevelElevations per-building prefix sums). No behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): storey height badge + edit popover on level rows Each floating-level-selector row shows its storey height; clicking opens a popover with 2.5/3.0/3.5 presets and a free slider writing level.height. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(core): vertical-model load migration — stored heights, ordinal compaction, wall-top classification Pass 3 in migrateNodes: derive and store each legacy level's exact stacked height (never snapped), compact ordinals per building anchored at zero so basements stay basements, classify wall tops against the derived plane (|plane - top| < 0.20 strictly -> plane-bound, else explicit height materialized), and drop the blind totalRise 2.5 stair default on legacy scenes only. Epsilon and strictness validated by a prod census. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: pin wall tops to the storey plane; stored heights become the only vertical truth Wall-top inversion: a wall without an explicit height now tops out at its storey plane (resolveWallTop); slabs lift only the base. Window/door caps resolve the real top through the same slab election instead of Infinity. All level stacking (viewer, elevator, first-person, stair openings, MCP scene queries) reads stored LevelNode.height; the four divergent live derivations and level.metadata.height are deleted. Stair totalRise becomes optional and derives from the storey height when absent. MCP create-level stops writing its elevation param into the ordinal. Level creation sites write explicit heights; templates carry their true derived heights. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: clamp slabs/ceilings under the storey plane; sweep wall-height fallbacks; wall Top control Slab elevation writes clamp to plane − MIN_WALL_HEIGHT when plane-bound walls elect the slab (pure clampSlabElevationForWalls + registry handle bounds + shared panels for 2D/3D parity); ceiling heights clamp under the plane and auto-ceilings derive from resolved wall tops. Every remaining wall.height ?? 2.5 fallback resolves through resolveWallTop / resolveWallEffectiveHeight (panels, overlays, measurements, quantities, spatial grid, MCP reports); template walls matching their storey become plane-bound. Wall panel gains a Top control (Follows storey / Custom height) derived purely from height presence; the store update path now deletes keys passed as explicit undefined so plane-binding round-trips. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(core): persisted support hosts — schema, host-preferring election, rendered-polygon unification Floor-placed nodes and walls gain a nullable supportSlabId. Elections surface the winning slab (getSlabSupportForItem, candidates query) and prefer a still-valid persisted host, falling back silently when the host is gone or reshaped away; deleting the host strips references in the same undo commit. Item-side support now tests the rendered slab polygon (like walls) through a per-level cache invalidated by the spatial-grid sync. Also adds the resolveStairTotalRise unit tests from the stage-1 gates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: persist support hosts at commit; thread wall host preference everywhere Floor-placed commits (draft pipeline, per-kind creation tools, registry move tool) and wall create/move/endpoint commits persist supportSlabId via shared resolveSupportSlabPatch helpers — only when overlapping supports disagree on elevation, clearing it otherwise or when the node leaves the floor. WallSlabSupport surfaces electedSlabId; every wall support read site passes wall.supportSlabId as the preferred host. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: split slab into placement + thickness; pools become explicit recess intent slab.elevation stays the walking surface; new thickness grows downward so the solid occupies [elevation − thickness, elevation]. Migration writes thickness := elevation for solids (byte-identical intervals, including degenerate zero) and recessed: true for legacy negative pools. Geometry branches on recessed instead of the elevation sign; presets keep today's intervals; free elevation edits move the body without coupling thickness (the deck semantic). Dead viewer SlabSystem component deleted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: clamp ceilings to covering-slab undersides across levels getLevelAbove + getCoveringSlabUndersideAt give the first cross-level query; ceiling writes clamp to min(storey plane, lowest covering underside) − 0.01, and the space-detection reconcile now clamps manual ceilings down (never up) when a deck above intrudes — a flush deck reactively lowers the ceiling below it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: mezzanine and balcony build-tab tools One-gesture composites over the kernel: draw a deck footprint and commit deck slab + railings + stair (mezzanine) or deck + railings (balcony) in a single undo step. Fences gain supportSlabId and lift onto their host deck; railing runs split around the stair mouth; edges near wall centerlines are treated as closed. Stairs target the deck via explicit totalRise with no level-to-level opening sync. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: adaptive slab vertical editing; level vocabulary in UI copy Dragging a grounded slab's top stretches it (elevation and thickness move together — gaps impossible); floating decks move with thickness preserved and land grounded at zero; pools keep the drag-through-zero gesture. User-facing copy says level, not storey (Follows level, Level height). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: clamp plane-bound wall tops to covering-slab undersides; fix vertical reactivity getWallPlaneTop samples the wall span against the level above's slabs, so a thick or flush upper floor shortens the walls below it instead of colliding (automatic attach, no dialog). Level-height edits now dirty the level's walls, stairs, ceilings, and fences; covering-slab changes dirty the level below. The effective-height helper triplicated across editor overlays moved to core. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: ceilings follow the level top by default ceiling.height becomes optional — absent means the ceiling tracks min(level top, covering slab underside) − 0.01 live, so level-height edits no longer require ceiling fixes. Ceiling panel gains the same Follows level / Custom height control as walls; auto-from-walls ceilings are created height-less and their height-derivation machinery is deleted; migration drops stored heights within 0.20 of the bound on legacy scenes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: wall plane clamp missed max-side boundary walls Auto slabs derive their polygon from wall centerlines, so covering-clamp samples sat exactly on the boundary where ray-cast point-in-polygon is side-dependent (min edges in, max edges out) — walls clamped or not by orientation. getWallPlaneTop now clips the wall's thickness band against the covering polygon (boundary-inclusive, arc-aware) and the ceiling bound's point sampling gained an explicit on-boundary test. Verified against the reported repro scene. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: stairs attach to decks; pointer-decided placement surface Stairs gain deckSlabId — rise follows the deck's elevation live (straight flights re-converge via a write-sync mirroring auto-openings), the panel shows a unified To destination with Follows deck / Custom rise, and the mezzanine tool attaches instead of baking a stale rise. Item placement under an elevated deck no longer flickers: grid events fed a feedback loop (the grid plane rode the ghost's elected height), so the support election is now capped at the surface the pointer ray actually hits, with a ground sentinel keeping under-deck commits deterministic. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: remove the mezzanine/balcony composite tools Decks ship as catalog presets instead; the kernel the presets rely on (fence deck-hosting, stair deck attachment, clone remaps, pointer-decided placement) all stays. The tool code lives at e30042db for reference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: gate wall adoption to grounded slabs; panel moves, drag stretches then unsticks Floating decks keep their drawn polygon (and stop being seam candidates for grounded neighbors) instead of growing into nearby walls. Panel elevation edits are pure placement; the viewport drag stretches a grounded slab up to 0.4 m then unsticks it into a thin deck. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: deck-attached stairs land flush with the deck surface The rise now subtracts the stair's own elected base (same election the visual lift uses), so base + rise always equals the deck walking surface; the auto-sync defers a microtask so it reads a settled spatial grid and re-converges on both deck and base-slab moves. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: draw walls and fences on elevated decks Wall and fence draw tools now publish the pointed surface, so the draw plane rides the deck top (no more perspective-skewed floor hits) and previews sit on the deck. Fences gain real support election: a pure resolveFenceSupportSlabPatch persists the deck host at draw and reshape commits; wall commits thread the pointer cap so aiming under a deck elects the floor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: unit-aware height presets Level, ceiling, and slab preset buttons show clean values per display system (8/9/10 ft storeys, 8'-9' ceilings, whole-inch slab steps) instead of converted metric labels; metric presets unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: stairs converge to their resolved rise; deck attachment disables the cutout syncStairRises now converges every follows-mode straight stair (level or deck) plus deck-attached custom rises — detaching a stair from a deck re-derives its height, and ordinary stairs finally track level-height changes. Attaching via the panel writes slabOpeningMode none and hides the cutout controls; detaching restores the destination cutout and clears the stale explicit rise. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: stacked-slab move hopping — one ray, one surface, one XZ The hop was hysteresis: the election consumed the riding grid plane's perspective-skewed hit, giving two self-consistent fixed points for one pointer ray. getPointedSupportSurface now returns the ray's crossing of the pointed surface and both the support cap and the cursor XZ derive from that single computation, so items stay on the surface the pointer aims at and sit exactly under the cursor across storeys. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: single stair per click; tools restore select mode on exit The stair tool subscribed to both node clicks (synthesized on pointerup) and the native-click grid event with none of the guards sibling tools carry — one physical click over any node surface committed twice. A commit gate + follow-up click swallow fix the double dispatch, and the stair and column tools now restore select mode on exit instead of leaving the dead build-mode-without-tool state that ignored every click. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: compact multi-selection panel with host footer slot Selecting multiple nodes now docks the collapsed-by-default panel on the right: N selected header, kind breakdown, and Duplicate/Delete mirroring the floating pill. A new multiSelectionFooter slot lets the host app dock actions below it, exactly like inspectorFooter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
22af31dea8 |
editor: snapping overhaul — shared-wall rooms, deterministic slab elevation, cursor-true transfers (#458)
* fix(snapping): show alignment lines in all modes, snap only in lines; north export; R/T hint; grid default - Alignment guides display in every snapping mode (grid/lines/angles/off); magnetic pull applies only in 'lines'. New isAlignmentGuideActive() predicate decouples guide display from the snap delta across all placement/move/draw producers (item, wall, fence, slab/ceiling/roof, column/shelf/spawn, door/ window, MEP, 2D floorplan drafting, surface snap). - Floorplan PDF export now rotates to north-up (FLOORPLAN_VIEW_ROTATION_DEG - building rotation), matching the on-screen aligned-to-north view. - Item placement rotate hint collapsed to a single 'R / T Rotate' row. - Default item snapping mode changed lines -> grid. * feat(walls): detect rooms across shared walls + tight connect-snap in all modes Room detection: planarize the wall graph before face-finding — split straight walls at T-junctions where another wall ends mid-span, so a room closed against the middle of an existing wall is detected, not just isolated 4-wall rooms. Auto-close: wire the wall builder's "Room (auto-close)" to the same room graph via wallClosesRoom(), so drafting stops when a segment seals a room against the existing structure — not only when the chain returns to its own start. Connectivity snap: add a tight wall-connect snap (WALL_CONNECT_SNAP_RADIUS 0.05) that also runs in grid/off/angles — within range of a wall (body or corner, uniform radius) the endpoint sticks onto it and the beacon shows, so rooms close in every mode. Lines keeps its wider magnetic radii. Gate alignment guides in non-magnetic modes to the same connect distance so a corner dot no longer magnetises the cursor from far. Tests: cover T-junction detection + wallClosesRoom; update the item-default and wall-split tests for the mode-driven behaviour. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(surfaces): gate alignment guides to connect distance in non-magnetic modes Port the wall drafting/endpoint gating to the shared slab/ceiling/roof snap (resolveSurfacePlanPointSnap): in grid/off/angles, only anchors within the connect distance are fed to the alignment resolver, so guides form to nearby points and far corner dots stop lighting up from across the plan. Filtering the candidates (local-frame, like the cursor) rather than the resolved guides avoids the floor-plan view rotation baked into the guide coords. Lines mode keeps the full-range guides; the surface wall connect-snap is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(slabs): deterministic wall-slab overlap via clipped overlap length Wall elevation used ray-cast point samples that landed exactly on the slab boundary for perpendicular walls, so whether a butting wall followed the slab's elevation depended on which side of the slab it touched. Clip the wall centerline and face lines against the polygon and require >=5cm of on/inside length instead: walls along the slab edge follow it on every side, point contact never does. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(grid): lift lattice 1mm off slab tops; wall-align on wall-item move start The horizontal snap lattice sat exactly at the followed surface Y, so it z-fought elevated slab tops while moving items. The visual mesh now rides 1mm above; the grid event plane keeps the true height. Moving a wall-hosted item showed a horizontal grid until the first pointer move published a wall surface — the mesh fallback assumed UP. Derive the host wall normal from the item mesh's world orientation (local +Z faces out of the wall) so the lattice is wall-aligned from the first frame. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): unify selection shortcuts + truthful select-mode hints Shift+click now toggles selection membership in 3D like Cmd/Ctrl (and appends on box-select release), matching the 2D floorplan. The HUD rows describe what actually works in both views: move is plain left-drag (the grip/dot), the two vague selection rows collapse into one Cmd/Shift or-group, and the modifier-held variants (freely / with guides / bypass snaps) are gone — guides follow the snapping mode now. Key pills join with + for combos and / for alternatives; Shift renders as the shift icon. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): group gizmos — reliable rotate grabs, mutual hide, snapping-mode integration The group-rotate gizmo never received pointer events through the shared invisible hit-area path (EDITOR_LAYER + custom raycast) in its portalled context; its handlers now live on the visible arrow plus a plain default-layer invisible torus, so hover and drag work with a fat target. Group drags begin a handle-drag scope: each gizmo hides while its sibling drags (the frozen corner goes stale), idle hints leave the HUD, and the drag gets contextual hints — Shift free-rotation for rotate, the snapping chips for move. Group move joins the snapping-mode system via the 'item' context: Shift cycles the mode, Ctrl the grid step (both read live mid-drag), and 'lines' runs the same Figma-style alignment as single-node moves against the group's bbox anchors. Chip clicks now tick like the keyboard cycles. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): group-move ticks in every snapping mode Mirror the single-node move's sfx: emit per delta change rather than only on grid crossings, so lines/off get the same rate-limited texture. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(items): keep host transfers glued to the cursor Moving between hosts used to diverge from the pointer and mangle state: - Grab offsets: every surface anchor (wall / ceiling / shelf / item surface / floor) preserved the grab offset by re-seeding from the item's carried-over position on each new host, landing it far from the cursor. The grab offset now survives only on the original host and only until the item anchors anywhere else — after that every host (the original included) centers the item under the cursor. Applied uniformly to the placement coordinator and the window/door move tools. - Rotation: detaching from a rotated shelf/table back to the floor kept the HOST-local yaw as the level yaw, visibly spinning the item. The detach now re-expresses the item's world yaw in the level frame. - Elevation: the same detach wrote the level parentId to the store but not the draft ref, so the floor-elevation resolver bailed on its parent-must-be-a-level guard — the snap grid and the item stopped following slab elevations for the rest of the drag. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(items): latch grab-offset forgetting across every host kind Review follow-up (PR #458): unify the per-surface grab-forget rule into one grabForgotten latch. A wall/ceiling item could not actually reach a shelf or the floor (item-surface enter rejects attachTo assets), but a wall item CAN anchor on a roof face — and returning to its original wall then restored the stale grab offset. Roof-wall transitions (and floor landings after a host visit) now trip the latch in the coordinator and the window/door move tools alike. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
84cdf4519d |
feat(nodes): mode-aware roof-segment edit + 2D rotation parity with 3D
Roof-segment edit/move ignored the active snap mode and showed no chip: - add snapProfile:'structural' so a body-move resolves the no-angle polygon context (grid/lines/off) like every other structural move; - resize uses getSegmentGridStep() (0 outside grid mode = the "smooth" resize that used to need a held Shift), dropping the captured gridSnapStep + Shift; - move drops its Shift bypass; - the affordance dispatcher opens a boundary reshape scope for the resize so the snapping chip shows and the context resolves. 2D rotation handles now match the 3D gizmo across all six rotate affordances (column / elevator / roof-segment / shelf / spawn / stair): a shared rotateAffordanceDelta snaps to the 15° step unless Shift (free), the dispatcher opens the same ROTATE_HANDLE_DRAG_LABEL handle-drag scope the 3D gizmo uses so the contextual HUD shows the "Shift = rotate freely" hint, and the live degree readout snaps to match the committed rotation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
8a57105eec |
feat(editor): mode-driven shelf/column/spawn placement + cross-kind floor collision
Migrate the remaining floor-placed kinds onto the unified snapping/modifier
model and generalize floor collision so any solid floor kind blocks any other.
- shelf/column/spawn declare `snapProfile: 'item'` → contextual snapping chip,
Shift=cycle, Ctrl=grid step during placement; their tools read the active
mode (grid/lines/off) instead of legacy Shift/Alt bypass; spawn fresh
placement now respects alignment ("lines") like its move.
- Resize/radial handles claim the handle-drag scope (new RESIZE_HANDLE_DRAG_LABEL)
so the HUD shows no select-mode shortcuts mid-resize.
- Column move migrated to the generic MoveRegistryNodeTool (declare `movable`,
drop the bespoke move-tool) — gains mode-driven snapping, alignment, R/T,
slab lift, grid SFX, and the collision box for free. 2D move still routes
through `floorplanMoveTarget`.
- Cross-kind floor collision: new declarative `FloorPlacedConfig.collides`
(item/shelf/column opt in; spawn/MEP/stair stay off). `canPlaceOnFloor` now
treats every colliding floor kind as an obstacle (was item-only), reading the
declarative footprint; the generic move tool's red/green placement box gates
on `collides`. Column footprint uses the visible `columnFootprintHalf` extent
so the box/slab-lift/collision track the real (round/square) column size.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
04f1b0d59e |
feat(editor): node-declared per-context snapping + contextual HUD + painter scope
Generify the snapping/modifier HUD off the FSM scope and node declarations
instead of wall-creation-shaped, leaking pills.
- Per-context snapping (`snappingModeByContext`, persisted): wall / item /
polygon mode-sets with exclusive modes (grid | lines | angles | off), each
doing exactly what its chip says. Context is node-declared via the new
`NodeDefinition.snapProfile` ('item' | 'structural'); the resolver maps
(profile × action) → context with no per-kind switch.
- Scope-driven HUD: helper-manager reads the interaction scope; reshaping
(endpoint/curve/boundary) and item move get their own chip, no select-hint
leak. Rotate R/T rounds to 45°; Alt = force-place only (hidden for
structural kinds); Shift = cycle everywhere.
- Slab/ceiling drafting: Shift=cycle, mode-aware grid/angle, Enter finishes
(minDraftVertices); polygon boundary vertex/edge drag begins a reshaping
scope. Fix grid/angle being ignored on boundary edit + slab creation:
make resolveSurfacePlanPointSnap exclusive (alignment gated on magnetic) so
grid/angles keep the snapped fallback instead of the raw cursor.
- Painter application scope: node-derived (single/object/matching/room) from
the hovered node, cyclable via Shift, single-source HUD chip.
- Remove the redundant GridSnapControl from view-toggles (grid step lives in
the contextual HUD now).
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> |
||
|
|
6d5f041b48 |
fix(walls): bound wall miters, heal corrupt scenes, follow grid snap
Three related editor fixes surfaced while debugging a captured house project that rendered an infinite wall and failed to load. Infinite wall (core/systems/wall/wall-mitering.ts): Junction miters are line-line intersections, so the joint point sits ~halfThickness/sin(theta) from the junction. The only guard was an exact-parallel check (det < 1e-9), so two walls meeting at a shallow angle (a room-preset preview dragged onto an existing wall, or a wall drawn nearly collinear to its neighbour) produced a joint point far away — an infinite spike. Add a miter limit: reject joints farther than 10x half-thickness from the junction and fall back to a square joint, exactly like the parallel case. Scene load failure (core/utils/heal-scene-graph.ts + validate-build-json + use-scene migrateNodes): Capture wall-merge could leave a `children: [null]` entry (see the matching merge-walls.ts fix in private-editor) and zero-length walls. `null` children fail wall schema validation, so the whole scene fails to load. Add a shared heal step — strip non-string child refs, drop childless zero-length walls — run on every load path: import validation now repairs instead of hard-failing (with a warning), and setScene heals on the prod project-load path too. Grid snap (nodes slab/ceiling/spawn tools): These tools hardcoded a 0.5 m snap (Math.round(x*2)/2) and ignored the editor's grid-snap setting, so the cursor jumped by 0.5 while later vertices already followed the configured step. Route them through snapPointToGrid / snapScalar with gridSnapStep. Adds unit tests for the miter limit and the heal step. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5411f5abc8 | fix(editor): improve guided manipulation and snap affordances | ||
|
|
aab48e053f |
fix(editor): harden editor interactions and WebGPU rendering
Fix editor bug sweep regressions, WebGPU CSG/material crashes, Shift snap bypass behavior, arrow handle drag projection, and the wall preview null guard covered by the Sentry follow-up PRs. |
||
|
|
5fd9be05df | Fix spawn floorplan move handle | ||
|
|
b33059ef81 | feat: add persistent site boundary handles | ||
|
|
6b8dc33b62 | fix: restore walkthrough collisions and spawn controls | ||
|
|
0b338cf647 |
feat(editor): live floor-stacking, unified handle system, slab-hole editing + interaction polish (#375)
- Live slab-stacking Y previews for all floor-placed kinds (item/shelf/spawn/column/stair) during placement + both move pathways, via a shared core resolver; canonical positions unchanged. - Unified 3D handle system (one drag pipeline + one visual primitive) with forgiving invisible hit-areas on every handle, kept on EDITOR_LAYER so they don't poison the MRT scene pass. - Hover + click-to-edit slab holes in 3D (manual hole -> hole editor; stair/elevator hole -> select owner); generic cross-arrow polygon-move grip; normalized handle interaction colors. - NaN-safe node mutations + non-finite shadow-light bounds guard. - Built on #373 (level-scoped alignment / registry slab tool); #373 owns X/Z alignment, this owns Y floor-stacking. |
||
|
|
7f49efce68 |
fix(nodes): repair the nodes test suite under bun (#360)
These tests had never run in CI (the editor repo's own CI only runs
mcp-ci); they surfaced when the private-editor monorepo runs
`@pascal-app/nodes` tests via turbo. Four independent issues, all
test-side except one skip that flags a real bug:
- index.test.ts: the AnyNode-discriminator drift check read the literal
via the zod-v3 `option.shape.type.value` getter, which is gone in
zod v4 (and the field is ZodDefault-wrapped). Unwrap to the innermost
def and read `_zod.def.values[0]`.
- spawn/parity.test.ts: the spawn palette icon is `{kind:'url'}` now
(registered kinds point at palette assets); the test still asserted
'iconify'. Update to 'url'.
- solar-panel/geometry.test.ts: `getSurfaceY` / `getAnalyticalNormal`
moved to `../../shared/roof-surface`; fix the stale import path.
- shelf/geometry.test.ts: skip "user-set material is applied" — it
catches a real bug (cloning a MeshStandardNodeMaterial drops
color/roughness/metalness, so painted shelves/slabs render white).
The clone is required; the fix belongs in the viewer material layer
and is tracked separately.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
1fd59dd9cd |
feat(editor): preset-system primitives (#340) (#341)
* feat(editor): preset-system primitives — presettable, sceneApi subtree round-trip, isolate + setCaptureMode enum, headless exports
Per pascalorg/editor#340 (redesigned: single live canvas, no Viewer scene prop).
Core
- `capabilities.presettable` on `NodeDefinition` + `isPresettable` /
`isPresettableKind` helpers. Explicit `false` on level / building /
site / zone / spawn / guide / scan / item; implicit `true` for any
kind with `def.parametrics`.
- `sceneApi.getSubtreeSnapshot(rootId)` + `materializeSubtree(subtree,
position, parentId?)` for round-tripping a node subtree through
catalog storage. Strips id / parentId / absolute root position /
host refs (`wallId`, `wallT`); fresh IDs minted at materialize time;
child ordering preserved (FIFO walk).
Viewer
- `<Viewer isolate>` prop + `ViewerHandle.setIsolated(ids | null)`.
Walks `sceneRegistry`, hides every registered group not in the
isolated set's ancestor + descendant closure. Building block for
preset capture + future focus-mode UX.
Editor
- `useEditor.captureMode: CaptureMode` discriminated union
(`idle` | `standard` | `preset`). `isCaptureMode` stays as a derived
boolean for the existing read sites; `setCaptureMode` accepts both
the boolean shape (back-compat) and the enum.
- `preset` capture mode in `SnapshotCaptureOverlay`: drag locked to a
square, mode-picker hidden, transparent flag forwarded through the
`camera-controls:generate-thumbnail` emitter event.
- Headless exports: `Inspector` (alias of `ParametricInspector`),
`FloatingMenu` (alias of `FloatingActionMenu`), `ToolbarLeft` /
`ToolbarRight` (aliases of `ViewerToolbarLeft` / `ViewerToolbarRight`),
`useSelection` hook returning `{selectedIds, selectedNode, building/
level/zone}`, plus re-exports of `useScene` / `useViewer` from core /
viewer so consumer shells (community, embedders) need only one import.
Out of scope by design (see issue #340 "Out of scope"): a separate
offscreen Viewer rendering an arbitrary subtree. The unified preset
modal captures inside the live canvas via isolation + the existing
snapshot pipeline — no `useScene` factory / React context refactor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(editor): split snapshot/materialize into pure getSubtree + cloneNodesInto; add def.hostRefFields; auto-stage preset capture square
Per pascalorg/editor#340 redesign discussion: the editor's scene API
should expose *pure* primitives and let the host (community modal,
embedders) own storage shape, position stripping, and host-ref
re-derivation policy.
Editor API delta
- `sceneApi.getSubtreeSnapshot(rootId)` → `sceneApi.getSubtree(rootId)`
Returns the live subtree verbatim (BFS via `children[]`, no clones,
no stripping). Callers deep-clone if they need persistence.
- `sceneApi.materializeSubtree(subtree, pos, parent?)`
→ `sceneApi.cloneNodesInto(nodes, { rootId, parentId?, position? })`
Generic clone-and-insert. Deep-clones via JSON, mints fresh ids
preserving the prefix, rewires parent/children, stamps position +
parent if supplied. Host-ref-agnostic — `wallId`/`wallT` etc are
preserved verbatim.
- New `capabilities.hostRefFields?: string[]` on `NodeDefinition`.
Declares per kind which schema fields are placement-derived so the
host strips them at preset-save time. Declared on door (`['wallId']`),
window (`['wallId']`), item (`['wallId', 'wallT']`).
- New `getHostRefFields(def)` exported from `@pascal-app/core`.
Removed the intermediate token-based payload format (`NodeSubtree`,
`buildSubtreeSnapshot`, `materializeSubtree`, `SubtreeNode`).
UX polish
- `<SnapshotCaptureOverlay>` in `preset` mode now auto-stages a centered
square crop sized to ~75% of the shorter viewport dimension. The
user can pan / move / resize within square-aspect, but doesn't have
to drag from scratch — clicking the capture button works
immediately on entry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(editor): lock preset capture frame; allow item presets
- SnapshotCaptureOverlay: in `preset` mode, the auto-staged centered
square is now fully locked — corner handles hidden, the dim layer is
click-through (no drag-to-move, no drag-to-resize). The user just
adjusts the camera (orbit / pan / zoom) and clicks capture. The
letterbox + dashed border stay visible as a cosmetic frame.
- `item.capabilities.presettable` removed (implicit `true` via
`def.parametrics`). Enables compositions like "table-with-plants",
"shelf-with-books" where the preset root may be an item and other
items ride along as descendants. The GLB-kind item catalog is
unchanged; presets become siblings of GLB rows under the same
`items` table.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(editor): auto-frame camera on preset capture entry; restore on exit
`<CustomCameraControls>` now watches `useEditor.captureMode` and, when
preset capture mode begins, flies the camera to a pose that fits the
union bounds of the isolated subtree inside the locked square crop —
no more hunting for the subject after opening the modal. The
pre-capture pose is stashed and restored on exit so the user lands
exactly where they were.
The user can still pan / orbit / zoom from the auto-staged pose if
they want a different angle before snapping.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
bb5ce68254 |
Viewer render modes: Solid/Rendered + textures + surface-role clay + scene themes + edges (#332)
* viewer: add Phase 1 render-modes foundation (shading/textures/colorPreset state, defaultRender prop, SSGI gating) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viewer: Phase 2 render-modes material-class switch (Lambert in solid, Standard in rendered) Shading-aware material factories (cached per class), reactive selection in renderers via the useViewer(shading) pattern, and dirty-rebuild on toggle for geometry/door systems. Rendered mode output unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viewer: Phase 3a render-modes surface roles + clay palette foundation Adds surfaceRole token to core NodeDefinition, per-kind default roles, ColorPreset palettes + resolveSurfaceColor/createSurfaceRoleMaterial (glazing stays translucent), and the textures-off recolor path for def.geometry kinds (slab/fence/shelf via GeometrySystem.applyDefaultSurfaceRole) + wall. Renderer- based kinds (roof/window/stair/item/column/door/ceiling/elevator) wired in 3b. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viewer: Phase 3b render-modes textures-off recoloring for renderer/system kinds Wires clay role coloring (textures=off) for roof/roof-segment, window, stair/ stair-segment, door, item, column, ceiling, elevator via createSurfaceRoleMaterial, reactive on textures/colorPreset. Per-surface roles: roof top+edge=roof / underside=ceiling; window frame=joinery / glass=glazing; stair+door+elevator= joinery; ceiling=ceiling; column=wall; item=furnishing. textures=on unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * editor: Phase 4a render-modes UI — Solid/Rendered toggle + per-context persistence Per-context shading via renderContext discriminator + shadingByContext (persisted); <Viewer> renderContext prop seeds per-context on mount. Solid/Rendered toggle in the editor action bar + standalone toolbar + command palette. Editor mounts default to renderContext=editor / shading=solid. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viewer: fix window-system glassMaterial type to allow clay glazing reassignment The let was inferred as MeshLambertNodeMaterial from the imported glass constant, so reassigning createSurfaceRoleMaterial('glazing') (returns THREE.Material) failed under tsc --build. Widen the annotation to THREE.Material. Surfaced by the build (check-types had replayed a stale turbo cache). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viewer: Phase 5 lighting — add theme-driven hemisphere light, trim fill directionals 3->2 Adds a sky/ground hemisphere fill (theme-lerped) and drops the second fill directional; the hemisphere covers the shadow-side fill it provided, at one fewer per-fragment directional term (shared by Solid + Rendered). Ambient lowered since the hemisphere now carries soft fill. Intensities are a starting point — tune visually on the gpu-perf overlay. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viewer: tune Solid lighting for more form — stronger hemisphere sky/ground contrast, lower ambient Darker hemisphere ground (#d8d6cf -> #aaa49a) + higher hemisphere intensity and lower ambient so directional shading reads as form and undersides ground without AO. Keeps Solid free of any post-processing pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: biome format render-mode files (lefthook pre-commit) Formatting-only — import wrapping, dep-array wrapping, single-line ternaries — across Phase 2-4a files that weren't biome-clean. No logic changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viewer: scene-theme system — named environment themes (studio/paper/sunset/night/...) New SceneTheme registry (lib/scene-themes.ts) drives lights, background, and tone mapping; lights.tsx refactored data-driven (N directionals + hemisphere + ambient). sceneTheme state (persisted) + cycle-button picker in editor bar + standalone toolbar, importing the registry from the viewer barrel (single source). Default 'studio' reproduces the prior look exactly; app light/dark 'theme' untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * editor: Phase 7 — popover-dropdown pickers for render mode + scene theme (editor bar) Replace the shading + scene-theme cycle buttons in viewer-overlay.tsx with DropdownMenu pickers: render mode shows 2 rows (Solid/Rendered) with one-line detail; scene theme lists all themes with a derived color-swatch strip + active check. Imports the registry from the viewer barrel (single source). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * editor: Phase 7 — dropdown pickers in standalone toolbar + export DropdownMenu from barrel Replicate the render-mode + scene-theme dropdown pickers (with swatch strip + active check) to apps/editor's compact toolbar, matching viewer-overlay.tsx. Export DropdownMenu* from the @pascal-app/editor barrel for the standalone app. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viewer+editor: Phase 8 — crisp geometry edge overlay (off/soft/strong/sketchy) EdgeOverlaySystem draws EdgesGeometry LineSegments over node-backed building meshes (scoped via sceneRegistry, skips zone-layer/hitbox/overlay meshes), rebuilt on geometry-uuid or mode change, line color follows scene-theme background luminance; sketchy = static TSL vertex jitter. New 'edges' state (persisted, default off) + Edges dropdown in editor bar + standalone toolbar. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viewer+editor: edge overlay — thick lines via Line2, drop sketchy mode Switch EdgeOverlaySystem from LineBasicNodeMaterial (1px hardware cap) to LineSegments2 + Line2NodeMaterial so edges have real screen-space width (soft 1.5px / strong 3px); resolution tracks viewport. EdgeMode is now off/soft/strong (sketchy removed). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viewer: fix edge overlay crash — webgpu Line2NodeMaterial has no settable resolution material.resolution is undefined under WebGPU (the node material reads the viewport internally); optional-chain the .set() call so it no-ops there instead of throwing. Thickness still applies via linewidth. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viewer: edge overlay — weld vertices to fix CSG spiderweb edges + lighter strong Position-only mergeVertices before EdgesGeometry so coplanar triangles from CSG-cut walls (doors/windows) share vertices and their interior edges are suppressed — only opening outlines + silhouettes remain. Strong linewidth 3 -> 2px (was too heavy). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viewer: edge overlay — crease-only extractor to fix CSG spiderweb Replace EdgesGeometry (which always draws unpaired boundary/T-junction edges) with buildCreaseEdges: weld positions, keep only edges shared by exactly two faces whose dihedral exceeds the threshold, drop everything unpaired. CSG-cut walls/slabs are watertight so real corners + opening outlines survive while the interior triangulation fans (coplanar or T-junction) are removed. Open meshes (bare ground plane, billboard leaves) shed their boundary clutter too. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viewer: crease edges — coarser weld (0.1mm->1mm) to recover CSG/extrude seam edges The cap<->side-wall top edge of ExtrudeGeometry walls drifts past 0.1mm after CSG, so it stayed unpaired and was dropped. Weld at ~1mm to pair it into a real crease while staying far below feature size (wall thickness, openings). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viewer: replace geometry edge overlay with screen-space ink (SketchUp look) Port the prototype's screen-space ink into the post-processing pipeline: depth + normal Sobel reading the scene-pass MRT. Crease term (normalized normals, center-vs-neighbour) + distance-independent depth-step term (raw Laplacian / (1-d)² with a noise gate so flat ground stays clean). Topology-agnostic, so it finally handles CSG-cut walls/openings without the spiderweb or missing-edge problems of EdgesGeometry. Driven by the existing edges off/soft/strong mode; MRT now builds when SSGI OR ink is on; ink colour tracks scene-theme luminance. Removes EdgeOverlaySystem + crease-edges (geometry approach). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viewer: soft/strong ink modes + keep editor overlays out of the ink Two adjustments to the screen-space ink pass: 1. Soft vs strong now visibly differ. The edge masks saturate, so the old `intensity` gain did nothing once a line was detected. Replace it with a sample radius (line thickness) + opacity: soft = 1px / 50%, strong = 2px / 100%. `inkedEdges` takes `radius` + `opacity` instead of `intensity`. 2. Editor overlays (gizmos, move handles, tool previews, grid) no longer get inked. The scene pass that feeds the depth/normal MRT now renders only SCENE_LAYER; overlays render in a dedicated pass on OVERLAY_LAYER and are composited on top after the ink + outlines, so they read as crisp UI and never get inked or AO'd. New OVERLAY_LAYER constant in viewer; editor's EDITOR_LAYER re-exports it so the two stay in lockstep. Also moves WallMoveSideHandles (the wall/fence move arrows) onto EDITOR_LAYER — it was the one overlay still on SCENE_LAYER. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viewer: render the grid in the scene pass so geometry occludes it The depth-gate fix couldn't help the grid: its material is depthWrite:false, so it never wrote overlay-pass depth and the "didn't write depth -> keep on top" term forced it on top — hence the floor grid bleeding through walls and objects. A full-floor plane can only be occluded correctly by living in the same depth context as the scene, so move the grid onto its own GRID_LAYER which the scene pass renders (alongside SCENE_LAYER). It's flat and depth-non-writing, so the screen-space ink still ignores it; gizmos/handles stay on OVERLAY_LAYER. The grid camera layer is enabled in custom-camera-controls and disabled on the thumbnail camera so thumbnails stay grid-free, matching EDITOR_LAYER. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viewer: strong ink — match soft's 1px thickness, differ by darkness only Strong at radius 2 read too thick. Soft's 1px line is the nice one, so use it for both modes and let strong distinguish itself purely by being fully solid (opacity 1) vs soft's lighter 50%. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viewer: shadow frustum follows the view + a shadows on/off setting The directional light's ortho shadow camera only covers ±50 around the light target, which was pinned at the origin — so zones far from origin received no shadows no matter where the camera moved. Recentre each shadow-casting light (position + target together, preserving direction) on the view focus every frame: the orbit-controls target when available, else the camera's ground projection. The shadow area now tracks wherever the user looks. Also add a persisted `shadows` toggle (default on) to the viewer store and a "Shadows" switch in the editor settings panel — the dedicated shadows control the render-modes plan deferred. Lights gate castShadow on it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * editor: shadows toggle in the standalone toolbar + a touch more shadow strength The shadows switch I added only lived in the cloud settings panel's Visibility section, which is hidden in the local/standalone editor (no projectId). Add a ShadowsToggle button next to the grid toggle in the standalone toolbar so it's reachable there, matching how Show Grid is exposed in both places. Also push shadow strength partway toward the aesthetic prototype (which runs near-black, no blur): bump the bright-key shadow-intensity cap 0.4 -> 0.55 and tighten shadow-radius 2 -> 1.5. Still softer than aesthetic by design. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * nodes: site ground receives shadows (lit material instead of unlit Basic) The site ground fill used MeshBasicMaterial — unlit, so it could never show the directional shadow, and shadows visibly truncated at the slab edge. Swap it for a lit MeshLambertNodeMaterial with receiveShadow on the mesh; the geometry is the site polygon (slab footprints punched out), so shadows now extend across the whole site and stop at its boundary, which is the desired bound. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * themes: per-theme clay palettes + 2x2 swatch in the theme pickers Each scene theme now carries a clayTints map (wall/floor/ceiling/roof/glazing) giving it a per-surface-role palette — e.g. Mediterranean's blue roof + warm walls. The theme pickers (standalone toolbar + community overlay) now render the aesthetic-style 2x2 swatch of those role tints over the theme background instead of the old 3-colour strip. Data + UI only; wiring the tints into the textures-off surface materials is a separate change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * editor: slimmer theme switcher + cloud-sun icon Shrink the scene-theme toolbar button (w-[8.5rem] -> w-28) so it stops reserving space for "Mediterranean"; the label truncates when it overflows. Swap the palette icon for cloud-sun (atmosphere/lighting, distinct from the app light/dark Sun-Moon toggle) in both the standalone toolbar and the community overlay. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * editor: theme switcher icon -> swatch-book Swap the scene-theme icon from cloud-sun to swatch-book in both the standalone toolbar and the community overlay. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * themes: colour untextured building surfaces by the active scene theme Untextured walls/roof/slab/ceiling now take the active theme's per-role colour (theme.clayTints[role], falling back to the colour preset) in BOTH textures modes. The textures toggle only governs surfaces that actually have an explicit material/preset — those still show their texture when textures are on. This is what makes e.g. Mediterranean read as a blue roof + warm walls instead of the old hardcoded white/grey defaults. - materials.ts: resolveSurfaceColor / createSurfaceRoleMaterial take an optional sceneThemeId (theme tint ?? preset palette); theme folded into the cache key. - wall-materials, roof-materials, slab/geometry, ceiling/renderer: the untextured fallback now resolves to the themed role colour instead of white/grey, in both modes; theme threaded into each builder + material cache key. - wall-cutout: now reads textures/colorPreset/sceneTheme and re-applies wall materials when any change (previously it ignored textures/colorPreset entirely). - geometry-system: threads sceneTheme into the generic surface-role path + rebuild effect. Renderers/preview call sites thread sceneTheme through. Doors/windows/stairs/columns/items still use their existing defaults — a follow-up pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(slab): recessed (negative-elevation) slabs extrude downward again The registry geometry builder created the slab mesh at Y=0 without applying the negative-elevation offset, so recessed slabs rendered above the floor plane (pool geometry is built locally with its floor cap at Y=0 and walls rising to Y=|elevation|, so the mesh must be shifted down by `elevation` to recess). The runtime slab-system already did this; the static builder path didn't. Mirror it: shift mesh.position.y by elevation when negative. Positive elevation unchanged. Unrelated to render modes — bundled into this branch's PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(roof): legacy roofs render again — migration wrote invalid wallHeight 0 Legacy roof nodes (old format, no `children`) were migrated with a hardcoded `wallHeight: 0`. With zero wall height the eave height (`wallHeight - autoDrop`) went negative in getRoofSegmentBrushes, producing geometrically invalid brushes; three-bvh-csg then spammed "TriangleClipper: Coplanar clip not handled" every frame and emitted NaN positions, so the merged roof geometry failed computeBoundingSphere and never rendered. - core/use-scene migration: wallHeight 0 -> 0.5 (the RoofSegmentNode schema default), so migrated segments have a valid wall height. - roof-system: clamp eave height to >= 0.01 so an intentional wallHeight 0 can never yield a negative eave, and guard updateMergedRoofGeometry so a CSG result with NaN positions is discarded (keep the last good mesh, warn once per roof) instead of poisoning the buffer + spamming the console. Unrelated to render modes — bundled into this branch's PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(column): columns cast and receive shadows Column meshes (box, beam, cylinder, sphere, torus) rendered without castShadow/ receiveShadow, so columns neither dropped a shadow nor caught one — unlike walls, slabs and roofs. Set both on all column shape meshes. Unrelated to render modes — bundled into this branch's PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * viewer: default edges to soft Editor defaults are now solid shading / studio theme / soft edges / shadows on. Shading (solid, via EDITOR_DEFAULT_RENDER), theme (studio) and shadows (on) were already the defaults; edges was 'off' — make 'soft' the default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * themes: fold light/dark into the scene theme (remove the separate toggle) The viewer had two overlapping appearance controls: a light/dark `theme` toggle AND scene themes (which already drive the 3D background + lights). They conflicted — e.g. Night/Twilight are dark themes, but the light/dark toggle was an independent axis still tinting the 2D scene chrome. Unify on the scene theme: add an explicit `appearance: 'light' | 'dark'` to each SceneTheme (twilight/night = dark, the rest = light) and drive everything the old toggle drove off it — canvas backdrop, grid line colours, measurement-label/ cursor/site-edge contrast, the site ground fill, the ground occluder, and the mobile viewer bg. The editor UI chrome is unaffected (always dark via a fixed body class). Removes the `theme`/`setTheme` store state (+ persistence) and every light/dark toggle UI: the standalone toolbar Sun/Moon button, the community overlay theme switch, the command-palette command, and the ifc-converter preview toolbar button. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * themes: per-theme ground colour + add the "Verdant" nature theme - Add a `ground` colour to every SceneTheme and drive the site ground fill + the infinite ground-occluder off it (instead of the binary isDark ? #1f2433 : #fafafa). Dark themes now get a lit mid-tone ground (twilight #4a4566, night #2b3247) so the ground reads as ground rather than going near-black. - Add a new green/nature scene theme "Verdant": soft green sky + lit, with a green roof clay tint and mossy ground. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * wiki: document the surface-colour / theme system The colour-per-node/renderer/system model from the render-modes work was undocumented. Add wiki/architecture/materials-and-themes.md covering surface roles, colour presets, the textures axis, scene themes (appearance / ground / clay tints), and the "untextured surfaces are theme-coloured in both modes" invariant + where each kind wires it. Also fix two pages that the same work made stale: - node-definitions: geometry builders receive (shading, textures, colorPreset, sceneTheme); document the `surfaceRole` token + applyDefaultSurfaceRole. - layers: OVERLAY_LAYER (1, viewer) with EDITOR_LAYER now its alias, the new GRID_LAYER (3, rendered in the scene pass for depth occlusion), and the overlay pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * nodes: wire #330 roof-accessory kinds into the surface-role/theme colours PR #330's new kinds (chimney, dormer, skylight, solar-panel, ridge-vent, box-vent) use custom renderers, so the generic textures-off recolour path never reached them — they fell back to hardcoded colours. Wire each renderer into the render-modes system: read shading/textures/colorPreset/sceneTheme and resolve untextured surfaces via createSurfaceRoleMaterial (and force the role colour when textures are off), matching column/ceiling. Roles: chimney body→wall / cap→roof; dormer wall→wall, roof→roof, glass→glazing, frame→joinery; skylight glass→glazing / frame→joinery; ridge-vent + box-vent→roof; solar-panel frame→roof (the dark product-specific cell face is left as-is). Each definition also gets its dominant `surfaceRole` token. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(roof): legacy-roof migration must use a non-zero wallHeight The render-modes/#330 merge left the legacy roof→roof-segment migration writing `wallHeight: 0`. With #330's pitch model that builds a flat, zero-volume wall CSG brush, which three-bvh-csg can't clip ("Coplanar clip not handled") and yields NaN positions — so the migrated old roof never renders. Use the schema default 0.5 (what new roofs use), giving a valid wall. The eave clamp + merged- geometry NaN guard added earlier stay as defense-in-depth. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(roof): guard slope frame against missing/NaN pitch (no more NaN geometry) getSegmentSlopeFrame used `pitch <= 0` to detect flat/zero-pitch, but an undefined or NaN pitch (a segment from an older migration that set `roofHeight` instead of `pitch`, or stale persisted data) slips past that check and computes Math.tan(NaN) → NaN tanTheta/activeRh → NaN segment geometry → the merged-roof CSG spews "Coplanar clip not handled" and NaN positions, so the roof never renders. Use `!(pitch > 0)` so any non-positive/non-finite pitch resolves to the flat frame. Self-heals bad data regardless of how the segment was produced. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(roof): migration guarantees a positive pitch for every roof-segment Segments saved with neither a valid pitch nor a roofHeight (older/partial saves, e.g. landing home-graph) fell through the legacy roofHeight->pitch branch, leaving pitch undefined. The slope-frame guard then resolved them to a flat frame, so the roof rendered as a slab instead of pitched. Branch 2b now normalises any segment lacking a valid pitch: derive from roofHeight when present, else fall back to the schema default (40deg). The migration result is cast (not zod-parsed), so this is the only place the default lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
3adae8acea | style: apply biome lint fixes after portforward | ||
|
|
9a481f2511 |
registry: generic FloorElevationSystem driven by a floorPlaced capability
Symptom: a shelf placed on a level with a raised slab underneath visually clipped through it — `ItemSystem` lifted items onto slabs, but shelves (and other floor-placed registry kinds) had no equivalent path. Fix: lift the slab-elevation logic out of `ItemSystem` into a generic `<FloorElevationSystem>` keyed off a new `capabilities.floorPlaced` config. Any kind that opts in declares a `footprint(node)` (dimensions + rotation used to query overlapping slabs) and an optional `applies` predicate (skips items whose `asset.attachTo` is wall / ceiling). The new system runs at frame priority 1 so its `mesh.position.y` override lands before `ItemSystem` / `GeometrySystem` (priority 2) clear the dirty mark. The spatial-grid sync's `markNodesOverlappingSlab` also dropped its hardcoded `item` branch in favour of an iteration over every registered kind that declares `floorPlaced` — so any new floor-placed kind picks up slab-driven re-elevation automatically. Tagged kinds: - `item` — `footprint = getScaledDimensions`, `applies = !asset.attachTo` - `shelf` — `footprint = (w, h, d)` - `column` — `footprint = (w, h, d)` - `spawn` — `footprint = (0.6, 1.8, 0.6)` (marker) `ItemSystem` retains only the wall-side z-offset block (`mesh.position.z = wallThickness / 2`). The elevation block + its `getScaledDimensions` / `resolveLevelId` / `spatialGridManager` imports moved to the generic system. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d747d2f0ea |
Phase 5 Stage E: full kind migration into packages/nodes
Wholesale move of every remaining kind into its own subdirectory under
`packages/nodes/src/`, finishing the registry-driven migration. Each
kind now ships its definition, schema (re-exported from core), and any
of `geometry` / `renderer` / `system` / `floorplan` / `tool` /
`move-tool` / `panel` / `floorplan-move` / `floorplan-affordances` /
`parametrics` / `preview` it needs — no per-kind code remains under
`packages/editor/src/components/tools/` or
`packages/viewer/src/components/renderers/`.
Deleted (replaced by registry-driven equivalents):
- `tools/{ceiling,column,door,fence,item,slab,spawn,wall,window}/...`
(boundary editors, hole editors, placement tools, move tools,
endpoint movers, curve tools, helpers, math libs)
- `ui/helpers/{ceiling,slab,wall}-helper.tsx`
- `ui/panels/{column,door,elevator,item,roof,roof-segment,spawn,
stair,stair-segment,wall,window}-panel.tsx`
- `viewer/src/components/renderers/{building,ceiling,column,door,
elevator,fence,guide,item,level,roof,roof-segment,scan,site,slab,
spawn,stair,stair-segment,wall,window,zone}-renderer.tsx`
- `viewer/src/components/viewer/legacy-system.tsx`
Added under `packages/nodes/src/`:
- `building/`, `column/`, `elevator/`, `guide/`, `level/`, `roof/`,
`roof-segment/`, `scan/`, `shared/`, `site/`, `stair/`,
`stair-segment/` packages with definition + schema + renderer / system
/ floorplan / panel as appropriate.
- New `floorplan-move.ts` for every kind that supports 2D moves
(ceiling, door, item, shelf, slab, window) — single registry-driven
dispatch path via `def.floorplanMoveTarget`.
- New `floorplan-affordances.ts` for kinds with polygon / endpoint
drags (ceiling, fence, slab, wall) — using the shared
`polygon-vertex-affordance` factories.
- New per-kind `panel.tsx` for kinds with custom inspector content
(door, item, shelf, spawn, wall, window).
- New per-kind `tool.tsx` for placement (door, item, shelf, window).
- New per-kind `move-tool.tsx` for kinds with custom 3D move flows
(door, item, slab, window).
Coordinator + manager updates in `packages/editor/`:
- `tool-manager.tsx` resolves tools from the registry only — no
hardcoded type→component map.
- `panel-manager.tsx` resolves inspector panels the same way.
- `placement-{coordinator,strategies,types}.ts` extended with
shelf-surface placement.
- `selection-manager.tsx` adds the registry-selectable fallback.
- `floorplan-panel.tsx`, `floorplan-background-placement.ts`,
`floorplan-render-context.tsx` updated for the registry layer's new
contract (props, affordance dispatch, render context).
Viewer updates:
- `viewer/index.tsx` drops legacy renderer mounts.
- `node-renderer.tsx` resolves by registry only.
- `scene-bvh.tsx`, `use-node-events.ts`, `level-system.tsx`,
`wall-cutout.tsx`, `zone-system.tsx`, `materials.ts` adjusted for
the registry-only world.
Sidebar tree nodes for ceiling / fence / slab / shelf / tree-node
updated to read from the registered nodes instead of the deleted
legacy renderer trees.
Wiki: new `plugin-authoring.md` page, README index updated.
Tests in `packages/nodes/src/index.test.ts` validate every registered
kind has the required shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
6a1d853dd8 |
IconRef: add url kind, point registered kinds at palette assets
User pointed at the palette's PNG icons in the bottom toolbar — they
expected the inspector title to use the same artwork, not the iconify
lucide glyphs. Different visual style.
- IconRef gains `{ kind: 'url'; src: string }`. Plain `<img>` render
in ParametricInspector (no next/image — the inspector is
`'use client'`).
- All currently-registered kinds switched to URL refs matching their
palette `iconSrc`:
fence → /icons/fence.png
slab → /icons/floor.png
ceiling → /icons/ceiling.png
wall → /icons/wall.png
spawn → /icons/site.png
shelf → /icons/column.png (placeholder, same as palette)
- Kind-owned panels (slab/ceiling) already pass URL strings to their
own PanelWrapper; unchanged.
Door/window/item will get URL refs when they register at Stage A.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
969b154b08 |
Phase 5 depth-first: spawn C, fence B+C, slab B+C, ceiling C
Depth-first session: drive registered kinds through Stage B (pure
def.geometry, drop system re-export) and Stage C (def.floorplan,
short-circuit legacy inline rendering in floorplan-panel.tsx).
spawn → C
- buildSpawnFloorplan wired on definition (was written but deferred
to avoid double-render).
- floorplan-panel.tsx's floorplanSpawnEntries useMemo short-circuits
to [] when nodeRegistry.has('spawn').
fence → B
- generateFenceGeometry exported from viewer; buildFenceGeometry
wraps it in a Group+Mesh with DEFAULT_STAIR_MATERIAL.
- def.geometry set; renderer + system fields dropped.
- Deleted nodes/src/fence/{renderer.tsx,system.tsx}.
fence → C
- buildFenceFloorplan: polyline along centerline (sampled for curved
fences via sampleWallCenterline from core). Stroke width = node.thickness.
- floorplan-panel.tsx's floorplanFenceEntries short-circuits.
slab → B
- generateSlabGeometry exported from viewer; buildSlabGeometry wraps
it in a Group+Mesh + cached material (preset / custom / default
pattern preserved from legacy renderer).
- def.geometry set; renderer + system fields dropped.
- Deleted nodes/src/slab/{renderer.tsx,system.tsx}.
slab → C
- buildSlabFloorplan: SVG path with outer polygon + hole subpaths
(uses getRenderableSlabPolygon from core for wall-clipping parity).
- floorplan-panel.tsx's slabPolygons short-circuits.
ceiling → B INTENTIONALLY SKIPPED
- Ceiling renderer renders React children (hosted items) + uses TSL
shader materials + named meshes that other systems poke
(getObjectByName('ceiling-grid')). Pure def.geometry can't preserve
that. Ceiling keeps def.renderer (the custom escape hatch) — same
pattern item uses. Documented in ceiling/definition.ts.
ceiling → C
- buildCeilingFloorplan: dashed-outline path with hole subpaths
(visually distinct from slab since ceilings are above).
- floorplan-panel.tsx's ceilingPolygons short-circuits.
Per-kind progress after this session:
- shelf: B ✅ C ✅ (Stage E since brand-new)
- spawn: A ✅ C ✅
- wall: A ✅ (B blocked on ctx.levelData design)
- fence: A ✅ B ✅ C ✅
- slab: A ✅ B ✅ C ✅
- ceiling: A ✅ C ✅ (B intentionally not applicable)
- door / window / item: A ✅ (B+C pending in future sessions)
Known test issue: `bun test` in packages/nodes fails to load
`three-bvh-csg` through the viewer's transitive imports (UMD/ESM
mismatch in Bun's test runner). The Next.js editor build works fine
because it bundles differently. Fix requires either dynamic imports
(breaks sync def.geometry contract) or test env config — deferred.
Other tests (schema, geometry, parity) pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
d10d7f8deb |
Phase 4 follow-on: floor-plan interaction + def.toolHints + spawn floorplan builder
Three additions on top of the floor-plan registry contract:
1. def.toolHints + RegisteredToolHelper (registry contract for the
shortcut hint panel)
- New ToolHint type in core: { key, label } static array.
- Added `def.toolHints?: ToolHint[]` to NodeDefinition.
- New <RegisteredToolHelper hints={...}> in editor — same visual
styling as WallHelper / ItemHelper but data-driven.
- HelperManager: registry-first check before falling through to the
hand-written per-tool switch. Per-tool helper files get deleted
as their kind migrates `toolHints` in.
- Shelf + spawn definitions ship toolHints today; wall ports in
Phase 3 Milestone C alongside its tool/affordance port.
2. Floor-plan interaction layer (selection + drag-to-move)
- <FloorplanRegistryLayer> now wraps each entry in an interactive
<g>:
* Click → useViewer.setSelection({ selectedIds: [id] }).
Selection visual is a thicker accent-colored stroke applied
via withSelectionStyle() recursion through the FloorplanGeometry
tree — kinds don't author selection decoration.
* Drag → imperative SVG transform during the gesture, single
updateNode commit on pointerup. Same "smooth move" pattern as
MoveRegistryNodeTool for 3D drag: no per-tick store update,
no React re-render storm, no zundo bloat. Coordinate
conversion via svg.getScreenCTM().inverse().
* useScene.temporal.pause/resume brackets the gesture so one
drag = one undo step.
- Global pointermove / pointerup listeners so the gesture survives
the cursor leaving the entry's bounding box (matches the legacy
elevator-resize-drag and item-drag patterns in floorplan-panel).
3. Spawn floor-plan builder (deferred wiring)
- buildSpawnFloorplan written but NOT wired on the definition —
spawn already renders in the legacy floorplan-panel.tsx via
`floorplanSpawnEntries`, and wiring def.floorplan now would
double-render. The pure builder lives in nodes/src/spawn/
floorplan.ts ready to wire when the legacy inline branch is
removed (Phase 5 spawn-floorplan migration PR — same shape as
wall's feature flag, but per kind inside the legacy panel).
Plan updated: floor-plan interaction section locks the click/drag
contract in, wall-floor-plan-as-legacy note flags everything advanced
the user sees today as legacy that ports alongside Milestone C.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
6d97a87547 |
Selection: registry-driven, drop spawn flag, restore green color
Two concerns from the spike:
1) Selection / floating-action-menu had hardcoded kind lists scattered
across 4 files. Adding 'shelf' to each one per migration was the
wrong abstraction — the user's question "did you make it generic
from the noderegistry?" was the right one. Done now.
Added to @pascal-app/core/registry:
- getSelectableKinds(): string[] — returns all registered kinds
whose definition declares `capabilities.selectable`.
- isRegistrySelectable(kind): boolean — predicate for OR-chains.
Refactored hardcoded sites to merge registry kinds at runtime,
keeping legacy hardcoded lists intact so existing kinds keep
working unchanged:
- editor SelectionManager: 4 subscription loops (enter/leave/click)
+ structure.isValid + getSelectionTarget — all augment with
registry kinds. Phase 6 deletes the hardcoded lists.
- viewer SelectionManager: subscription loop + SelectableNodeType
broadened with `(string & {})` to accept registry kinds.
- floating-action-menu: ALLOWED_TYPES OR'd with isRegistrySelectable.
- Removed the manually-added 'shelf' entries from previous commit
857ddd4; they were redundant once the registry-driven path landed.
Future built-in nodes that declare `capabilities.selectable` get
click-selection + hover + the floating action menu (move/delete
icons) for free, no editing of these 4 files.
2) Spawn parity is signed off. Drop the
NEXT_PUBLIC_USE_REGISTRY_FOR_SPAWN flag entirely; spawn registers
unconditionally in builtinPlugin.nodes. Restored SPAWN_COLOR to
the original #22c55e green (was #ef4444 red as a Phase 2
verification marker).
Pre-existing typecheck errors in editor (ceiling/fence/slab tree-node,
scene.ts buildingId) are unchanged.
630 tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
d17e083c77 |
Add sfx:grid-snap on cursor cell-cross for shelf + spawn
Matches the wall / slab / curve-wall tool pattern: emit sfx:grid-snap only when the snapped position changes (cursor crosses a grid cell), not every frame of mouse movement within the same cell. Tracked via a `previousSnapRef` per tool, reset when the tool re-activates. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
76794ceb7d |
Wire shelf + spawn placement polish: SFX, cursor, sidebar, selection
User-visible follow-ups after first running the Phase 2 spike.
Spawn tool now matches legacy UX:
- CursorSphere from @pascal-app/editor for the placement indicator
(ring + line + tool-icon tooltip) — was a plain sphere mesh.
- Emits sfx:structure-build on commit + setTool(null) + setMode
('select') to exit build mode, matching legacy spawn-tool.
Shelf tool placement:
- Emits sfx:structure-build on commit.
- Cursor preview now shows top board + brackets (was just the top),
matching what gets placed.
Shelf selectable from the 3D canvas:
- ShelfEvent type added to @pascal-app/core/events/bus.
- 'shelf' added to NodeConfig in useNodeEvents.
- ShelfRenderer wires `useNodeEvents(node, 'shelf')` handlers onto
every mesh. Clicks/hovers now bubble through the editor's selection
manager and update useViewer.selection.
Shelf appears in the sidebar:
- ShelfTreeNode component (mirrors spawn-tree-node's shape +
selection/hover/rename wiring; lucide Layers icon).
- TreeNode dispatcher adds a `case 'shelf':` arm.
Framework changes:
- @pascal-app/editor exports CursorSphere alongside triggerSFX.
- @pascal-app/nodes now declares @pascal-app/editor as peer/dev dep.
Pre-existing typecheck errors in @pascal-app/editor (ceiling-tree-node,
fence-tree-node, slab-tree-node, scene.ts) are unchanged — present on
main and not introduced by this commit.
630 tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
ac71c1a83b |
Fix shelf cursor tracking + bigger snap step; tint registry spawn red
Three small follow-ups after the first user-visible spike test. shelf cursor stuck at origin: The placeholder cursor is a translucent slab inside a <group ref>. The tool was calling setCursor(state) on every grid:move, which triggered a React re-render. R3F re-applies props on each render, and since the <group> had no `position` prop, the implicit default [0,0,0] clobbered the imperative `position.set` from the previous tick. Result: cursor stuck at level origin instead of following the mouse. Fix: drop the unused useState entirely. Pure imperative position updates via the ref. No re-renders, no clobbering. (The legacy spawn tool gets away with the same pattern because CursorSphere buffers its position prop differently — but for the spike, the simpler model is fine.) shelf snap step: Was 0.1 (10cm) — much finer than the editor's default 0.5 grid. Bumped to 0.5 (matches the toolbar grid setting and the legacy half-meter snap pattern used by spawn/column). registry-driven spawn renderer paints red: Temporary verification marker. With NEXT_PUBLIC_USE_REGISTRY_FOR_ SPAWN=1, spawns rendered via the new path now appear in #ef4444 red. Legacy renderer stays in #22c55e green. Easy visual check for "which dispatch path is this spawn on?" Reverted in the PR that signs off spawn parity (alongside legacy file deletion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b6d77206b4 |
Phase 2 spike: spawn migration (flagged) + new shelf node
The first time registry-driven nodes actually run in the editor. Spawn migration (under NEXT_PUBLIC_USE_REGISTRY_FOR_SPAWN flag): - New packages/nodes/src/spawn/ folder with renderer, tool, schema (re-exported from core), parametrics, definition, index. - Spawn definition appended to builtinPlugin.nodes only when the flag is set. With the flag off, the Phase 0 shims fall through and the legacy SpawnRenderer / SpawnTool keep ownership. - New no-props SpawnTool reads activeLevelId from useViewer directly, matches legacy placement behavior (half-meter snap, singleton-per- level, replace-on-reclick). - Structural parity test (9 cases) validates definition shape + schema identity. Pixel-diff defers to Phase 4 alongside more nodes. New shelf node (no legacy — registered unconditionally): - ShelfNode schema in core/schema/nodes/shelf.ts (hand-maintained AnyNode union for now; Phase 6 derives the union from the registry and moves the schema fully into nodes/shelf/). - packages/nodes/src/shelf/ folder: pure geometry builder (buildShelfGeometry returns a Three.js Group of top board + brackets), R3F renderer that mounts the built group, no-props placement tool, parametrics descriptor (width/depth/thickness/ height/bracketStyle/color), definition with surfaces.top stackable surface for future stacking, and presentation metadata for the palette. - 13 unit tests across schema bounds and geometry behavior. - Palette wiring: 'shelf' added to StructureTool union + an entry in the structure-tools array (placeholder icon, replaced in Phase 4 when palette is registry-driven). Framework changes: - @pascal-app/viewer now exports useNodeEvents from its public barrel so node bundles in @pascal-app/nodes can subscribe to node-specific pointer events. (Used by spawn renderer; shelf renderer skips it for now since useNodeEvents has a hardcoded kind list — Phase 4 generalizes it via the registry.) - @pascal-app/nodes gains @pascal-app/viewer as a peer + dev dep so node bundles can import from it. 630 tests pass across 76 files (22 new this phase). Editor app continues to ship green with both legacy spawn and the new shelf node co-existing through the Phase 0 dispatch shims. To validate end-to-end in dev: - bun dev:community → open editor → click 'Shelf' in structure toolbar → click to place. Confirms full registry path (NodeRenderer dispatch + ToolManager dispatch + sceneRegistry byType Proxy). - Set NEXT_PUBLIC_USE_REGISTRY_FOR_SPAWN=1, restart dev, place spawn → visually identical to legacy. Confirms parity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |