editor: green/red placement box for shelf + tool sync for presets (#366)
* feat(editor): shelf placement validity box + sync tool for positioned presets Give the generic move tool a green/red footprint box for shelf placement, matching the GLB item cursor, and drop the vertical-arrow CursorSphere for shelves. Box colour comes from canPlaceOnFloor; an invalid (red) drop is refused unless Shift forces it, and R/T play the rotate sfx. Re-sync the box transform on node change so a re-armed clone isn't left at the previous rotation/position. Extract the box wireframe geometry helpers into a shared placement-box-geometry module reused by the item coordinator and the new declarative PlacementBox component. Export the Tool type so host apps can set the active tool for a positioned preset. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: apply biome formatting + ignore generated next-env.d.ts Format files that predate the current biome config (lineWidth 100, single quotes, semicolons as-needed) so they stop showing as dirty on every checkout. Formatting only — no behavior change. Also exclude **/next-env.d.ts from biome: Next regenerates it (double quotes + semicolon) on every build, so biome kept reformatting it into a perpetual dirty diff. Ignoring it lets Next own the file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e741ba677a
commit
c3199e7540
@@ -96,7 +96,10 @@ function buildSegPlan(roof: RoofNode, seg: RoofSegmentNode): SegPlan {
|
||||
const rot = -(roof.rotation + seg.rotation)
|
||||
const cos = Math.cos(rot)
|
||||
const sin = Math.sin(rot)
|
||||
const tp = (lx: number, lz: number): Pt => [segCx + lx * cos - lz * sin, segCz + lx * sin + lz * cos]
|
||||
const tp = (lx: number, lz: number): Pt => [
|
||||
segCx + lx * cos - lz * sin,
|
||||
segCz + lx * sin + lz * cos,
|
||||
]
|
||||
const hw = Math.max(seg.width, 0.01) / 2
|
||||
const hd = Math.max(seg.depth, 0.01) / 2
|
||||
const lw = getRoofSegmentPlanLinework(seg)
|
||||
@@ -110,7 +113,10 @@ function buildSegPlan(roof: RoofNode, seg: RoofSegmentNode): SegPlan {
|
||||
hips: lw.hips.map(mapSeg),
|
||||
breaks: lw.breaks.map(mapSeg),
|
||||
slope: lw.slope
|
||||
? { tail: tp(lw.slope.tail[0], lw.slope.tail[1]), head: tp(lw.slope.head[0], lw.slope.head[1]) }
|
||||
? {
|
||||
tail: tp(lw.slope.tail[0], lw.slope.tail[1]),
|
||||
head: tp(lw.slope.head[0], lw.slope.head[1]),
|
||||
}
|
||||
: null,
|
||||
}
|
||||
}
|
||||
@@ -133,13 +139,8 @@ function buildSegPlan(roof: RoofNode, seg: RoofSegmentNode): SegPlan {
|
||||
* group is decorative (`pointerEvents: 'none'`) — clicks fall through to the
|
||||
* segment hit-targets.
|
||||
*/
|
||||
export function buildRoofFloorplan(
|
||||
node: RoofNode,
|
||||
ctx: GeometryContext,
|
||||
): FloorplanGeometry | null {
|
||||
const segments = ctx.children.filter(
|
||||
(c): c is RoofSegmentNode => c.type === 'roof-segment',
|
||||
)
|
||||
export function buildRoofFloorplan(node: RoofNode, ctx: GeometryContext): FloorplanGeometry | null {
|
||||
const segments = ctx.children.filter((c): c is RoofSegmentNode => c.type === 'roof-segment')
|
||||
if (segments.length === 0) return null
|
||||
|
||||
const plans = segments.map((s) => buildSegPlan(node, s))
|
||||
|
||||
Reference in New Issue
Block a user