Reset core/viewer/editor/mcp packages and apps/editor from private-editor
Wholesale swap of packages/{core,viewer,editor,mcp} and apps/editor with the
versions from the private editor repo, which is the production source of truth.
Setup changes:
- packages/{core,viewer,editor} versions held at 0.7.0 baseline (matching
the most recent published release) so a bump=minor publishes 0.8.0
- packages/mcp held at 0.1.1 (never published; first publish will go through
the new release.yml flow)
- peerDependencies and devDependencies for inter-package @pascal-app/*
references pinned to ^0.7.0 instead of '*' / 'workspace:*' so they are
valid for npm consumers
- Root package.json: TypeScript bumped to 6.0.2, added overrides for
@types/react, @types/react-dom, @types/three to prevent JSX namespace
fragmentation across the workspace
- release.yml extended to also publish editor and mcp; 'both' option renamed
to 'all'; added a sync step that updates inter-package peerDeps/devDeps to
match the new versions on every bump (so viewer/editor/mcp tarballs always
reference the version of core they were built against)
- Root scripts gained release:editor and release:mcp shortcuts
Verification:
- bun install --frozen-lockfile is consistent
- packages/{core,viewer,mcp} build cleanly, dist/index.d.ts emitted
- packages/editor check-types reports 21 pre-existing errors, identical to
what private-editor currently reports
Open PRs against editor-v2 will need rebasing/conflict resolution.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useFrame } from '@react-three/fiber'
|
||||
import { type AnyNodeId, type CeilingNode, sceneRegistry, useScene } from '@pascal-app/core'
|
||||
import { useFrame } from '@react-three/fiber'
|
||||
import * as THREE from 'three'
|
||||
|
||||
function ensureUv2Attribute(geometry: THREE.BufferGeometry) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
clampDoorOperationState,
|
||||
type AnyNodeId,
|
||||
clampDoorOperationState,
|
||||
type DoorNode,
|
||||
getDoorRenderOpenAmount,
|
||||
sceneRegistry,
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useThree } from '@react-three/fiber'
|
||||
import { useEffect } from 'react'
|
||||
import type { Scene } from 'three'
|
||||
import * as THREE from 'three'
|
||||
import { STLExporter } from 'three/examples/jsm/exporters/STLExporter.js'
|
||||
import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js'
|
||||
import { OBJExporter } from 'three/examples/jsm/exporters/OBJExporter.js'
|
||||
import useViewer from '../../store/use-viewer'
|
||||
|
||||
const EDITOR_LAYER = 1 // same constant used across the editor
|
||||
|
||||
function downloadBlob(blob: Blob, filename: string) {
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = filename
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
export const ExportSystem = () => {
|
||||
const { scene } = useThree()
|
||||
const setExportScene = useViewer((state) => state.setExportScene)
|
||||
|
||||
useEffect(() => {
|
||||
const exportFn = async (format: 'glb' | 'stl' | 'obj' = 'glb') => {
|
||||
const date = new Date().toISOString().split('T')[0]
|
||||
const filename = `pascal-export-${date}`
|
||||
|
||||
// Clone scene and strip editor-only objects (layer 1 = EDITOR_LAYER)
|
||||
const exportRoot = scene.clone(true) as Scene
|
||||
const toRemove: THREE.Object3D[] = []
|
||||
exportRoot.traverse((obj) => {
|
||||
if (obj.layers.isEnabled(EDITOR_LAYER)) {
|
||||
toRemove.push(obj)
|
||||
}
|
||||
})
|
||||
for (const obj of toRemove) {
|
||||
obj.parent?.remove(obj)
|
||||
}
|
||||
|
||||
if (format === 'glb') {
|
||||
const exporter = new GLTFExporter()
|
||||
const result = await new Promise<ArrayBuffer>((resolve, reject) => {
|
||||
exporter.parse(
|
||||
exportRoot,
|
||||
(output) => resolve(output as ArrayBuffer),
|
||||
(err) => reject(err),
|
||||
{ binary: true }
|
||||
)
|
||||
})
|
||||
downloadBlob(new Blob([result], { type: 'model/gltf-binary' }), `${filename}.glb`)
|
||||
} else if (format === 'stl') {
|
||||
const exporter = new STLExporter()
|
||||
const result = exporter.parse(exportRoot, { binary: true }) as DataView
|
||||
downloadBlob(new Blob([result.buffer as ArrayBuffer], { type: 'model/stl' }), `${filename}.stl`)
|
||||
} else if (format === 'obj') {
|
||||
const exporter = new OBJExporter()
|
||||
const result = exporter.parse(exportRoot)
|
||||
downloadBlob(new Blob([result], { type: 'model/obj' }), `${filename}.obj`)
|
||||
}
|
||||
}
|
||||
|
||||
setExportScene(exportFn)
|
||||
return () => setExportScene(null)
|
||||
}, [scene, setExportScene])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useFrame } from '@react-three/fiber'
|
||||
import {
|
||||
type AnyNodeId,
|
||||
type FenceNode,
|
||||
@@ -7,6 +6,7 @@ import {
|
||||
sceneRegistry,
|
||||
useScene,
|
||||
} from '@pascal-app/core'
|
||||
import { useFrame } from '@react-three/fiber'
|
||||
import * as THREE from 'three'
|
||||
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
||||
|
||||
@@ -69,7 +69,9 @@ function createFenceCurveSpanParts(
|
||||
const parts: FencePart[] = []
|
||||
const frameCount = Math.max(
|
||||
1,
|
||||
Math.ceil((getWallCurveLength(fence) * Math.max(1e-4, endT - startT)) / MIN_CURVE_SEGMENT_LENGTH),
|
||||
Math.ceil(
|
||||
(getWallCurveLength(fence) * Math.max(1e-4, endT - startT)) / MIN_CURVE_SEGMENT_LENGTH,
|
||||
),
|
||||
)
|
||||
|
||||
let previous = getFencePointAt(fence, startT)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { emitter, sceneRegistry } from '@pascal-app/core'
|
||||
import { sceneRegistry } from '@pascal-app/core'
|
||||
import { useEffect } from 'react'
|
||||
import useViewer from '../../store/use-viewer'
|
||||
|
||||
@@ -15,29 +15,5 @@ export const GuideSystem = () => {
|
||||
})
|
||||
}, [showGuides])
|
||||
|
||||
useEffect(() => {
|
||||
const hideForCapture = () => {
|
||||
const guides = sceneRegistry.byType.guide || new Set()
|
||||
guides.forEach((guideId) => {
|
||||
const node = sceneRegistry.nodes.get(guideId)
|
||||
if (node) node.visible = false
|
||||
})
|
||||
}
|
||||
const restoreAfterCapture = () => {
|
||||
const showGuidesNow = useViewer.getState().showGuides
|
||||
const guides = sceneRegistry.byType.guide || new Set()
|
||||
guides.forEach((guideId) => {
|
||||
const node = sceneRegistry.nodes.get(guideId)
|
||||
if (node) node.visible = showGuidesNow
|
||||
})
|
||||
}
|
||||
emitter.on('thumbnail:before-capture', hideForCapture)
|
||||
emitter.on('thumbnail:after-capture', restoreAfterCapture)
|
||||
return () => {
|
||||
emitter.off('thumbnail:before-capture', hideForCapture)
|
||||
emitter.off('thumbnail:after-capture', restoreAfterCapture)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useFrame } from '@react-three/fiber'
|
||||
import {
|
||||
type AnyNodeId,
|
||||
getScaledDimensions,
|
||||
@@ -9,6 +8,7 @@ import {
|
||||
useScene,
|
||||
type WallNode,
|
||||
} from '@pascal-app/core'
|
||||
import { useFrame } from '@react-three/fiber'
|
||||
import type * as THREE from 'three'
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useFrame } from '@react-three/fiber'
|
||||
import {
|
||||
type AnyNode,
|
||||
type AnyNodeId,
|
||||
@@ -8,11 +7,21 @@ import {
|
||||
sceneRegistry,
|
||||
useScene,
|
||||
} from '@pascal-app/core'
|
||||
import { useFrame } from '@react-three/fiber'
|
||||
import * as THREE from 'three'
|
||||
import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
||||
import { ADDITION, Brush, Evaluator, SUBTRACTION } from 'three-bvh-csg'
|
||||
import { computeBoundsTree } from 'three-mesh-bvh'
|
||||
|
||||
function csgGeometry(brush: Brush): THREE.BufferGeometry {
|
||||
return brush.geometry as unknown as THREE.BufferGeometry
|
||||
}
|
||||
|
||||
function csgMaterials(brush: Brush): THREE.Material[] {
|
||||
const mat = (brush as any).material
|
||||
return Array.isArray(mat) ? mat : [mat]
|
||||
}
|
||||
|
||||
const csgEvaluator = new Evaluator()
|
||||
csgEvaluator.useGroups = true
|
||||
;(csgEvaluator as any).consolidateGroups = false // shared dummyMats across brushes causes consolidation to misalign groupIndices vs groupOrder indices → crash
|
||||
@@ -183,7 +192,7 @@ function updateMergedRoofGeometry(
|
||||
)
|
||||
|
||||
const applyTransform = (brush: Brush) => {
|
||||
brush.geometry.applyMatrix4(_matrix)
|
||||
csgGeometry(brush).applyMatrix4(_matrix)
|
||||
brush.updateMatrixWorld()
|
||||
}
|
||||
|
||||
@@ -242,11 +251,9 @@ function updateMergedRoofGeometry(
|
||||
const shinDeck = csgEvaluator.evaluate(finalShinTrimmed, finalDeckTrimmed, ADDITION)
|
||||
const combined = csgEvaluator.evaluate(shinDeck, finalWallTrimmed, ADDITION)
|
||||
|
||||
const resultGeo = combined.geometry
|
||||
const resultGeo = csgGeometry(combined)
|
||||
|
||||
const resultMaterials: THREE.Material[] = Array.isArray(combined.material)
|
||||
? combined.material
|
||||
: [combined.material]
|
||||
const resultMaterials = csgMaterials(combined)
|
||||
|
||||
const matToIndex = new Map<THREE.Material, number>([
|
||||
[dummyMats[0], 0],
|
||||
@@ -259,8 +266,8 @@ function updateMergedRoofGeometry(
|
||||
g.materialIndex = mapRoofGroupMaterialIndex(g.materialIndex, resultMaterials, matToIndex)
|
||||
}
|
||||
|
||||
ensureUv2Attribute(resultGeo)
|
||||
resultGeo.computeVertexNormals()
|
||||
ensureUv2Attribute(resultGeo)
|
||||
mergedMesh.geometry.dispose()
|
||||
mergedMesh.geometry = resultGeo
|
||||
|
||||
@@ -614,11 +621,9 @@ export function generateRoofSegmentGeometry(node: RoofSegmentNode): THREE.Buffer
|
||||
const shinDeck = csgEvaluator.evaluate(shinSlab, deckSlab, ADDITION)
|
||||
const combined = csgEvaluator.evaluate(shinDeck, hollowWall, ADDITION)
|
||||
|
||||
resultGeo = combined.geometry
|
||||
resultGeo = csgGeometry(combined)
|
||||
|
||||
const resultMaterials: THREE.Material[] = Array.isArray(combined.material)
|
||||
? combined.material
|
||||
: [combined.material]
|
||||
const resultMaterials = csgMaterials(combined)
|
||||
|
||||
const matToIndex = new Map<THREE.Material, number>([
|
||||
[dummyMats[0], 0],
|
||||
@@ -641,7 +646,7 @@ export function generateRoofSegmentGeometry(node: RoofSegmentNode): THREE.Buffer
|
||||
shinDeck.geometry.dispose()
|
||||
} catch (e) {
|
||||
console.error('Roof CSG failed:', e)
|
||||
resultGeo = wallBrush.geometry.clone()
|
||||
resultGeo = csgGeometry(wallBrush).clone()
|
||||
}
|
||||
|
||||
deckSlab.geometry.dispose()
|
||||
@@ -649,8 +654,8 @@ export function generateRoofSegmentGeometry(node: RoofSegmentNode): THREE.Buffer
|
||||
wallBrush.geometry.dispose()
|
||||
innerBrush.geometry.dispose()
|
||||
|
||||
ensureUv2Attribute(resultGeo)
|
||||
resultGeo.computeVertexNormals()
|
||||
ensureUv2Attribute(resultGeo)
|
||||
return resultGeo
|
||||
}
|
||||
|
||||
@@ -1014,8 +1019,8 @@ function createGeometryFromFaces(
|
||||
// Merge identical vertices to optimize geometry for CSG and create clean topology
|
||||
const mergedGeo = mergeVertices(geometry, 1e-4)
|
||||
geometry.dispose()
|
||||
ensureUv2Attribute(mergedGeo)
|
||||
|
||||
ensureUv2Attribute(mergedGeo)
|
||||
return mergedGeo
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { emitter, sceneRegistry } from '@pascal-app/core'
|
||||
import { sceneRegistry } from '@pascal-app/core'
|
||||
import { useEffect } from 'react'
|
||||
import useViewer from '../../store/use-viewer'
|
||||
|
||||
@@ -15,29 +15,5 @@ export const ScanSystem = () => {
|
||||
})
|
||||
}, [showScans])
|
||||
|
||||
useEffect(() => {
|
||||
const hideForCapture = () => {
|
||||
const scans = sceneRegistry.byType.scan || new Set()
|
||||
scans.forEach((scanId) => {
|
||||
const node = sceneRegistry.nodes.get(scanId)
|
||||
if (node) node.visible = false
|
||||
})
|
||||
}
|
||||
const restoreAfterCapture = () => {
|
||||
const showScansNow = useViewer.getState().showScans
|
||||
const scans = sceneRegistry.byType.scan || new Set()
|
||||
scans.forEach((scanId) => {
|
||||
const node = sceneRegistry.nodes.get(scanId)
|
||||
if (node) node.visible = showScansNow
|
||||
})
|
||||
}
|
||||
emitter.on('thumbnail:before-capture', hideForCapture)
|
||||
emitter.on('thumbnail:after-capture', restoreAfterCapture)
|
||||
return () => {
|
||||
emitter.off('thumbnail:before-capture', hideForCapture)
|
||||
emitter.off('thumbnail:after-capture', restoreAfterCapture)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useFrame } from '@react-three/fiber'
|
||||
import {
|
||||
type AnyNodeId,
|
||||
getRenderableSlabPolygon,
|
||||
sceneRegistry,
|
||||
type SlabNode,
|
||||
sceneRegistry,
|
||||
useScene,
|
||||
} from '@pascal-app/core'
|
||||
import { useFrame } from '@react-three/fiber'
|
||||
import * as THREE from 'three'
|
||||
|
||||
function ensureUv2Attribute(geometry: THREE.BufferGeometry) {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { useFrame } from '@react-three/fiber'
|
||||
import {
|
||||
type AnyNode,
|
||||
type AnyNodeId,
|
||||
resolveLevelId,
|
||||
sceneRegistry,
|
||||
spatialGridManager,
|
||||
type StairNode,
|
||||
type StairSegmentNode,
|
||||
sceneRegistry,
|
||||
spatialGridManager,
|
||||
syncAutoStairOpenings,
|
||||
useScene,
|
||||
} from '@pascal-app/core'
|
||||
import { useFrame } from '@react-three/fiber'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import * as THREE from 'three'
|
||||
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
||||
@@ -459,7 +459,7 @@ function applyStairSegmentUvs(geometry: THREE.BufferGeometry) {
|
||||
const position = geometry.getAttribute('position')
|
||||
const normal = geometry.getAttribute('normal')
|
||||
|
||||
if (!position || !normal || position.count === 0) {
|
||||
if (!(position && normal) || position.count === 0) {
|
||||
geometry.deleteAttribute('uv')
|
||||
return
|
||||
}
|
||||
@@ -609,7 +609,13 @@ function generateStairRailingGeometry(
|
||||
const landingInset = 0.08
|
||||
const geometries: THREE.BufferGeometry[] = []
|
||||
|
||||
const segmentRailPaths = buildStairRailPaths(segments, transforms, railingMode, inset, landingInset)
|
||||
const segmentRailPaths = buildStairRailPaths(
|
||||
segments,
|
||||
transforms,
|
||||
railingMode,
|
||||
inset,
|
||||
landingInset,
|
||||
)
|
||||
|
||||
for (const segmentRailPath of segmentRailPaths) {
|
||||
for (const sidePath of segmentRailPath.sidePaths) {
|
||||
@@ -618,9 +624,7 @@ function generateStairRailingGeometry(
|
||||
|
||||
geometries.push(...buildBalusterGeometries(points, railHeight, postRadius))
|
||||
geometries.push(...buildOffsetRailSegmentGeometries(points, railHeight, railRadius))
|
||||
geometries.push(
|
||||
...buildOffsetRailSegmentGeometries(points, midRailHeight, railRadius * 0.8),
|
||||
)
|
||||
geometries.push(...buildOffsetRailSegmentGeometries(points, midRailHeight, railRadius * 0.8))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,42 +699,34 @@ function buildStairRailPaths(
|
||||
previousSegment?.segmentType === 'stair' &&
|
||||
nextSegment?.segmentType === 'stair'
|
||||
const visualTurnSide = nextSegment?.attachmentSide
|
||||
const sideCandidates =
|
||||
hideLandingRailing
|
||||
? visualTurnSide === 'left'
|
||||
? (['front', 'right'] as const)
|
||||
: visualTurnSide === 'right'
|
||||
? (['front', 'left'] as const)
|
||||
: (['left', 'right'] as const)
|
||||
: segment.segmentType === 'landing'
|
||||
? nextSegment?.segmentType === 'landing' && visualTurnSide === 'left'
|
||||
? (['front', 'right'] as const)
|
||||
: nextSegment?.segmentType === 'landing' && visualTurnSide === 'right'
|
||||
? (['front', 'left'] as const)
|
||||
: visualTurnSide === 'left'
|
||||
? (['right'] as const)
|
||||
: visualTurnSide === 'right'
|
||||
? (['left'] as const)
|
||||
: (['left', 'right'] as const)
|
||||
const sideCandidates = hideLandingRailing
|
||||
? visualTurnSide === 'left'
|
||||
? (['front', 'right'] as const)
|
||||
: visualTurnSide === 'right'
|
||||
? (['front', 'left'] as const)
|
||||
: (['left', 'right'] as const)
|
||||
: segment.segmentType === 'landing'
|
||||
? nextSegment?.segmentType === 'landing' && visualTurnSide === 'left'
|
||||
? (['front', 'right'] as const)
|
||||
: nextSegment?.segmentType === 'landing' && visualTurnSide === 'right'
|
||||
? (['front', 'left'] as const)
|
||||
: visualTurnSide === 'left'
|
||||
? (['right'] as const)
|
||||
: visualTurnSide === 'right'
|
||||
? (['left'] as const)
|
||||
: (['left', 'right'] as const)
|
||||
: (['left', 'right'] as const)
|
||||
const sidePaths = sideCandidates
|
||||
.map((side) =>
|
||||
buildSegmentRailPath(
|
||||
layout,
|
||||
side,
|
||||
previousSegment,
|
||||
nextSegment,
|
||||
inset,
|
||||
landingInset,
|
||||
),
|
||||
buildSegmentRailPath(layout, side, previousSegment, nextSegment, inset, landingInset),
|
||||
)
|
||||
.filter((entry): entry is StairRailSidePath => entry !== null)
|
||||
|
||||
return {
|
||||
segment,
|
||||
sidePaths:
|
||||
sidePaths:
|
||||
isStraightLineDoubleLandingLayout && index === 1
|
||||
? ((['left', 'right'] as const)
|
||||
? (['left', 'right'] as const)
|
||||
.map((side) =>
|
||||
buildSegmentRailPath(
|
||||
layout,
|
||||
@@ -741,7 +737,7 @@ function buildStairRailPaths(
|
||||
landingInset,
|
||||
),
|
||||
)
|
||||
.filter((entry): entry is StairRailSidePath => entry !== null))
|
||||
.filter((entry): entry is StairRailSidePath => entry !== null)
|
||||
: sidePaths,
|
||||
connectFromPrevious:
|
||||
index > 0 &&
|
||||
@@ -780,10 +776,20 @@ function buildStairRailPaths(
|
||||
nextAttachmentSide === railingMode
|
||||
: true
|
||||
|
||||
const sidePaths =
|
||||
suppressLandingRailing
|
||||
? []
|
||||
: segment.segmentType !== 'landing'
|
||||
const sidePaths = suppressLandingRailing
|
||||
? []
|
||||
: segment.segmentType !== 'landing'
|
||||
? [
|
||||
buildSegmentRailPath(
|
||||
layout,
|
||||
railingMode,
|
||||
previousSegment,
|
||||
nextSegment,
|
||||
inset,
|
||||
landingInset,
|
||||
),
|
||||
]
|
||||
: isStraightLineDoubleLandingLayout
|
||||
? [
|
||||
buildSegmentRailPath(
|
||||
layout,
|
||||
@@ -794,19 +800,29 @@ function buildStairRailPaths(
|
||||
landingInset,
|
||||
),
|
||||
]
|
||||
: isStraightLineDoubleLandingLayout
|
||||
? [
|
||||
buildSegmentRailPath(
|
||||
layout,
|
||||
railingMode,
|
||||
previousSegment,
|
||||
nextSegment,
|
||||
inset,
|
||||
landingInset,
|
||||
),
|
||||
]
|
||||
: isMiddleLandingBetweenFlights && railingMode === 'left'
|
||||
? nextAttachmentSide === 'right'
|
||||
: isMiddleLandingBetweenFlights && railingMode === 'left'
|
||||
? nextAttachmentSide === 'right'
|
||||
? [
|
||||
buildSegmentRailPath(
|
||||
layout,
|
||||
'front',
|
||||
previousSegment,
|
||||
nextSegment,
|
||||
inset,
|
||||
landingInset,
|
||||
),
|
||||
buildSegmentRailPath(
|
||||
layout,
|
||||
'left',
|
||||
previousSegment,
|
||||
nextSegment,
|
||||
inset,
|
||||
landingInset,
|
||||
),
|
||||
]
|
||||
: []
|
||||
: isMiddleLandingBetweenFlights && railingMode === 'right'
|
||||
? nextAttachmentSide === 'left'
|
||||
? [
|
||||
buildSegmentRailPath(
|
||||
layout,
|
||||
@@ -818,7 +834,7 @@ function buildStairRailPaths(
|
||||
),
|
||||
buildSegmentRailPath(
|
||||
layout,
|
||||
'left',
|
||||
'right',
|
||||
previousSegment,
|
||||
nextSegment,
|
||||
inset,
|
||||
@@ -826,59 +842,38 @@ function buildStairRailPaths(
|
||||
),
|
||||
]
|
||||
: []
|
||||
: isMiddleLandingBetweenFlights && railingMode === 'right'
|
||||
? nextAttachmentSide === 'left'
|
||||
? [
|
||||
buildSegmentRailPath(
|
||||
layout,
|
||||
'front',
|
||||
previousSegment,
|
||||
nextSegment,
|
||||
inset,
|
||||
landingInset,
|
||||
),
|
||||
buildSegmentRailPath(
|
||||
layout,
|
||||
'right',
|
||||
previousSegment,
|
||||
nextSegment,
|
||||
inset,
|
||||
landingInset,
|
||||
),
|
||||
]
|
||||
: []
|
||||
: nextSegment?.segmentType === 'landing' &&
|
||||
nextAttachmentSide != null &&
|
||||
nextAttachmentSide !== 'front' &&
|
||||
nextAttachmentSide !== railingMode
|
||||
? [
|
||||
buildSegmentRailPath(
|
||||
layout,
|
||||
'front',
|
||||
previousSegment,
|
||||
nextSegment,
|
||||
inset,
|
||||
landingInset,
|
||||
),
|
||||
buildSegmentRailPath(
|
||||
layout,
|
||||
railingMode,
|
||||
previousSegment,
|
||||
nextSegment,
|
||||
inset,
|
||||
landingInset,
|
||||
),
|
||||
]
|
||||
: [
|
||||
buildSegmentRailPath(
|
||||
layout,
|
||||
railingMode,
|
||||
previousSegment,
|
||||
nextSegment,
|
||||
inset,
|
||||
landingInset,
|
||||
),
|
||||
]
|
||||
: nextSegment?.segmentType === 'landing' &&
|
||||
nextAttachmentSide != null &&
|
||||
nextAttachmentSide !== 'front' &&
|
||||
nextAttachmentSide !== railingMode
|
||||
? [
|
||||
buildSegmentRailPath(
|
||||
layout,
|
||||
'front',
|
||||
previousSegment,
|
||||
nextSegment,
|
||||
inset,
|
||||
landingInset,
|
||||
),
|
||||
buildSegmentRailPath(
|
||||
layout,
|
||||
railingMode,
|
||||
previousSegment,
|
||||
nextSegment,
|
||||
inset,
|
||||
landingInset,
|
||||
),
|
||||
]
|
||||
: [
|
||||
buildSegmentRailPath(
|
||||
layout,
|
||||
railingMode,
|
||||
previousSegment,
|
||||
nextSegment,
|
||||
inset,
|
||||
landingInset,
|
||||
),
|
||||
]
|
||||
|
||||
resolved.push({
|
||||
segment,
|
||||
@@ -924,10 +919,11 @@ function buildSegmentRailPath(
|
||||
const segmentStepDepth = segment.length / segmentSteps
|
||||
const segmentStepHeight = segment.segmentType === 'landing' ? 0 : segment.height / segmentSteps
|
||||
const segmentTopThickness = getSegmentTopThickness(segment)
|
||||
const flightSideOffset =
|
||||
side === 'left' ? segment.width / 2 - 0.045 : -segment.width / 2 + 0.045
|
||||
const flightSideOffset = side === 'left' ? segment.width / 2 - 0.045 : -segment.width / 2 + 0.045
|
||||
const flightStartX =
|
||||
previousSegment?.segmentType === 'landing' ? -segment.length / 2 + landingInset : -segment.length / 2
|
||||
previousSegment?.segmentType === 'landing'
|
||||
? -segment.length / 2 + landingInset
|
||||
: -segment.length / 2
|
||||
const flightEndX =
|
||||
nextSegment?.segmentType === 'landing' ? segment.length / 2 - landingInset : segment.length / 2
|
||||
|
||||
@@ -947,9 +943,7 @@ function buildSegmentRailPath(
|
||||
points: [
|
||||
...(previousSegment?.segmentType === 'landing'
|
||||
? []
|
||||
: [
|
||||
toRailLayoutWorldPoint(layout, flightStartX, segmentTopThickness, flightSideOffset),
|
||||
]),
|
||||
: [toRailLayoutWorldPoint(layout, flightStartX, segmentTopThickness, flightSideOffset)]),
|
||||
...Array.from({ length: segmentSteps }).map((_, index) =>
|
||||
toRailLayoutWorldPoint(
|
||||
layout,
|
||||
@@ -960,9 +954,7 @@ function buildSegmentRailPath(
|
||||
),
|
||||
...(nextSegment?.segmentType === 'landing'
|
||||
? []
|
||||
: [
|
||||
toRailLayoutWorldPoint(layout, flightEndX, segment.height, flightSideOffset),
|
||||
]),
|
||||
: [toRailLayoutWorldPoint(layout, flightEndX, segment.height, flightSideOffset)]),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,10 @@ function computeGeometryBoundsTree(geometry: THREE.BufferGeometry) {
|
||||
;(geometry as any).computeBoundsTree({ maxLeafSize: 10 })
|
||||
}
|
||||
|
||||
function csgGeometry(brush: Brush): THREE.BufferGeometry {
|
||||
return brush.geometry as unknown as THREE.BufferGeometry
|
||||
}
|
||||
|
||||
type WallBoundaryEdgeTag = 'front' | 'back' | 'base'
|
||||
|
||||
type TaggedWallBoundaryEdge = {
|
||||
@@ -57,7 +61,7 @@ function insetCurvedWallBoundaryPointsFor3D(
|
||||
boundaryPoints: ReturnType<typeof getWallMiterBoundaryPoints>,
|
||||
miterData: WallMiterData,
|
||||
) {
|
||||
if (!boundaryPoints || !isCurvedWall(wall)) {
|
||||
if (!(boundaryPoints && isCurvedWall(wall))) {
|
||||
return boundaryPoints
|
||||
}
|
||||
|
||||
@@ -431,7 +435,7 @@ export function generateExtrudedWall(
|
||||
childrenNodes: AnyNode[],
|
||||
miterData: WallMiterData,
|
||||
slabElevation = 0,
|
||||
) {
|
||||
): THREE.BufferGeometry {
|
||||
const wallStart: Point2D = { x: wallNode.start[0], y: wallNode.start[1] }
|
||||
const wallEnd: Point2D = { x: wallNode.end[0], y: wallNode.end[1] }
|
||||
// Positive slab: shift the whole wall up (full height preserved)
|
||||
@@ -519,18 +523,18 @@ export function generateExtrudedWall(
|
||||
cutoutBrush.updateMatrixWorld()
|
||||
const newResult = csgEvaluator.evaluate(resultBrush, cutoutBrush, SUBTRACTION)
|
||||
if (resultBrush !== wallBrush) {
|
||||
resultBrush.geometry.dispose()
|
||||
csgGeometry(resultBrush).dispose()
|
||||
}
|
||||
resultBrush = newResult
|
||||
}
|
||||
|
||||
// Clean up
|
||||
wallBrush.geometry.dispose()
|
||||
csgGeometry(wallBrush).dispose()
|
||||
for (const brush of cutoutBrushes) {
|
||||
brush.geometry.dispose()
|
||||
csgGeometry(brush).dispose()
|
||||
}
|
||||
|
||||
const resultGeometry = resultBrush.geometry
|
||||
const resultGeometry = csgGeometry(resultBrush)
|
||||
resultGeometry.computeVertexNormals()
|
||||
assignWallMaterialGroups(resultGeometry, wallNode, boundaryEdges)
|
||||
ensureUv2Attribute(resultGeometry)
|
||||
|
||||
Reference in New Issue
Block a user