@pascal-app/core 1.1.0 → 1.1.1
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/dist/agent-operations/__fixtures__/cases.d.ts +3 -2
- package/dist/agent-operations/__fixtures__/cases.d.ts.map +1 -1
- package/dist/agent-operations/__fixtures__/place-items-cases.d.ts.map +1 -1
- package/dist/agent-operations/__fixtures__/place-items-cases.js +28 -0
- package/dist/agent-operations/__fixtures__/roof-bearing-cases.d.ts +12209 -12209
- package/dist/agent-operations/asset-catalog.d.ts +21 -11
- package/dist/agent-operations/asset-catalog.d.ts.map +1 -1
- package/dist/agent-operations/asset-catalog.js +28 -10
- package/dist/agent-operations/asset-views.d.ts +8 -3
- package/dist/agent-operations/asset-views.d.ts.map +1 -1
- package/dist/agent-operations/asset-views.js +19 -3
- package/dist/agent-operations/batch.d.ts +7 -1
- package/dist/agent-operations/batch.d.ts.map +1 -1
- package/dist/agent-operations/batch.js +17 -2
- package/dist/agent-operations/catalog-asset.d.ts +15 -0
- package/dist/agent-operations/catalog-asset.d.ts.map +1 -0
- package/dist/agent-operations/catalog-asset.js +4 -0
- package/dist/agent-operations/circulation-spaces.d.ts +2963 -2963
- package/dist/agent-operations/design-brief.d.ts.map +1 -1
- package/dist/agent-operations/design-brief.js +2 -0
- package/dist/agent-operations/furnish-room.d.ts.map +1 -1
- package/dist/agent-operations/furnish-room.js +2 -1
- package/dist/agent-operations/index.d.ts +3 -0
- package/dist/agent-operations/index.d.ts.map +1 -1
- package/dist/agent-operations/index.js +4 -1
- package/dist/agent-operations/level-reads.d.ts.map +1 -1
- package/dist/agent-operations/level-reads.js +4 -1
- package/dist/agent-operations/place-in-room.d.ts.map +1 -1
- package/dist/agent-operations/place-in-room.js +2 -1
- package/dist/agent-operations/place-items.d.ts.map +1 -1
- package/dist/agent-operations/place-items.js +2 -1
- package/dist/agent-operations/scene-queries.d.ts +585 -585
- package/dist/agent-operations/scene-view.d.ts.map +1 -1
- package/dist/agent-operations/scene-view.js +38 -13
- package/dist/agent-operations/search-assets.d.ts.map +1 -1
- package/dist/agent-operations/search-assets.js +6 -0
- package/dist/agent-operations/types.d.ts +3 -2
- package/dist/agent-operations/types.d.ts.map +1 -1
- package/dist/agent-operations/usage-checks.js +18 -3
- package/dist/agent-tools/assets.js +1 -1
- package/dist/building/__fixtures__/wall-opening-cases.d.ts +234 -234
- package/dist/building/level-duplication.d.ts +3516 -3516
- package/dist/building/level-duplication.d.ts.map +1 -1
- package/dist/building/level-duplication.js +3 -1
- package/dist/building/wall-openings.d.ts +75 -75
- package/dist/commands/structure/shared.d.ts +3407 -3407
- package/dist/commands/structure/zone-wall-merge.d.ts +566 -566
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +585 -585
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/lib/__fixtures__/mezzanine.d.ts +9262 -9262
- package/dist/lib/absorb-wall-separators.d.ts +1344 -1344
- package/dist/lib/asset-storage.d.ts.map +1 -1
- package/dist/lib/asset-storage.js +3 -0
- package/dist/lib/floor-foundation-datum.d.ts.map +1 -1
- package/dist/lib/floor-foundation-datum.js +5 -1
- package/dist/lib/floorplan/context.d.ts +95 -0
- package/dist/lib/floorplan/context.d.ts.map +1 -0
- package/dist/lib/floorplan/context.js +155 -0
- package/dist/lib/floorplan/index.d.ts +6 -0
- package/dist/lib/floorplan/index.d.ts.map +1 -0
- package/dist/lib/floorplan/index.js +5 -0
- package/dist/lib/floorplan/marks.d.ts +71 -0
- package/dist/lib/floorplan/marks.d.ts.map +1 -0
- package/dist/lib/floorplan/marks.js +157 -0
- package/dist/lib/floorplan/plan-math.d.ts +18 -0
- package/dist/lib/floorplan/plan-math.d.ts.map +1 -0
- package/dist/lib/floorplan/plan-math.js +82 -0
- package/dist/lib/floorplan/svg-paths.d.ts +17 -0
- package/dist/lib/floorplan/svg-paths.d.ts.map +1 -0
- package/dist/lib/floorplan/svg-paths.js +92 -0
- package/dist/lib/floorplan/wall-affordances.d.ts +31 -0
- package/dist/lib/floorplan/wall-affordances.d.ts.map +1 -0
- package/dist/lib/floorplan/wall-affordances.js +24 -0
- package/dist/registry/__fixtures__/roof-planes.d.ts +69 -0
- package/dist/registry/__fixtures__/roof-planes.d.ts.map +1 -0
- package/dist/registry/__fixtures__/roof-planes.js +59 -0
- package/dist/registry/index.d.ts +2 -1
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +1 -0
- package/dist/registry/roofs.d.ts +34 -0
- package/dist/registry/roofs.d.ts.map +1 -0
- package/dist/registry/roofs.js +61 -0
- package/dist/registry/types.d.ts +30 -0
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry/validate-relations.d.ts +5 -1
- package/dist/registry/validate-relations.d.ts.map +1 -1
- package/dist/registry/validate-relations.js +6 -2
- package/dist/schema/asset-url.d.ts +8 -0
- package/dist/schema/asset-url.d.ts.map +1 -1
- package/dist/schema/asset-url.js +17 -0
- package/dist/schema/nodes/roof.d.ts +1 -1
- package/dist/schema/nodes/roof.d.ts.map +1 -1
- package/dist/schema/nodes/roof.js +13 -8
- package/dist/schema/types.d.ts +1 -1
- package/dist/services/roof-level.d.ts.map +1 -1
- package/dist/services/roof-level.js +12 -2
- package/dist/services/roof-move.d.ts +4 -2
- package/dist/services/roof-move.d.ts.map +1 -1
- package/dist/services/roof-move.js +21 -9
- package/dist/spatial/usage-field/findings.d.ts +7 -1
- package/dist/spatial/usage-field/findings.d.ts.map +1 -1
- package/dist/spatial/usage-field/findings.js +42 -10
- package/dist/spatial/usage-field/path.d.ts.map +1 -1
- package/dist/spatial/usage-field/path.js +28 -18
- package/dist/store/actions/node-actions.d.ts +1641 -1641
- package/dist/store/scene-annotations.d.ts +1778 -1778
- package/dist/systems/document-reconcile.d.ts.map +1 -1
- package/dist/systems/document-reconcile.js +1 -2
- package/dist/systems/roof/roof-elevation.d.ts +11 -6
- package/dist/systems/roof/roof-elevation.d.ts.map +1 -1
- package/dist/systems/roof/roof-elevation.js +24 -11
- package/dist/systems/slab/__fixtures__/floor-step.d.ts +1622 -1622
- package/dist/systems/slab/__fixtures__/raised-room.d.ts +585 -585
- package/dist/systems/stair/stair-opening-preview.d.ts +1919 -1919
- package/dist/utils/ensure-scene-openings.d.ts +3409 -3409
- package/dist/utils/floor-plate-migration.d.ts +1344 -1344
- package/dist/utils/owned-floor-opening-migration.d.ts +1344 -1344
- package/dist/utils/reconcile-structure-on-load.d.ts +1641 -1641
- package/dist/utils/scene-bounds.d.ts +32 -0
- package/dist/utils/scene-bounds.d.ts.map +1 -0
- package/dist/utils/scene-bounds.js +201 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-storage.d.ts","sourceRoot":"","sources":["../../src/lib/asset-storage.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"asset-storage.d.ts","sourceRoot":"","sources":["../../src/lib/asset-storage.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,YAAY,gBAAgB,CAAA;AAQzC;;GAEG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAI3D;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAqCtE"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { get, set } from 'idb-keyval';
|
|
2
2
|
import { customAlphabet } from 'nanoid';
|
|
3
|
+
import { isSchemeRelativeUrl } from '../schema/asset-url.js';
|
|
3
4
|
import { ARTIFACT_URL_PREFIX, resolveArtifactUrl } from './artifact-store.js';
|
|
4
5
|
export const ASSET_PREFIX = 'asset_data:';
|
|
5
6
|
// Cache for active object URLs to prevent leaks and flickering
|
|
@@ -21,6 +22,8 @@ export async function saveAsset(file) {
|
|
|
21
22
|
export async function loadAssetUrl(url) {
|
|
22
23
|
if (!url)
|
|
23
24
|
return null;
|
|
25
|
+
if (isSchemeRelativeUrl(url))
|
|
26
|
+
return null;
|
|
24
27
|
// If it's already a blob or http URL, return as is
|
|
25
28
|
if (url.startsWith('blob:') || url.startsWith('http')) {
|
|
26
29
|
return url;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"floor-foundation-datum.d.ts","sourceRoot":"","sources":["../../src/lib/floor-foundation-datum.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"floor-foundation-datum.d.ts","sourceRoot":"","sources":["../../src/lib/floor-foundation-datum.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAclD,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,KAAK,EAAE,QAAQ,GACd,MAAM,CAER;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,KAAK,EAAE,QAAQ,GACd,MAAM,CA+BR;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,KAAK,EAAE,QAAQ,GACd,WAAW,GAAG,gBAAgB,CA+BhC;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,KAAK,EAAE,QAAQ,GACd,OAAO,CAMT;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,KAAK,EAAE,QAAQ,GACd,OAAO,CAET;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,KAAK,EAAE,QAAQ,GACd;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,CAK7E;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,KAAK,EAAE,QAAQ,GACd,MAAM,CAER;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG,MAAM,CAM/F;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,KAAK,EAAE,QAAQ,EACf,SAAS,GAAE,MAA6C,GACvD;IACD,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5B,MAAM,CAAC,EAAE,aAAa,CAAA;CACvB,GAAG,IAAI,CAYP;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,IAAI,EAAE,OAAO,EACb,YAAY,EAAE,MAAM,GACnB,MAAM,CAUR;AAID,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACvC,OAAO,CAgBT"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { roofFaceShapes } from '../registry/roofs.js';
|
|
1
2
|
import { DEFAULT_SLAB_ELEVATION, MIN_SLAB_THICKNESS } from '../schema/nodes/slab.js';
|
|
2
3
|
import { wallOverlapsSlabFootprint } from '../systems/slab/slab-support.js';
|
|
3
4
|
import { getWallArcData } from '../systems/wall/wall-curve.js';
|
|
@@ -199,8 +200,11 @@ function computeFootprintSupport(base, node, nodes) {
|
|
|
199
200
|
const segments = node.children
|
|
200
201
|
.map((id) => nodes[id])
|
|
201
202
|
.filter((segment) => segment?.type === 'roof-segment');
|
|
202
|
-
|
|
203
|
+
const faces = roofFaceShapes(node, nodes);
|
|
204
|
+
if (!segments.length && !faces.length)
|
|
203
205
|
return containsPoint([{ outer: base.polygon, holes: base.holes ?? [] }], [node.position[0], node.position[2]]);
|
|
206
|
+
if (faces.some((face) => polygonsOverlap(base, face.outline)))
|
|
207
|
+
return true;
|
|
204
208
|
return segments.some((segment) => {
|
|
205
209
|
if (segment.type !== 'roof-segment')
|
|
206
210
|
return false;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { FloorplanGeometry, FloorplanPalette, GeometryContext } from '../../registry/types.js';
|
|
2
|
+
import type { AnyNode } from '../../schema/index.js';
|
|
3
|
+
/** The `def.floorplan` contract every plan host shares: context extensions and geometry metadata. */
|
|
4
|
+
export declare const FLOORPLAN_GEOMETRY_METADATA_KEY = "pascal:editor/floorplan";
|
|
5
|
+
export declare const FLOORPLAN_CONTEXT_EXTENSION_KEY = "pascal:editor/floorplan";
|
|
6
|
+
export type FloorplanRenderPurpose = 'edit' | 'document';
|
|
7
|
+
export type FloorplanMetricNotation = 'meters' | 'millimeters';
|
|
8
|
+
/**
|
|
9
|
+
* Display → Measure walls: which finished faces wall dimensions measure to —
|
|
10
|
+
* the interior faces (ID, the room sizes), the exterior faces (OD, the
|
|
11
|
+
* outside size) or both. Read by a selected wall's temporary dimensions in 2D
|
|
12
|
+
* and 3D and by the Expert plan's automatic dimension strings.
|
|
13
|
+
*/
|
|
14
|
+
export type FloorplanWallDimensionFaces = 'interior' | 'exterior' | 'both';
|
|
15
|
+
export declare const DEFAULT_FLOORPLAN_WALL_DIMENSION_FACES: FloorplanWallDimensionFaces;
|
|
16
|
+
export declare function normalizeFloorplanWallDimensionFaces(value: unknown): FloorplanWallDimensionFaces;
|
|
17
|
+
export type FloorplanAnnotationRole = 'automatic-dimension' | 'contextual-dimension' | 'manual-dimension' | 'measurement' | 'opening-mark' | 'structural-grid' | 'column-center' | 'room-label'
|
|
18
|
+
/** A room's finish / ceiling-height lines — detail under the name, off in the clean plan. */
|
|
19
|
+
| 'room-detail'
|
|
20
|
+
/** The roof plan proper: outline, ridges, hips, slope arrows. Off over a floor plan unless asked. */
|
|
21
|
+
| 'roof-plan' | 'roof-pitch' | 'stair-annotation';
|
|
22
|
+
export type FloorplanSchedule = {
|
|
23
|
+
id: string;
|
|
24
|
+
title: string;
|
|
25
|
+
columns: ReadonlyArray<{
|
|
26
|
+
key: string;
|
|
27
|
+
label: string;
|
|
28
|
+
weight?: number;
|
|
29
|
+
}>;
|
|
30
|
+
rows: ReadonlyArray<{
|
|
31
|
+
id: string;
|
|
32
|
+
cells: Readonly<Record<string, string>>;
|
|
33
|
+
}>;
|
|
34
|
+
issues?: readonly string[];
|
|
35
|
+
};
|
|
36
|
+
type FloorplanGeometryMetadata = {
|
|
37
|
+
annotationRole?: FloorplanAnnotationRole;
|
|
38
|
+
annotationObstacle?: 'bounds' | 'outline';
|
|
39
|
+
renderPass?: 'overlay';
|
|
40
|
+
/**
|
|
41
|
+
* Annotation text only: the size to print at, in paper points, instead of
|
|
42
|
+
* the size its role implies. Lets a sheet set a room tag's name larger than
|
|
43
|
+
* its number without inventing a role per size.
|
|
44
|
+
*/
|
|
45
|
+
textSizePt?: number;
|
|
46
|
+
};
|
|
47
|
+
type FloorplanContextExtension = {
|
|
48
|
+
automaticDimensions: boolean;
|
|
49
|
+
purpose: FloorplanRenderPurpose;
|
|
50
|
+
/**
|
|
51
|
+
* Sheet drafting (the Sheets plan viewports): kinds draw the permit-set
|
|
52
|
+
* convention — fixtures as labelled linework, furniture as light outlines,
|
|
53
|
+
* no raster sprites. Only `collectSheetGeometry` sets it; the editor and the
|
|
54
|
+
* read-only viewer never do, so their plans keep today's look.
|
|
55
|
+
*/
|
|
56
|
+
drafting: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* The editor canvas draws room labels as screen pills over the plan
|
|
59
|
+
* (`RoomLabels2D`), so zones skip their drawn tags and wash there. Exports,
|
|
60
|
+
* sheets and the read-only viewer keep the drawn tags.
|
|
61
|
+
*/
|
|
62
|
+
roomLabelOverlay: boolean;
|
|
63
|
+
metricNotation: FloorplanMetricNotation;
|
|
64
|
+
wallDimensionFaces: FloorplanWallDimensionFaces;
|
|
65
|
+
};
|
|
66
|
+
export declare function floorplanGeometryMetadata(values: FloorplanGeometryMetadata): Readonly<Record<string, unknown>>;
|
|
67
|
+
export declare function withFloorplanGeometryMetadata<T extends FloorplanGeometry | null>(geometry: T, values: FloorplanGeometryMetadata): T;
|
|
68
|
+
export declare function readFloorplanGeometryMetadata(geometry: unknown): FloorplanGeometryMetadata;
|
|
69
|
+
export declare function isFloorplanAnnotationObstacleGeometry(geometry: FloorplanGeometry): boolean;
|
|
70
|
+
export declare function floorplanAnnotationObstacleMode(geometry: FloorplanGeometry): 'bounds' | 'outline' | '' | undefined;
|
|
71
|
+
export declare function createFloorplanContextExtensions(values: Partial<FloorplanContextExtension>): Readonly<Record<string, unknown>>;
|
|
72
|
+
export declare function readFloorplanContext(ctx: GeometryContext): FloorplanContextExtension;
|
|
73
|
+
export declare function readFloorplanMetricNotationOverride(ctx: GeometryContext): FloorplanMetricNotation | undefined;
|
|
74
|
+
export type FloorplanViewState = {
|
|
75
|
+
automaticDimensions?: boolean;
|
|
76
|
+
selected: boolean;
|
|
77
|
+
unit: 'metric' | 'imperial';
|
|
78
|
+
metricNotation?: 'meters' | 'millimeters';
|
|
79
|
+
purpose?: 'edit' | 'document';
|
|
80
|
+
/** Sheet drafting conventions (see `FloorplanContextExtension.drafting`). */
|
|
81
|
+
drafting?: boolean;
|
|
82
|
+
/** Room labels drawn as screen pills (see `FloorplanContextExtension.roomLabelOverlay`). */
|
|
83
|
+
roomLabelOverlay?: boolean;
|
|
84
|
+
wallDimensionFaces?: FloorplanWallDimensionFaces;
|
|
85
|
+
highlighted: boolean;
|
|
86
|
+
hovered: boolean;
|
|
87
|
+
moving: boolean;
|
|
88
|
+
focusedUnitId?: string;
|
|
89
|
+
focusedUnitMemberIds?: readonly string[];
|
|
90
|
+
palette: FloorplanPalette | undefined;
|
|
91
|
+
};
|
|
92
|
+
export declare function buildFloorplanContext(node: AnyNode, nodes: Record<string, AnyNode>, viewState: FloorplanViewState, levelData?: unknown): GeometryContext;
|
|
93
|
+
export declare function floorplanLayerRank(type: string): number;
|
|
94
|
+
export {};
|
|
95
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/lib/floorplan/context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/** The `def.floorplan` contract every plan host shares: context extensions and geometry metadata. */
|
|
2
|
+
export const FLOORPLAN_GEOMETRY_METADATA_KEY = 'pascal:editor/floorplan';
|
|
3
|
+
export const FLOORPLAN_CONTEXT_EXTENSION_KEY = 'pascal:editor/floorplan';
|
|
4
|
+
export const DEFAULT_FLOORPLAN_WALL_DIMENSION_FACES = 'both';
|
|
5
|
+
export function normalizeFloorplanWallDimensionFaces(value) {
|
|
6
|
+
return value === 'interior' || value === 'exterior'
|
|
7
|
+
? value
|
|
8
|
+
: DEFAULT_FLOORPLAN_WALL_DIMENSION_FACES;
|
|
9
|
+
}
|
|
10
|
+
export function floorplanGeometryMetadata(values) {
|
|
11
|
+
return { [FLOORPLAN_GEOMETRY_METADATA_KEY]: values };
|
|
12
|
+
}
|
|
13
|
+
export function withFloorplanGeometryMetadata(geometry, values) {
|
|
14
|
+
if (!geometry)
|
|
15
|
+
return geometry;
|
|
16
|
+
const existing = readFloorplanGeometryMetadata(geometry);
|
|
17
|
+
return {
|
|
18
|
+
...geometry,
|
|
19
|
+
metadata: floorplanGeometryMetadata({ ...existing, ...values }),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function readFloorplanGeometryMetadata(geometry) {
|
|
23
|
+
const metadata = geometry?.metadata;
|
|
24
|
+
const value = metadata?.[FLOORPLAN_GEOMETRY_METADATA_KEY];
|
|
25
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
26
|
+
? value
|
|
27
|
+
: {};
|
|
28
|
+
}
|
|
29
|
+
export function isFloorplanAnnotationObstacleGeometry(geometry) {
|
|
30
|
+
if (floorplanAnnotationObstacleMode(geometry))
|
|
31
|
+
return true;
|
|
32
|
+
if (geometry.kind !== 'group')
|
|
33
|
+
return false;
|
|
34
|
+
const hasPlate = geometry.children.some((child) => child.kind === 'rect' || child.kind === 'circle');
|
|
35
|
+
const hasUprightText = geometry.children.some((child) => child.kind === 'text' && child.upright);
|
|
36
|
+
return hasPlate && hasUprightText;
|
|
37
|
+
}
|
|
38
|
+
export function floorplanAnnotationObstacleMode(geometry) {
|
|
39
|
+
const metadata = readFloorplanGeometryMetadata(geometry);
|
|
40
|
+
if (metadata.annotationObstacle)
|
|
41
|
+
return metadata.annotationObstacle;
|
|
42
|
+
switch (metadata.annotationRole) {
|
|
43
|
+
case 'room-label':
|
|
44
|
+
return geometry.kind === 'text' ? 'bounds' : undefined;
|
|
45
|
+
case 'column-center':
|
|
46
|
+
return geometry.kind === 'line' || geometry.kind === 'text' ? 'bounds' : undefined;
|
|
47
|
+
case 'stair-annotation':
|
|
48
|
+
return geometry.kind === 'polyline' || geometry.kind === 'line' ? 'outline' : 'bounds';
|
|
49
|
+
default:
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function createFloorplanContextExtensions(values) {
|
|
54
|
+
return {
|
|
55
|
+
[FLOORPLAN_CONTEXT_EXTENSION_KEY]: {
|
|
56
|
+
automaticDimensions: values.automaticDimensions !== false,
|
|
57
|
+
purpose: values.purpose === 'document' ? 'document' : 'edit',
|
|
58
|
+
drafting: values.drafting === true,
|
|
59
|
+
roomLabelOverlay: values.roomLabelOverlay === true,
|
|
60
|
+
metricNotation: values.metricNotation === 'millimeters' ? 'millimeters' : 'meters',
|
|
61
|
+
wallDimensionFaces: normalizeFloorplanWallDimensionFaces(values.wallDimensionFaces),
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function readFloorplanContext(ctx) {
|
|
66
|
+
const value = ctx.extensions?.[FLOORPLAN_CONTEXT_EXTENSION_KEY];
|
|
67
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
68
|
+
const extension = value;
|
|
69
|
+
return {
|
|
70
|
+
automaticDimensions: extension.automaticDimensions !== false,
|
|
71
|
+
purpose: extension.purpose === 'document' ? 'document' : 'edit',
|
|
72
|
+
drafting: extension.drafting === true,
|
|
73
|
+
roomLabelOverlay: extension.roomLabelOverlay === true,
|
|
74
|
+
metricNotation: extension.metricNotation === 'millimeters' ? 'millimeters' : 'meters',
|
|
75
|
+
wallDimensionFaces: normalizeFloorplanWallDimensionFaces(extension.wallDimensionFaces),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
automaticDimensions: true,
|
|
80
|
+
purpose: 'edit',
|
|
81
|
+
drafting: false,
|
|
82
|
+
roomLabelOverlay: false,
|
|
83
|
+
metricNotation: 'meters',
|
|
84
|
+
wallDimensionFaces: DEFAULT_FLOORPLAN_WALL_DIMENSION_FACES,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export function readFloorplanMetricNotationOverride(ctx) {
|
|
88
|
+
const value = ctx.extensions?.[FLOORPLAN_CONTEXT_EXTENSION_KEY];
|
|
89
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
90
|
+
return undefined;
|
|
91
|
+
const metricNotation = value.metricNotation;
|
|
92
|
+
return metricNotation === 'meters' || metricNotation === 'millimeters'
|
|
93
|
+
? metricNotation
|
|
94
|
+
: undefined;
|
|
95
|
+
}
|
|
96
|
+
export function buildFloorplanContext(node, nodes, viewState, levelData) {
|
|
97
|
+
const resolve = (id) => nodes[id];
|
|
98
|
+
const childIds = node.children;
|
|
99
|
+
const children = Array.isArray(childIds)
|
|
100
|
+
? childIds.map((id) => nodes[id]).filter((child) => child !== undefined)
|
|
101
|
+
: [];
|
|
102
|
+
const parentId = node.parentId;
|
|
103
|
+
const parent = parentId ? (nodes[parentId] ?? null) : null;
|
|
104
|
+
let siblings = [];
|
|
105
|
+
if (parent) {
|
|
106
|
+
const parentChildIds = parent.children;
|
|
107
|
+
siblings = Array.isArray(parentChildIds)
|
|
108
|
+
? parentChildIds
|
|
109
|
+
.filter((id) => id !== node.id)
|
|
110
|
+
.map((id) => nodes[id])
|
|
111
|
+
.filter((sibling) => sibling?.type === node.type)
|
|
112
|
+
: Object.values(nodes).filter((candidate) => candidate.id !== node.id &&
|
|
113
|
+
candidate.type === node.type &&
|
|
114
|
+
candidate.parentId === node.parentId);
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
resolve,
|
|
118
|
+
children,
|
|
119
|
+
siblings,
|
|
120
|
+
parent,
|
|
121
|
+
levelData,
|
|
122
|
+
sceneNodes: nodes,
|
|
123
|
+
extensions: createFloorplanContextExtensions({
|
|
124
|
+
automaticDimensions: viewState.automaticDimensions,
|
|
125
|
+
metricNotation: viewState.metricNotation ?? 'meters',
|
|
126
|
+
purpose: viewState.purpose ?? 'edit',
|
|
127
|
+
drafting: viewState.drafting === true,
|
|
128
|
+
roomLabelOverlay: viewState.roomLabelOverlay === true,
|
|
129
|
+
wallDimensionFaces: viewState.wallDimensionFaces,
|
|
130
|
+
}),
|
|
131
|
+
viewState: viewState.palette
|
|
132
|
+
? {
|
|
133
|
+
selected: viewState.selected,
|
|
134
|
+
unit: viewState.unit,
|
|
135
|
+
highlighted: viewState.highlighted,
|
|
136
|
+
hovered: viewState.hovered,
|
|
137
|
+
moving: viewState.moving,
|
|
138
|
+
focusedUnitId: viewState.focusedUnitId,
|
|
139
|
+
focusedUnitMemberIds: viewState.focusedUnitMemberIds,
|
|
140
|
+
palette: viewState.palette,
|
|
141
|
+
}
|
|
142
|
+
: undefined,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
export function floorplanLayerRank(type) {
|
|
146
|
+
switch (type) {
|
|
147
|
+
case 'zone':
|
|
148
|
+
return 0;
|
|
149
|
+
case 'slab':
|
|
150
|
+
case 'ceiling':
|
|
151
|
+
return 1;
|
|
152
|
+
default:
|
|
153
|
+
return 2;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/floorplan/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,oBAAoB,CAAA"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { AnyNode, AnyNodeId, DoorNode, WindowNode } from '../../schema/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic door / window marks per level — WS3.
|
|
4
|
+
*
|
|
5
|
+
* `packages/core/src/schema/nodes/door.ts:75-79` promises "the deterministic
|
|
6
|
+
* level fallback (101, 102, ...)"; this is the implementation.
|
|
7
|
+
*
|
|
8
|
+
* Numbering
|
|
9
|
+
* ---------
|
|
10
|
+
* base = (level.level + 1) * 100 — level 0 → 100, level 1 → 200
|
|
11
|
+
* doors = `D${base + n}` → D101, D102 …
|
|
12
|
+
* windows= `W${base + n}` → W101, W102 …
|
|
13
|
+
*
|
|
14
|
+
* Order
|
|
15
|
+
* -----
|
|
16
|
+
* Openings are visited **clockwise** around the level, starting from the
|
|
17
|
+
* north-west-most exterior wall, exterior walls first and interior walls
|
|
18
|
+
* after. Clockwise is the ascending-angle sweep of the wall midpoint about
|
|
19
|
+
* the level centroid, anchored on the north-west diagonal (plan axes are
|
|
20
|
+
* x east, z south — z grows down the screen — so a screen-clockwise sweep
|
|
21
|
+
* is increasing atan2(z, x)). Openings on the same wall are ordered along
|
|
22
|
+
* the wall direction.
|
|
23
|
+
*
|
|
24
|
+
* Stability
|
|
25
|
+
* ---------
|
|
26
|
+
* An explicit `mark` on the node always wins and is never reassigned.
|
|
27
|
+
* `resolveMarks` also reports, in `assignments`, the marks it invented for
|
|
28
|
+
* nodes that had none — the caller is expected to PERSIST those back onto
|
|
29
|
+
* `node.mark` the first time they are resolved in an edit session
|
|
30
|
+
* (`persistResolvedMarks`). That is what makes numbering stable under
|
|
31
|
+
* insertion: once written, an existing opening keeps its number forever and
|
|
32
|
+
* a newly inserted opening only ever takes the next free number. Without
|
|
33
|
+
* the write-back, inserting a door in the middle of a wall would silently
|
|
34
|
+
* renumber every door after it.
|
|
35
|
+
*/
|
|
36
|
+
export type OpeningMarkKind = 'door' | 'window';
|
|
37
|
+
export type MarkResolution = {
|
|
38
|
+
/** Every door and window on the level → its mark. */
|
|
39
|
+
marks: ReadonlyMap<string, string>;
|
|
40
|
+
/** Only the marks that were invented (node.mark was empty). */
|
|
41
|
+
assignments: ReadonlyMap<string, string>;
|
|
42
|
+
/** Duplicate explicit marks and other reportable defects. */
|
|
43
|
+
issues: readonly string[];
|
|
44
|
+
};
|
|
45
|
+
export type MarkSceneInput = {
|
|
46
|
+
nodes: Readonly<Record<string, AnyNode>>;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Resolve marks for every door and window under `levelId`.
|
|
50
|
+
*
|
|
51
|
+
* `scene` may be a `{ nodes }` snapshot or the raw node map.
|
|
52
|
+
*/
|
|
53
|
+
export declare function resolveMarks(scene: MarkSceneInput | Readonly<Record<string, AnyNode>>, levelId: AnyNodeId): Map<string, string>;
|
|
54
|
+
export declare function resolveMarkDetail(scene: MarkSceneInput | Readonly<Record<string, AnyNode>>, levelId: AnyNodeId): MarkResolution;
|
|
55
|
+
/**
|
|
56
|
+
* Write invented marks back onto the nodes so numbering never drifts.
|
|
57
|
+
*
|
|
58
|
+
* Call once per level per edit session (the caller decides when — this
|
|
59
|
+
* module stays pure). `update` is typically `sceneApi.update`.
|
|
60
|
+
*/
|
|
61
|
+
export declare function persistResolvedMarks(resolution: MarkResolution, update: (id: AnyNodeId, data: Record<string, unknown>) => void): number;
|
|
62
|
+
type Opening = (DoorNode | WindowNode) & {
|
|
63
|
+
type: 'door' | 'window';
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Every door and window under the level, clockwise from the NW-most
|
|
67
|
+
* exterior wall, exterior walls first.
|
|
68
|
+
*/
|
|
69
|
+
export declare function orderedOpenings(nodes: Readonly<Record<string, AnyNode>>, levelId: AnyNodeId): Opening[];
|
|
70
|
+
export {};
|
|
71
|
+
//# sourceMappingURL=marks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marks.d.ts","sourceRoot":"","sources":["../../../src/lib/floorplan/marks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAuB,UAAU,EAAE,MAAM,cAAc,CAAA;AAEjG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,CAAA;AAE/C,MAAM,MAAM,cAAc,GAAG;IAC3B,qDAAqD;IACrD,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAClC,+DAA+D;IAC/D,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACxC,6DAA6D;IAC7D,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CACzC,CAAA;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzD,OAAO,EAAE,SAAS,GACjB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAErB;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzD,OAAO,EAAE,SAAS,GACjB,cAAc,CAkDhB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,cAAc,EAC1B,MAAM,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAC7D,MAAM,CAOR;AAID,KAAK,OAAO,GAAG,CAAC,QAAQ,GAAG,UAAU,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAA;CAAE,CAAA;AAEpE;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,OAAO,EAAE,SAAS,GACjB,OAAO,EAAE,CAuCX"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve marks for every door and window under `levelId`.
|
|
3
|
+
*
|
|
4
|
+
* `scene` may be a `{ nodes }` snapshot or the raw node map.
|
|
5
|
+
*/
|
|
6
|
+
export function resolveMarks(scene, levelId) {
|
|
7
|
+
return new Map(resolveMarkDetail(scene, levelId).marks);
|
|
8
|
+
}
|
|
9
|
+
export function resolveMarkDetail(scene, levelId) {
|
|
10
|
+
const nodes = normalizeNodes(scene);
|
|
11
|
+
const level = nodes[levelId];
|
|
12
|
+
const ordinal = level && level.type === 'level' ? (level.level ?? 0) : 0;
|
|
13
|
+
const base = (Math.max(0, ordinal) + 1) * 100;
|
|
14
|
+
const openings = orderedOpenings(nodes, levelId);
|
|
15
|
+
const marks = new Map();
|
|
16
|
+
const assignments = new Map();
|
|
17
|
+
const issues = [];
|
|
18
|
+
for (const kind of ['door', 'window']) {
|
|
19
|
+
const prefix = kind === 'door' ? 'D' : 'W';
|
|
20
|
+
const list = openings.filter((opening) => opening.type === kind);
|
|
21
|
+
const used = new Set();
|
|
22
|
+
const explicitOwners = new Map();
|
|
23
|
+
for (const opening of list) {
|
|
24
|
+
const explicit = opening.mark?.trim();
|
|
25
|
+
if (!explicit)
|
|
26
|
+
continue;
|
|
27
|
+
marks.set(opening.id, explicit);
|
|
28
|
+
const normalized = explicit.toLocaleUpperCase();
|
|
29
|
+
used.add(normalized);
|
|
30
|
+
const owners = explicitOwners.get(normalized);
|
|
31
|
+
if (owners)
|
|
32
|
+
owners.push(opening.id);
|
|
33
|
+
else
|
|
34
|
+
explicitOwners.set(normalized, [opening.id]);
|
|
35
|
+
}
|
|
36
|
+
let sequence = 1;
|
|
37
|
+
for (const opening of list) {
|
|
38
|
+
if (marks.has(opening.id))
|
|
39
|
+
continue;
|
|
40
|
+
let candidate = `${prefix}${base + sequence}`;
|
|
41
|
+
while (used.has(candidate.toLocaleUpperCase())) {
|
|
42
|
+
sequence++;
|
|
43
|
+
candidate = `${prefix}${base + sequence}`;
|
|
44
|
+
}
|
|
45
|
+
marks.set(opening.id, candidate);
|
|
46
|
+
assignments.set(opening.id, candidate);
|
|
47
|
+
used.add(candidate.toLocaleUpperCase());
|
|
48
|
+
sequence++;
|
|
49
|
+
}
|
|
50
|
+
for (const [mark, owners] of explicitOwners) {
|
|
51
|
+
if (owners.length > 1) {
|
|
52
|
+
issues.push(`Duplicate ${kind} mark ${mark} (${owners.length} instances)`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { marks, assignments, issues };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Write invented marks back onto the nodes so numbering never drifts.
|
|
60
|
+
*
|
|
61
|
+
* Call once per level per edit session (the caller decides when — this
|
|
62
|
+
* module stays pure). `update` is typically `sceneApi.update`.
|
|
63
|
+
*/
|
|
64
|
+
export function persistResolvedMarks(resolution, update) {
|
|
65
|
+
let written = 0;
|
|
66
|
+
for (const [id, mark] of resolution.assignments) {
|
|
67
|
+
update(id, { mark });
|
|
68
|
+
written++;
|
|
69
|
+
}
|
|
70
|
+
return written;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Every door and window under the level, clockwise from the NW-most
|
|
74
|
+
* exterior wall, exterior walls first.
|
|
75
|
+
*/
|
|
76
|
+
export function orderedOpenings(nodes, levelId) {
|
|
77
|
+
const walls = [];
|
|
78
|
+
const orphanOpenings = [];
|
|
79
|
+
const visit = (id) => {
|
|
80
|
+
const node = nodes[id];
|
|
81
|
+
if (!node)
|
|
82
|
+
return;
|
|
83
|
+
if (node.visible === false)
|
|
84
|
+
return;
|
|
85
|
+
if (node.type === 'wall')
|
|
86
|
+
walls.push(node);
|
|
87
|
+
else if (node.type === 'door' || node.type === 'window') {
|
|
88
|
+
const opening = node;
|
|
89
|
+
if (!opening.wallId || !nodes[opening.wallId])
|
|
90
|
+
orphanOpenings.push(opening);
|
|
91
|
+
}
|
|
92
|
+
for (const childId of node.children ?? [])
|
|
93
|
+
visit(childId);
|
|
94
|
+
};
|
|
95
|
+
visit(levelId);
|
|
96
|
+
const centroid = wallCentroid(walls);
|
|
97
|
+
const ordered = [...walls].sort((left, right) => {
|
|
98
|
+
const leftExterior = isExteriorWall(left) ? 0 : 1;
|
|
99
|
+
const rightExterior = isExteriorWall(right) ? 0 : 1;
|
|
100
|
+
if (leftExterior !== rightExterior)
|
|
101
|
+
return leftExterior - rightExterior;
|
|
102
|
+
return clockwiseKey(left, centroid) - clockwiseKey(right, centroid);
|
|
103
|
+
});
|
|
104
|
+
const result = [];
|
|
105
|
+
for (const wall of ordered) {
|
|
106
|
+
const wallOpenings = [];
|
|
107
|
+
for (const childId of wall.children ?? []) {
|
|
108
|
+
const child = nodes[childId];
|
|
109
|
+
if (child && child.visible !== false && (child.type === 'door' || child.type === 'window')) {
|
|
110
|
+
wallOpenings.push(child);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
wallOpenings.sort((left, right) => left.position[0] - right.position[0]);
|
|
114
|
+
result.push(...wallOpenings);
|
|
115
|
+
}
|
|
116
|
+
result.push(...orphanOpenings);
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
function wallCentroid(walls) {
|
|
120
|
+
if (walls.length === 0)
|
|
121
|
+
return [0, 0];
|
|
122
|
+
let x = 0;
|
|
123
|
+
let z = 0;
|
|
124
|
+
for (const wall of walls) {
|
|
125
|
+
x += (wall.start[0] + wall.end[0]) / 2;
|
|
126
|
+
z += (wall.start[1] + wall.end[1]) / 2;
|
|
127
|
+
}
|
|
128
|
+
return [x / walls.length, z / walls.length];
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Sweep key: 0 at the north-west direction, increasing clockwise on screen.
|
|
132
|
+
*
|
|
133
|
+
* Plan axes are x east, z south — z grows DOWN the screen — so a clockwise
|
|
134
|
+
* screen sweep (up → right → down → left) is the INCREASING atan2(z, x)
|
|
135
|
+
* direction. The north-west diagonal sits at atan2(-1, -1) = -3π/4, so
|
|
136
|
+
* anchoring there and wrapping into [0, 2π) yields north, east, south, west.
|
|
137
|
+
*/
|
|
138
|
+
const NORTH_WEST_ANGLE = (-3 * Math.PI) / 4;
|
|
139
|
+
function clockwiseKey(wall, centroid) {
|
|
140
|
+
const midX = (wall.start[0] + wall.end[0]) / 2 - centroid[0];
|
|
141
|
+
const midZ = (wall.start[1] + wall.end[1]) / 2 - centroid[1];
|
|
142
|
+
let sweep = Math.atan2(midZ, midX) - NORTH_WEST_ANGLE;
|
|
143
|
+
while (sweep < 0)
|
|
144
|
+
sweep += Math.PI * 2;
|
|
145
|
+
while (sweep >= Math.PI * 2)
|
|
146
|
+
sweep -= Math.PI * 2;
|
|
147
|
+
return sweep;
|
|
148
|
+
}
|
|
149
|
+
function isExteriorWall(wall) {
|
|
150
|
+
return wall.frontSide === 'exterior' || wall.backSide === 'exterior';
|
|
151
|
+
}
|
|
152
|
+
function normalizeNodes(scene) {
|
|
153
|
+
const candidate = scene;
|
|
154
|
+
return candidate && typeof candidate === 'object' && 'nodes' in candidate
|
|
155
|
+
? candidate.nodes
|
|
156
|
+
: scene;
|
|
157
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type WallJustification } from '../../systems/wall/wall-frame.js';
|
|
2
|
+
import type { Point2D } from '../../systems/wall/wall-mitering.js';
|
|
3
|
+
export declare const FLOORPLAN_VIEW_ROTATION_DEG = 0;
|
|
4
|
+
export declare function clampPlanValue(value: number, min: number, max: number): number;
|
|
5
|
+
export declare function rotatePlanVector(x: number, y: number, rotation: number): [number, number];
|
|
6
|
+
export declare function worldToFloorplanLocalPoint(worldX: number, worldZ: number, buildingPosition: readonly [number, number, number], buildingRotationY: number): Point2D;
|
|
7
|
+
export declare function floorplanLocalToWorldPoint(point: Point2D | [number, number], buildingPosition: readonly [number, number, number], buildingRotationY: number): {
|
|
8
|
+
x: number;
|
|
9
|
+
z: number;
|
|
10
|
+
};
|
|
11
|
+
export declare function interpolatePlanPoint(start: Point2D, end: Point2D, t: number): Point2D;
|
|
12
|
+
export declare function getPlanPointDistance(start: Point2D, end: Point2D): number;
|
|
13
|
+
export declare function movePlanPointTowards(start: Point2D, end: Point2D, distance: number): Point2D;
|
|
14
|
+
export declare function getThickPlanLinePolygon(line: {
|
|
15
|
+
start: Point2D;
|
|
16
|
+
end: Point2D;
|
|
17
|
+
}, thickness: number, justification?: WallJustification): Point2D[];
|
|
18
|
+
//# sourceMappingURL=plan-math.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-math.d.ts","sourceRoot":"","sources":["../../../src/lib/floorplan/plan-math.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,iBAAiB,EAAE,MAAM,+BAA+B,CAAA;AAC1F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAA;AAY/D,eAAO,MAAM,2BAA2B,IAAI,CAAA;AAE5C,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,UAErE;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAIzF;AAQD,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EACnD,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAUT;AAGD,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,OAAO,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,gBAAgB,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EACnD,iBAAiB,EAAE,MAAM,GACxB;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAU1B;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAKrF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,CAEzE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAO5F;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,OAAO,CAAA;CAAE,EACtC,SAAS,EAAE,MAAM,EACjB,aAAa,CAAC,EAAE,iBAAiB,GAChC,OAAO,EAAE,CAqBX"}
|