@pascal-app/editor 1.0.0-beta.1 → 1.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pascal-app/editor",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.2",
4
4
  "description": "Pascal building editor component",
5
5
  "type": "module",
6
6
  "exports": {
@@ -11,8 +11,8 @@
11
11
  "check-types": "tsgo --noEmit"
12
12
  },
13
13
  "peerDependencies": {
14
- "@pascal-app/core": "^1.0.0-beta.1",
15
- "@pascal-app/viewer": "^1.0.0-beta.1",
14
+ "@pascal-app/core": "^1.0.0-beta.2",
15
+ "@pascal-app/viewer": "^1.0.0-beta.2",
16
16
  "@react-three/drei": "^10",
17
17
  "@react-three/fiber": "^9",
18
18
  "next": ">=15",
@@ -56,8 +56,8 @@
56
56
  "zustand": "^5.0.11"
57
57
  },
58
58
  "devDependencies": {
59
- "@pascal-app/core": "^1.0.0-beta.1",
60
- "@pascal-app/viewer": "^1.0.0-beta.1",
59
+ "@pascal-app/core": "^1.0.0-beta.2",
60
+ "@pascal-app/viewer": "^1.0.0-beta.2",
61
61
  "@pascal/typescript-config": "*",
62
62
  "@types/blob-stream": "^0.1.33",
63
63
  "@types/bun": "^1.3.0",
@@ -1,8 +1,10 @@
1
1
  import {
2
2
  type AnyNode,
3
3
  type AnyNodeId,
4
+ findLevelAncestorId,
4
5
  getWallBaseElevationForNodes,
5
6
  getWallEffectiveHeightForNodes,
7
+ levelBaseElevationAt,
6
8
  resolveCeilingHeight,
7
9
  } from '@pascal-app/core'
8
10
  import useElevationGuides from '../store/use-elevation-guides'
@@ -46,14 +48,22 @@ function segmentCenter(
46
48
  return [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2]
47
49
  }
48
50
 
51
+ // Mirrors `resolveFenceLiftElevationForNodes` in `@pascal-app/nodes`, which this
52
+ // package cannot import (the fence definition imports the guides from here).
53
+ // A stale host resolves to the level base — the sculpted ground under the
54
+ // fence's start point, sampled where the builder samples it, so the guide line
55
+ // lands on the rail it claims to describe.
49
56
  function fenceBaseElevation(node: AnyNode, nodes: Record<string, AnyNode>): number {
50
57
  if (node.type !== 'fence') return 0
51
58
  const host = node.supportSlabId ? nodes[node.supportSlabId as AnyNodeId] : undefined
52
- const hostElevation =
53
- host?.type === 'slab' && (host.parentId ?? null) === (node.parentId ?? null)
54
- ? (host.elevation ?? 0)
59
+ const hosted = host?.type === 'slab' && (host.parentId ?? null) === (node.parentId ?? null)
60
+ const levelId = findLevelAncestorId(node.id as AnyNodeId, nodes)
61
+ const support = hosted
62
+ ? (host.elevation ?? 0)
63
+ : levelId
64
+ ? levelBaseElevationAt(nodes, levelId, node.start[0], node.start[1])
55
65
  : 0
56
- return hostElevation + (node.supportOffset ?? 0)
66
+ return support + (node.supportOffset ?? 0)
57
67
  }
58
68
 
59
69
  /**
@@ -75,6 +85,25 @@ export function collectElevationSnapTargets(
75
85
  label: 'Level',
76
86
  },
77
87
  ]
88
+ // Sculpted ground under the thing being dragged. A separate target rather than
89
+ // a redefinition of `Level`: the storey plane is still a real datum a user may
90
+ // want (a fence sunk to the building's floor line), and on a hillside the
91
+ // ground is a second, different one. Emitted only when they actually differ,
92
+ // so a flat scene keeps exactly one target at 0.
93
+ const groundElevation = levelBaseElevationAt(
94
+ nodes,
95
+ source.levelId,
96
+ source.anchor[0],
97
+ source.anchor[1],
98
+ )
99
+ if (Math.abs(groundElevation) > GUIDE_MATCH_EPSILON_M) {
100
+ targets.push({
101
+ id: `${source.levelId}:ground`,
102
+ elevation: groundElevation,
103
+ anchor: source.anchor,
104
+ label: 'Ground',
105
+ })
106
+ }
78
107
  const level = nodes[source.levelId as AnyNodeId]
79
108
  if (level?.type !== 'level') return targets
80
109