@twinfinity/printing 6.1.0-beta → 6.1.2-ci.30738-beta
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/BuildInfo.js +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/layout/analyzeTemplate.d.ts +4 -4
- package/dist/layout/analyzeTemplate.d.ts.map +1 -1
- package/dist/layout/analyzeTemplate.js +18 -16
- package/dist/layout/analyzeTemplate.js.map +1 -1
- package/dist/layout/classClosure.d.ts +41 -0
- package/dist/layout/classClosure.d.ts.map +1 -0
- package/dist/layout/classClosure.js +143 -0
- package/dist/layout/classClosure.js.map +1 -0
- package/dist/layout/clipUtils.d.ts +11 -0
- package/dist/layout/clipUtils.d.ts.map +1 -1
- package/dist/layout/clipUtils.js +67 -6
- package/dist/layout/clipUtils.js.map +1 -1
- package/dist/layout/filters.d.ts +14 -8
- package/dist/layout/filters.d.ts.map +1 -1
- package/dist/layout/filters.js +28 -3
- package/dist/layout/filters.js.map +1 -1
- package/dist/layout/index.d.ts +24 -57
- package/dist/layout/index.d.ts.map +1 -1
- package/dist/layout/index.js +20 -337
- package/dist/layout/index.js.map +1 -1
- package/dist/layout/itemBuilders.d.ts +11 -0
- package/dist/layout/itemBuilders.d.ts.map +1 -1
- package/dist/layout/itemBuilders.js +17 -3
- package/dist/layout/itemBuilders.js.map +1 -1
- package/dist/layout/labelsRender.d.ts.map +1 -1
- package/dist/layout/labelsRender.js +15 -27
- package/dist/layout/labelsRender.js.map +1 -1
- package/dist/layout/legendRender.d.ts +18 -6
- package/dist/layout/legendRender.d.ts.map +1 -1
- package/dist/layout/legendRender.js +49 -79
- package/dist/layout/legendRender.js.map +1 -1
- package/dist/layout/metadata.d.ts.map +1 -1
- package/dist/layout/metadata.js +26 -5
- package/dist/layout/metadata.js.map +1 -1
- package/dist/layout/model.d.ts +4 -3
- package/dist/layout/model.d.ts.map +1 -1
- package/dist/layout/model.js +17 -57
- package/dist/layout/model.js.map +1 -1
- package/dist/layout/options.d.ts +1 -2
- package/dist/layout/options.d.ts.map +1 -1
- package/dist/layout/options.js.map +1 -1
- package/dist/layout/preflight.d.ts +32 -0
- package/dist/layout/preflight.d.ts.map +1 -0
- package/dist/layout/preflight.js +69 -0
- package/dist/layout/preflight.js.map +1 -0
- package/dist/layout/qrRender.d.ts.map +1 -1
- package/dist/layout/qrRender.js +11 -10
- package/dist/layout/qrRender.js.map +1 -1
- package/dist/layout/renderDrawing.d.ts +28 -17
- package/dist/layout/renderDrawing.d.ts.map +1 -1
- package/dist/layout/renderDrawing.js +107 -88
- package/dist/layout/renderDrawing.js.map +1 -1
- package/dist/layout/renderLayout.d.ts +25 -0
- package/dist/layout/renderLayout.d.ts.map +1 -0
- package/dist/layout/renderLayout.js +315 -0
- package/dist/layout/renderLayout.js.map +1 -0
- package/dist/layout/renderTemplateSheet.d.ts +99 -0
- package/dist/layout/renderTemplateSheet.d.ts.map +1 -0
- package/dist/layout/renderTemplateSheet.js +296 -0
- package/dist/layout/renderTemplateSheet.js.map +1 -0
- package/dist/layout/renderUtils.d.ts +19 -0
- package/dist/layout/renderUtils.d.ts.map +1 -1
- package/dist/layout/renderUtils.js +47 -4
- package/dist/layout/renderUtils.js.map +1 -1
- package/dist/layout/sectionDefs.d.ts +65 -0
- package/dist/layout/sectionDefs.d.ts.map +1 -0
- package/dist/layout/sectionDefs.js +88 -0
- package/dist/layout/sectionDefs.js.map +1 -0
- package/dist/layout/slabModelView.d.ts +34 -0
- package/dist/layout/slabModelView.d.ts.map +1 -0
- package/dist/layout/slabModelView.js +175 -0
- package/dist/layout/slabModelView.js.map +1 -0
- package/dist/layout/templateVariables.d.ts +1 -1
- package/dist/layout/templateVariables.d.ts.map +1 -1
- package/dist/layout/templateVariables.js +21 -9
- package/dist/layout/templateVariables.js.map +1 -1
- package/dist/layout/types.d.ts +164 -83
- package/dist/layout/types.d.ts.map +1 -1
- package/dist/layout/types.js.map +1 -1
- package/dist/layout/validation.d.ts.map +1 -1
- package/dist/layout/validation.js +78 -33
- package/dist/layout/validation.js.map +1 -1
- package/dist/layout/viewportPrep.d.ts +8 -6
- package/dist/layout/viewportPrep.d.ts.map +1 -1
- package/dist/layout/viewportPrep.js +35 -23
- package/dist/layout/viewportPrep.js.map +1 -1
- package/dist/layout/viewportRender.d.ts +9 -7
- package/dist/layout/viewportRender.d.ts.map +1 -1
- package/dist/layout/viewportRender.js +93 -126
- package/dist/layout/viewportRender.js.map +1 -1
- package/dist/layout/viewportUtils.d.ts +20 -0
- package/dist/layout/viewportUtils.d.ts.map +1 -1
- package/dist/layout/viewportUtils.js +107 -4
- package/dist/layout/viewportUtils.js.map +1 -1
- package/dist/printToPdf.d.ts +7 -1
- package/dist/printToPdf.d.ts.map +1 -1
- package/dist/printToPdf.js +81 -14
- package/dist/printToPdf.js.map +1 -1
- package/dist/printToSvg.d.ts.map +1 -1
- package/dist/printToSvg.js +7 -3
- package/dist/printToSvg.js.map +1 -1
- package/dist/schemaUtils.d.ts +9 -0
- package/dist/schemaUtils.d.ts.map +1 -0
- package/dist/schemaUtils.js +14 -0
- package/dist/schemaUtils.js.map +1 -0
- package/dist/templates/default-template.json +30 -8
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +10 -1
- package/dist/types.js.map +1 -1
- package/package.json +8 -3
- package/src/index.ts +4 -0
- package/src/layout/analyzeTemplate.ts +21 -19
- package/src/layout/classClosure.ts +142 -0
- package/src/layout/clipUtils.ts +65 -6
- package/src/layout/filters.ts +31 -3
- package/src/layout/index.ts +61 -571
- package/src/layout/itemBuilders.ts +20 -5
- package/src/layout/labelsRender.ts +23 -40
- package/src/layout/legendRender.ts +64 -84
- package/src/layout/metadata.ts +26 -5
- package/src/layout/model.ts +21 -66
- package/src/layout/options.ts +1 -2
- package/src/layout/preflight.ts +96 -0
- package/src/layout/qrRender.ts +11 -10
- package/src/layout/renderDrawing.ts +132 -110
- package/src/layout/renderLayout.ts +483 -0
- package/src/layout/renderTemplateSheet.ts +435 -0
- package/src/layout/renderUtils.ts +53 -4
- package/src/layout/sectionDefs.ts +131 -0
- package/src/layout/slabModelView.ts +196 -0
- package/src/layout/templateVariables.ts +25 -9
- package/src/layout/types.ts +160 -70
- package/src/layout/validation.ts +98 -32
- package/src/layout/viewportPrep.ts +47 -29
- package/src/layout/viewportRender.ts +114 -141
- package/src/layout/viewportUtils.ts +118 -5
- package/src/printToPdf.ts +84 -15
- package/src/printToSvg.ts +7 -3
- package/src/schemaUtils.ts +13 -0
- package/src/templates/default-template.json +30 -8
- package/src/types.ts +12 -2
- package/templates/default-a0-landscape.json +171 -66
- package/templates/default-a0-portrait.json +159 -60
- package/templates/default-a1-landscape.json +135 -48
- package/templates/default-a1-portrait.json +123 -42
- package/templates/default-a3-landscape.json +87 -24
- package/templates/default-a3-portrait.json +99 -30
- package/templates/default-a4-landscape.json +75 -18
- package/templates/default-a4-portrait.json +75 -18
- package/templates/llm-authoring-guide.md +247 -0
- package/templates/template-schema.json +295 -120
|
@@ -93,6 +93,22 @@ const getBBoxCenter = (product: unknown): { x: number | null; z: number | null }
|
|
|
93
93
|
return { x: null, z: null };
|
|
94
94
|
};
|
|
95
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Read the plan-projection X/Z of a polyline point, tolerating both shapes the
|
|
98
|
+
* section pipeline emits: `Vec3` arrays `[x, y, z]` (from
|
|
99
|
+
* `computePlaneSectionsForProducts`) and `{ x, z }` objects. Coords are `null`
|
|
100
|
+
* when the point is neither / non-finite. Shared by every consumer so the dual
|
|
101
|
+
* contract is handled in exactly one place.
|
|
102
|
+
*/
|
|
103
|
+
export const readXZ = (pt: unknown): { x: number | null; z: number | null } => {
|
|
104
|
+
const rawX = Array.isArray(pt) ? pt[0] : getProp(pt, 'x');
|
|
105
|
+
const rawZ = Array.isArray(pt) ? pt[2] : getProp(pt, 'z');
|
|
106
|
+
return {
|
|
107
|
+
x: Number.isFinite(rawX) ? (rawX as number) : null,
|
|
108
|
+
z: Number.isFinite(rawZ) ? (rawZ as number) : null
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
|
|
96
112
|
export const computePolylinesCenter = (entry: unknown): { x: number | null; z: number | null } => {
|
|
97
113
|
try {
|
|
98
114
|
const polylines = getProp(entry, 'polylines');
|
|
@@ -106,11 +122,10 @@ export const computePolylinesCenter = (entry: unknown): { x: number | null; z: n
|
|
|
106
122
|
const points = getProp(polyline, 'points');
|
|
107
123
|
if (!Array.isArray(points)) continue;
|
|
108
124
|
for (const pt of points) {
|
|
109
|
-
const px =
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
sumZ += pz as number;
|
|
125
|
+
const { x: px, z: pz } = readXZ(pt);
|
|
126
|
+
if (px !== null && pz !== null) {
|
|
127
|
+
sumX += px;
|
|
128
|
+
sumZ += pz;
|
|
114
129
|
count++;
|
|
115
130
|
}
|
|
116
131
|
}
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { LabelsElement, LayoutData } from './types';
|
|
2
|
-
import {
|
|
2
|
+
import { matchesFilter } from './filters';
|
|
3
3
|
import { pointInPolygon } from './clipUtils';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getPathValue,
|
|
6
|
+
resolvePath,
|
|
7
|
+
escapeAttr,
|
|
8
|
+
escapeText,
|
|
9
|
+
formatNumber,
|
|
10
|
+
splitPlaceholder,
|
|
11
|
+
applyPlaceholderFilter
|
|
12
|
+
} from './renderUtils';
|
|
5
13
|
|
|
6
14
|
import { isRecord, getProp } from './typeGuards';
|
|
7
15
|
|
|
@@ -60,41 +68,22 @@ export const renderLabels = (
|
|
|
60
68
|
}
|
|
61
69
|
// Filter by class + where
|
|
62
70
|
let filteredItems =
|
|
63
|
-
dl.class || dl.where ? items.filter((item) =>
|
|
64
|
-
|
|
65
|
-
console.log(`[Labels ${dl.id}] Total items:`, items.length, 'After class+where filter:', filteredItems.length);
|
|
71
|
+
dl.class || dl.where ? items.filter((item) => matchesFilter(item, dl.class, dl.where)) : items;
|
|
66
72
|
|
|
67
73
|
// If matchClipFilter is true, filter by spatial position inside clip polygon
|
|
68
74
|
if (dl.matchClipFilter && viewportClipPolygonsWorld[dl.viewport]?.length) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const beforeClipFilter = filteredItems.length;
|
|
75
|
+
// pointInPolygon expects y — world polys are {x, z}; map ONCE, not per item.
|
|
76
|
+
const polys2D = viewportClipPolygonsWorld[dl.viewport].map((poly) => poly.map((p) => ({ x: p.x, y: p.z })));
|
|
72
77
|
filteredItems = filteredItems.filter((item) => {
|
|
73
78
|
const ix = getProp(item, 'x');
|
|
74
79
|
const iz = getProp(item, 'z');
|
|
75
80
|
if (!Number.isFinite(ix) || !Number.isFinite(iz)) return false;
|
|
76
|
-
return
|
|
77
|
-
// pointInPolygon expects y coordinate, but we use z for world space
|
|
78
|
-
const polyWith2D = poly.map((p) => ({ x: p.x, y: p.z }));
|
|
79
|
-
return pointInPolygon(polyWith2D, ix as number, iz as number);
|
|
80
|
-
});
|
|
81
|
+
return polys2D.some((poly) => pointInPolygon(poly, ix as number, iz as number));
|
|
81
82
|
});
|
|
82
|
-
console.log(
|
|
83
|
-
`[Labels ${dl.id}] After matchClipFilter (spatial):`,
|
|
84
|
-
filteredItems.length,
|
|
85
|
-
'(was:',
|
|
86
|
-
beforeClipFilter,
|
|
87
|
-
')'
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
// Debug: log first few items to see their properties
|
|
91
|
-
if (filteredItems.length === 0 && beforeClipFilter > 0) {
|
|
92
|
-
console.log('[Labels] Sample item that was filtered out:', items[0]);
|
|
93
|
-
}
|
|
94
83
|
}
|
|
95
84
|
|
|
96
85
|
if (!filteredItems.length) {
|
|
97
|
-
|
|
86
|
+
warnings.push(`Labels '${dl.id}' matched 0 of ${items.length} items after filtering.`);
|
|
98
87
|
continue;
|
|
99
88
|
}
|
|
100
89
|
const styleName = dl.textStyle;
|
|
@@ -103,12 +92,10 @@ export const renderLabels = (
|
|
|
103
92
|
warnings.push(`Labels '${dl.id}' missing textStyle '${styleName ?? '(none)'}'.`);
|
|
104
93
|
continue;
|
|
105
94
|
}
|
|
106
|
-
|
|
95
|
+
// Schema default is center — labels sit on item centroids.
|
|
96
|
+
const anchor = dl.align === 'left' ? 'start' : dl.align === 'right' ? 'end' : 'middle';
|
|
107
97
|
const t = vpInfo.transform;
|
|
108
98
|
|
|
109
|
-
let renderedCount = 0;
|
|
110
|
-
let spatiallyFilteredCount = 0;
|
|
111
|
-
|
|
112
99
|
filteredItems.forEach((item) => {
|
|
113
100
|
// Hardcoded x/z for BIM floor plane convention
|
|
114
101
|
const xVal = getProp(item, 'x');
|
|
@@ -128,18 +115,18 @@ export const renderLabels = (
|
|
|
128
115
|
pointInPolygon(poly, viewX, viewY)
|
|
129
116
|
);
|
|
130
117
|
if (!inside) {
|
|
131
|
-
spatiallyFilteredCount++;
|
|
132
118
|
return;
|
|
133
119
|
}
|
|
134
120
|
}
|
|
135
121
|
|
|
136
|
-
renderedCount++;
|
|
137
|
-
|
|
138
122
|
const text = escapeText(
|
|
139
123
|
dl.text && typeof dl.text === 'string'
|
|
140
|
-
? dl.text.replace(/\{\{\s*([^}]+?)\s*\}\}/g, (_m,
|
|
124
|
+
? dl.text.replace(/\{\{\s*([^}]+?)\s*\}\}/g, (_m, expr) => {
|
|
125
|
+
const { path, filter } = splitPlaceholder(expr);
|
|
141
126
|
const val = resolvePath(item, path) ?? getPathValue(dataWithViewport, path);
|
|
142
|
-
return val === undefined || val === null || typeof val === 'object'
|
|
127
|
+
return val === undefined || val === null || typeof val === 'object'
|
|
128
|
+
? ''
|
|
129
|
+
: String(applyPlaceholderFilter(val, filter));
|
|
143
130
|
})
|
|
144
131
|
: ''
|
|
145
132
|
);
|
|
@@ -152,7 +139,7 @@ export const renderLabels = (
|
|
|
152
139
|
const lineHeightMm = getProp(style, 'lineHeightMm');
|
|
153
140
|
|
|
154
141
|
parts.push(
|
|
155
|
-
`<text x="${xMm}" y="${yMm}" font-family="${escapeAttr(String(fontFamily))}" font-size="${fontSizeMm}"` +
|
|
142
|
+
`<text x="${formatNumber(xMm)}" y="${formatNumber(yMm)}" font-family="${escapeAttr(String(fontFamily))}" font-size="${fontSizeMm}"` +
|
|
156
143
|
(fontWeight ? ` font-weight="${escapeAttr(String(fontWeight))}"` : '') +
|
|
157
144
|
(color ? ` fill="${escapeAttr(String(color))}"` : '') +
|
|
158
145
|
(letterSpacingMm !== undefined ? ` letter-spacing="${letterSpacingMm}"` : '') +
|
|
@@ -160,10 +147,6 @@ export const renderLabels = (
|
|
|
160
147
|
` text-anchor="${anchor}">${text}</text>`
|
|
161
148
|
);
|
|
162
149
|
});
|
|
163
|
-
|
|
164
|
-
console.log(
|
|
165
|
-
`[Labels ${dl.id}] Rendered ${renderedCount} labels. Spatially filtered out: ${spatiallyFilteredCount}`
|
|
166
|
-
);
|
|
167
150
|
}
|
|
168
151
|
|
|
169
152
|
return parts;
|
|
@@ -14,8 +14,8 @@ import {
|
|
|
14
14
|
parseLegendColor,
|
|
15
15
|
formatLegendValue
|
|
16
16
|
} from './legendUtils';
|
|
17
|
-
import { resolvePath } from './renderUtils';
|
|
18
|
-
import {
|
|
17
|
+
import { resolvePath, escapeText, escapeAttr } from './renderUtils';
|
|
18
|
+
import { matchesFilter } from './filters';
|
|
19
19
|
import { pointInPolygon } from './clipUtils';
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -80,43 +80,52 @@ import { isRecord, getProp } from './typeGuards';
|
|
|
80
80
|
export const applyLegendFilter = (
|
|
81
81
|
legendData: unknown[],
|
|
82
82
|
legend: LegendElement,
|
|
83
|
-
|
|
84
|
-
viewportClipPolygonsLocal: Record<string, Array<Array<{ x: number; y: number }>>>,
|
|
85
|
-
viewportRenderInfo: Record<string, { transform?: unknown }>
|
|
83
|
+
viewportClipPolygonsWorld: Record<string, Array<Array<{ x: number; z: number }>>>
|
|
86
84
|
): unknown[] => {
|
|
87
85
|
// Filter by class + where
|
|
88
86
|
let rows = Array.isArray(legendData)
|
|
89
|
-
? legendData.filter((row) =>
|
|
87
|
+
? legendData.filter((row) => matchesFilter(row, legend.class, legend.where))
|
|
90
88
|
: [];
|
|
91
89
|
if (!rows.length) return rows;
|
|
92
90
|
|
|
91
|
+
// clipToViewport filters in WORLD space (same convention as labels and
|
|
92
|
+
// clipAllowedGids) so the result is available before viewports render —
|
|
93
|
+
// legend color maps must exist by then for fill: "legend".
|
|
93
94
|
if (legend.clipToViewport) {
|
|
94
95
|
const targetViewportId = legend.viewport;
|
|
95
|
-
const polys = targetViewportId ?
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const rowZ = getProp(row, 'z');
|
|
105
|
-
if (!Number.isFinite(rowX) || !Number.isFinite(rowZ)) return false;
|
|
106
|
-
const viewX = Number(rowX) - viewMinX;
|
|
107
|
-
const viewY = viewMaxZ - Number(rowZ);
|
|
108
|
-
return polys.some((poly) => pointInPolygon(poly, viewX, viewY));
|
|
109
|
-
});
|
|
110
|
-
}
|
|
96
|
+
const polys = targetViewportId ? viewportClipPolygonsWorld[targetViewportId] : null;
|
|
97
|
+
if (polys && polys.length) {
|
|
98
|
+
const polys2D = polys.map((poly) => poly.map((p) => ({ x: p.x, y: p.z })));
|
|
99
|
+
rows = rows.filter((row) => {
|
|
100
|
+
const rowX = getProp(row, 'x');
|
|
101
|
+
const rowZ = getProp(row, 'z');
|
|
102
|
+
if (!Number.isFinite(rowX) || !Number.isFinite(rowZ)) return false;
|
|
103
|
+
return polys2D.some((poly) => pointInPolygon(poly, Number(rowX), Number(rowZ)));
|
|
104
|
+
});
|
|
111
105
|
}
|
|
112
106
|
}
|
|
113
107
|
|
|
114
108
|
return rows;
|
|
115
109
|
};
|
|
116
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Colors a legend assigned to its rows (assignColors), for consumption by
|
|
113
|
+
* viewport fill rules with `fill: "legend"`. Lookup order for an item:
|
|
114
|
+
* its gid (per-row identity — works without groupBy), then the value of the
|
|
115
|
+
* legend's groupBy property (shared across a group).
|
|
116
|
+
*/
|
|
117
|
+
export interface LegendColorAssignment {
|
|
118
|
+
/** The legend's groupBy property path, when set. */
|
|
119
|
+
groupBy?: string;
|
|
120
|
+
/** Row color by item gid/id. */
|
|
121
|
+
byGid: Map<string, string>;
|
|
122
|
+
/** Row color by groupBy property value (only populated with groupBy). */
|
|
123
|
+
byValue: Map<string, string>;
|
|
124
|
+
}
|
|
125
|
+
|
|
117
126
|
export interface LegendResult {
|
|
118
127
|
svg: string;
|
|
119
|
-
|
|
128
|
+
colors?: LegendColorAssignment;
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
export const buildLegendGroup = (
|
|
@@ -129,36 +138,25 @@ export const buildLegendGroup = (
|
|
|
129
138
|
): LegendResult | null => {
|
|
130
139
|
if (!legend || !legendData || !legendData.length) return null;
|
|
131
140
|
|
|
132
|
-
// Debug: Log raw input data
|
|
133
|
-
// console.log(`[LEGEND INPUT] Received ${legendData.length} items for legend "${legend.id}"`);
|
|
134
|
-
// if (legend.groupBy) {
|
|
135
|
-
// // Count duplicates by groupBy property
|
|
136
|
-
// const counts = new Map<string, number>();
|
|
137
|
-
// for (const item of legendData) {
|
|
138
|
-
// const key = String(resolvePath(item, legend.groupBy) ?? '');
|
|
139
|
-
// counts.set(key, (counts.get(key) || 0) + 1);
|
|
140
|
-
// }
|
|
141
|
-
// console.log('[LEGEND INPUT] Items per group (before grouping):', Array.from(counts.entries()).map(([k, v]) => `${k}: ${v}`).join(', '));
|
|
142
|
-
// }
|
|
143
|
-
|
|
144
141
|
const pageMargin = typeof marginMm === 'number' ? marginMm : 0;
|
|
145
142
|
const fontStyle = (legend.textStyle && styles?.[legend.textStyle]) || styles?.['body'];
|
|
146
143
|
const fontSizeMm = typeof fontStyle?.fontSizeMm === 'number' ? fontStyle.fontSizeMm : 5;
|
|
147
|
-
|
|
148
|
-
|
|
144
|
+
// Escaped once here — every emitted <text>/<rect> below interpolates these
|
|
145
|
+
// (and cell text via escapeText) into XML.
|
|
146
|
+
const fontFamily = escapeAttr(String(fontStyle?.fontFamily ?? 'Arial, sans-serif'));
|
|
147
|
+
const fontColor = escapeAttr(String(isRecord(fontStyle) ? (getProp(fontStyle, 'color') ?? '#000') : '#000'));
|
|
149
148
|
|
|
150
149
|
// Show color column if enabled (but swatches will be empty if no fills configured)
|
|
151
150
|
const showColorColumn = legend.showColorColumn !== false;
|
|
152
151
|
const colorColumnWidthMm = showColorColumn ? (legend.colorColumnWidthMm ?? DEFAULT_LEGEND_SWATCH_WIDTH_MM) : 0;
|
|
153
152
|
const hasFills = fillOpacity !== undefined;
|
|
154
|
-
// console.log(`[LEGEND RENDER] fillOpacity=${fillOpacity}, hasFills=${hasFills}, showColorColumn=${showColorColumn}`);
|
|
155
153
|
const cellPaddingMm = legend.cellPaddingMm ?? DEFAULT_LEGEND_CELL_PADDING_MM;
|
|
156
|
-
const borderStrokeWidthMm = legend.
|
|
157
|
-
const borderStrokeColor = legend.
|
|
158
|
-
const gridStrokeWidthMm = legend.
|
|
159
|
-
const gridStrokeColor = legend.
|
|
160
|
-
const headerStrokeWidthMm = legend.
|
|
161
|
-
const headerStrokeColor = legend.
|
|
154
|
+
const borderStrokeWidthMm = legend.border?.strokeWidthMm ?? DEFAULT_LEGEND_BORDER_WIDTH_MM;
|
|
155
|
+
const borderStrokeColor = escapeAttr(legend.border?.stroke ?? '#000000');
|
|
156
|
+
const gridStrokeWidthMm = legend.grid?.strokeWidthMm ?? DEFAULT_LEGEND_GRID_WIDTH_MM;
|
|
157
|
+
const gridStrokeColor = escapeAttr(legend.grid?.stroke ?? '#000000');
|
|
158
|
+
const headerStrokeWidthMm = legend.header?.strokeWidthMm ?? DEFAULT_LEGEND_HEADER_WIDTH_MM;
|
|
159
|
+
const headerStrokeColor = escapeAttr(legend.header?.stroke ?? '#000000');
|
|
162
160
|
const anchor = legend.anchor ?? 'top-right';
|
|
163
161
|
const legendMarginMm = legend.marginMm ?? DEFAULT_LEGEND_OFFSET_MM;
|
|
164
162
|
|
|
@@ -176,13 +174,6 @@ export const buildLegendGroup = (
|
|
|
176
174
|
groups.get(groupKey)!.push(row);
|
|
177
175
|
}
|
|
178
176
|
|
|
179
|
-
// console.log(`[LEGEND GROUPING] Total input rows: ${legendData.length}, Groups created: ${groups.size}`);
|
|
180
|
-
// for (const [key, rows] of groups.entries()) {
|
|
181
|
-
// if (rows.length > 1) {
|
|
182
|
-
// console.log(`[LEGEND GROUPING] Group "${key}": ${rows.length} rows`);
|
|
183
|
-
// }
|
|
184
|
-
// }
|
|
185
|
-
|
|
186
177
|
// Aggregate each group
|
|
187
178
|
processedData = Array.from(groups.values()).map((groupRows) => {
|
|
188
179
|
if (groupRows.length === 0) return groupRows[0];
|
|
@@ -217,16 +208,6 @@ export const buildLegendGroup = (
|
|
|
217
208
|
return acc + num;
|
|
218
209
|
}, 0);
|
|
219
210
|
|
|
220
|
-
// Debug logging for aggregation
|
|
221
|
-
// if (groupRows.length > 1 && legend.groupBy) {
|
|
222
|
-
// const groupKey = resolvePath(groupRows[0], legend.groupBy);
|
|
223
|
-
// console.log(`[LEGEND AGGREGATION] Group "${groupKey}", Column "${col.property}":`, {
|
|
224
|
-
// itemCount: values.length,
|
|
225
|
-
// values: values,
|
|
226
|
-
// sum: sum
|
|
227
|
-
// });
|
|
228
|
-
// }
|
|
229
|
-
|
|
230
211
|
// Set the aggregated value in the nested property structure
|
|
231
212
|
const pathParts = col.property.split('.');
|
|
232
213
|
let target = aggregated;
|
|
@@ -245,29 +226,28 @@ export const buildLegendGroup = (
|
|
|
245
226
|
});
|
|
246
227
|
}
|
|
247
228
|
|
|
248
|
-
// Generate colors if assignColors is enabled
|
|
229
|
+
// Generate colors if assignColors is enabled — consumed by viewport fill
|
|
230
|
+
// rules with fill: "legend" (byGid works without groupBy: every row gets
|
|
231
|
+
// its own color).
|
|
249
232
|
let generatedColors: string[] | null = null;
|
|
250
|
-
let
|
|
233
|
+
let colors: LegendColorAssignment | undefined;
|
|
251
234
|
|
|
252
235
|
if (legend.assignColors?.enabled) {
|
|
253
236
|
const palette = legend.assignColors.palette ?? 'auto';
|
|
254
237
|
generatedColors = generateLegendColors(processedData.length, palette);
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
colorMap = new Map();
|
|
260
|
-
for (let i = 0; i < processedData.length; i++) {
|
|
261
|
-
const row = processedData[i];
|
|
238
|
+
colors = { groupBy: legend.groupBy || undefined, byGid: new Map(), byValue: new Map() };
|
|
239
|
+
for (let i = 0; i < processedData.length; i++) {
|
|
240
|
+
const row = processedData[i];
|
|
241
|
+
if (legend.groupBy) {
|
|
262
242
|
const groupValue = String(resolvePath(row, legend.groupBy) ?? '');
|
|
263
|
-
if (!
|
|
264
|
-
|
|
265
|
-
// console.log(`[LEGEND COLORS] Mapped "${groupValue}" -> ${generatedColors[i]}`);
|
|
243
|
+
if (!colors.byValue.has(groupValue)) {
|
|
244
|
+
colors.byValue.set(groupValue, generatedColors[i]);
|
|
266
245
|
}
|
|
267
246
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
247
|
+
const gid = getProp(row, 'gid') ?? getProp(row, 'id');
|
|
248
|
+
if (gid !== null && gid !== undefined) {
|
|
249
|
+
colors.byGid.set(String(gid), generatedColors[i]);
|
|
250
|
+
}
|
|
271
251
|
}
|
|
272
252
|
}
|
|
273
253
|
|
|
@@ -405,7 +385,7 @@ export const buildLegendGroup = (
|
|
|
405
385
|
for (let i = 2; i < columnCount; i++) {
|
|
406
386
|
const x = originXmm + colXmm[i];
|
|
407
387
|
lines.push(
|
|
408
|
-
`<line x1="${x}" y1="${originYmm}" x2="${x}" y2="${originYmm + tableHeightMm}" stroke="${gridStrokeColor}" stroke-width="${
|
|
388
|
+
`<line x1="${x}" y1="${originYmm}" x2="${x}" y2="${originYmm + tableHeightMm}" stroke="${gridStrokeColor}" stroke-width="${gridStrokeWidthMm}" />`
|
|
409
389
|
);
|
|
410
390
|
}
|
|
411
391
|
|
|
@@ -415,7 +395,7 @@ export const buildLegendGroup = (
|
|
|
415
395
|
|
|
416
396
|
const y = originYmm + headerHeightMm + r * rowHeightMm;
|
|
417
397
|
lines.push(
|
|
418
|
-
`<line x1="${originXmm}" y1="${y}" x2="${originXmm + tableWidthMm}" y2="${y}" stroke="${gridStrokeColor}" stroke-width="${
|
|
398
|
+
`<line x1="${originXmm}" y1="${y}" x2="${originXmm + tableWidthMm}" y2="${y}" stroke="${gridStrokeColor}" stroke-width="${gridStrokeWidthMm}" />`
|
|
419
399
|
);
|
|
420
400
|
}
|
|
421
401
|
|
|
@@ -433,7 +413,7 @@ export const buildLegendGroup = (
|
|
|
433
413
|
const y = originYmm + headerHeightMm / 2 + textYOffsetMm;
|
|
434
414
|
const anchorText = col.align === 'center' ? 'middle' : col.align === 'right' ? 'end' : 'start';
|
|
435
415
|
texts.push(
|
|
436
|
-
`<text x="${x}" y="${y}" font-family="${fontFamily}" font-size="${fontSizeMm}" fill="${fontColor}" dominant-baseline="middle" text-anchor="${anchorText}">${col.label}</text>`
|
|
416
|
+
`<text x="${x}" y="${y}" font-family="${fontFamily}" font-size="${fontSizeMm}" fill="${fontColor}" dominant-baseline="middle" text-anchor="${anchorText}">${escapeText(col.label)}</text>`
|
|
437
417
|
);
|
|
438
418
|
});
|
|
439
419
|
|
|
@@ -449,7 +429,7 @@ export const buildLegendGroup = (
|
|
|
449
429
|
const swatchFill = row.color; // Already a CSS color string
|
|
450
430
|
const opacityAttr = swatchOpacity < 1 ? ` fill-opacity="${swatchOpacity.toFixed(3)}"` : '';
|
|
451
431
|
rects.push(
|
|
452
|
-
`<rect x="${originXmm + colXmm[0]}" y="${yTop}" width="${colWidthsMm[0]}" height="${rowHeightMm}" fill="${swatchFill}"${opacityAttr} stroke="none" />`
|
|
432
|
+
`<rect x="${originXmm + colXmm[0]}" y="${yTop}" width="${colWidthsMm[0]}" height="${rowHeightMm}" fill="${escapeAttr(String(swatchFill))}"${opacityAttr} stroke="none" />`
|
|
453
433
|
);
|
|
454
434
|
} else {
|
|
455
435
|
// Empty swatch when no fills configured - just the border cell
|
|
@@ -474,7 +454,7 @@ export const buildLegendGroup = (
|
|
|
474
454
|
const y = yTop + rowHeightMm / 2 + textYOffsetMm;
|
|
475
455
|
const anchorText = col.align === 'center' ? 'middle' : col.align === 'right' ? 'end' : 'start';
|
|
476
456
|
texts.push(
|
|
477
|
-
`<text x="${x}" y="${y}" font-family="${fontFamily}" font-size="${fontSizeMm}" fill="${fontColor}" dominant-baseline="middle" text-anchor="${anchorText}">${text}</text>`
|
|
457
|
+
`<text x="${x}" y="${y}" font-family="${fontFamily}" font-size="${fontSizeMm}" fill="${fontColor}" dominant-baseline="middle" text-anchor="${anchorText}">${escapeText(text)}</text>`
|
|
478
458
|
);
|
|
479
459
|
});
|
|
480
460
|
});
|
|
@@ -514,7 +494,7 @@ export const buildLegendGroup = (
|
|
|
514
494
|
const labelX = contentOriginXmm + innerPad;
|
|
515
495
|
const labelY = yTop + rowHeightMm / 2 + textYOffsetMm;
|
|
516
496
|
texts.push(
|
|
517
|
-
`<text x="${labelX}" y="${labelY}" font-family="${fontFamily}" font-size="${fontSizeMm}" fill="${fontColor}" font-weight="bold" dominant-baseline="middle" text-anchor="start">${summaryLabel}</text>`
|
|
497
|
+
`<text x="${labelX}" y="${labelY}" font-family="${fontFamily}" font-size="${fontSizeMm}" fill="${fontColor}" font-weight="bold" dominant-baseline="middle" text-anchor="start">${escapeText(summaryLabel)}</text>`
|
|
518
498
|
);
|
|
519
499
|
|
|
520
500
|
// Render summary values for other columns
|
|
@@ -533,11 +513,11 @@ export const buildLegendGroup = (
|
|
|
533
513
|
const y = yTop + rowHeightMm / 2 + textYOffsetMm;
|
|
534
514
|
const anchorText = col.align === 'center' ? 'middle' : col.align === 'right' ? 'end' : 'start';
|
|
535
515
|
texts.push(
|
|
536
|
-
`<text x="${x}" y="${y}" font-family="${fontFamily}" font-size="${fontSizeMm}" fill="${fontColor}" font-weight="bold" dominant-baseline="middle" text-anchor="${anchorText}">${summaryText}</text>`
|
|
516
|
+
`<text x="${x}" y="${y}" font-family="${fontFamily}" font-size="${fontSizeMm}" fill="${fontColor}" font-weight="bold" dominant-baseline="middle" text-anchor="${anchorText}">${escapeText(summaryText)}</text>`
|
|
537
517
|
);
|
|
538
518
|
});
|
|
539
519
|
}
|
|
540
520
|
|
|
541
521
|
const svg = [`<g id="legend">`, ...rects, ...lines, ...texts, `</g>`].join('');
|
|
542
|
-
return { svg,
|
|
522
|
+
return { svg, colors };
|
|
543
523
|
};
|
package/src/layout/metadata.ts
CHANGED
|
@@ -56,10 +56,15 @@ export const normalizePropertySets = (
|
|
|
56
56
|
const source = isRecord(innerProps) ? innerProps : setValue;
|
|
57
57
|
const props: Record<string, { value: unknown; unit?: string; type?: string }> = {};
|
|
58
58
|
Object.entries(source).forEach(([propName, propVal]) => {
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
// T8 conflict arrays: T8PropertySet.properties values are
|
|
60
|
+
// T8Property | T8Property[] — multiple source files disagreed on
|
|
61
|
+
// the property. Use the first entry so the value stays scalar and
|
|
62
|
+
// filterable rather than leaking an array into `value`.
|
|
63
|
+
const single = Array.isArray(propVal) ? propVal[0] : propVal;
|
|
64
|
+
if (isRecord(single) && 'value' in single) {
|
|
65
|
+
props[propName] = single as { value: unknown; unit?: string; type?: string };
|
|
61
66
|
} else {
|
|
62
|
-
props[propName] = { value:
|
|
67
|
+
props[propName] = { value: single };
|
|
63
68
|
}
|
|
64
69
|
});
|
|
65
70
|
propertySets[setName] = props;
|
|
@@ -131,9 +136,16 @@ export const resolveIncludeClasses = (
|
|
|
131
136
|
} else if (isRecord(item)) {
|
|
132
137
|
const cls = getProp(item, 'class');
|
|
133
138
|
const where = getProp(item, 'where');
|
|
139
|
+
const occludes = getProp(item, 'occludes');
|
|
140
|
+
const classify = getProp(item, 'classify');
|
|
134
141
|
// Must have either class or where (or both)
|
|
135
142
|
if (cls !== undefined || (where && isRecord(where))) {
|
|
136
|
-
result.push({
|
|
143
|
+
result.push({
|
|
144
|
+
class: cls as string | string[] | undefined,
|
|
145
|
+
where: where as WhereCondition,
|
|
146
|
+
...(typeof occludes === 'boolean' ? { occludes } : {}),
|
|
147
|
+
...(typeof classify === 'boolean' ? { classify } : {})
|
|
148
|
+
});
|
|
137
149
|
}
|
|
138
150
|
}
|
|
139
151
|
}
|
|
@@ -164,7 +176,16 @@ export const resolveIncludeClasses = (
|
|
|
164
176
|
|
|
165
177
|
// Filter object: { class: "space", where: {...} }
|
|
166
178
|
if (cls !== undefined || (where && isRecord(where))) {
|
|
167
|
-
|
|
179
|
+
const occludes = getProp(includeClasses, 'occludes');
|
|
180
|
+
const classify = getProp(includeClasses, 'classify');
|
|
181
|
+
return [
|
|
182
|
+
{
|
|
183
|
+
class: cls as string | string[] | undefined,
|
|
184
|
+
where: where as WhereCondition,
|
|
185
|
+
...(typeof occludes === 'boolean' ? { occludes } : {}),
|
|
186
|
+
...(typeof classify === 'boolean' ? { classify } : {})
|
|
187
|
+
}
|
|
188
|
+
];
|
|
168
189
|
}
|
|
169
190
|
}
|
|
170
191
|
|
package/src/layout/model.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { SectionPath, SectionLabel } from '@twinfinity/geometry';
|
|
|
2
2
|
import type { SvgModelView, ViewportElement, LabelStyle, ResolvedIncludeFilter } from './types';
|
|
3
3
|
import type { ModelMetadata } from './metadata';
|
|
4
4
|
import { resolveIncludeClasses, resolveMeta } from './metadata';
|
|
5
|
-
import { normalizeClass, matchesWhere } from './filters';
|
|
5
|
+
import { normalizeClass, classEquals, matchesWhere } from './filters';
|
|
6
6
|
import { escapeAttr, escapeText } from './renderUtils';
|
|
7
7
|
import { BUILTIN_LABEL_DEFAULTS } from './renderDrawing';
|
|
8
8
|
|
|
@@ -33,6 +33,7 @@ export const buildModelViewFromRaw = (
|
|
|
33
33
|
(path.stroke ? ` stroke="${escapeAttr(path.stroke)}"` : '') +
|
|
34
34
|
(path.strokeWidth ? ` stroke-width="${path.strokeWidth}"` : '') +
|
|
35
35
|
(path.strokeOpacity !== undefined ? ` stroke-opacity="${path.strokeOpacity}"` : '') +
|
|
36
|
+
(path.dash ? ` stroke-dasharray="${escapeAttr(path.dash)}"` : '') +
|
|
36
37
|
(path.fillRule ? ` fill-rule="${escapeAttr(path.fillRule)}"` : '') +
|
|
37
38
|
` stroke-linejoin="round" stroke-linecap="round"` +
|
|
38
39
|
` />`
|
|
@@ -102,13 +103,13 @@ const matchesIncludeFilter = (
|
|
|
102
103
|
meta: unknown
|
|
103
104
|
): boolean => {
|
|
104
105
|
for (const filter of includeFilters) {
|
|
105
|
-
// String filter: class name only
|
|
106
|
+
// String filter: class name only. classEquals treats the Ifc prefix
|
|
107
|
+
// as optional — same rule as every other class filter in the format.
|
|
106
108
|
if (typeof filter === 'string') {
|
|
107
109
|
const normalizedFilter = normalizeClass(filter);
|
|
108
|
-
if (cls && normalizedFilter
|
|
110
|
+
if (cls && classEquals(normalizedFilter, cls)) return true;
|
|
109
111
|
if (clsId && typeof clsId === 'string') {
|
|
110
|
-
|
|
111
|
-
if (normalizedId === normalizedFilter) return true;
|
|
112
|
+
if (classEquals(normalizeClass(clsId), normalizedFilter)) return true;
|
|
112
113
|
}
|
|
113
114
|
continue;
|
|
114
115
|
}
|
|
@@ -125,13 +126,12 @@ const matchesIncludeFilter = (
|
|
|
125
126
|
classMatches = false;
|
|
126
127
|
for (const c of classes) {
|
|
127
128
|
const normalizedC = normalizeClass(c);
|
|
128
|
-
if (normalizedC && cls
|
|
129
|
+
if (normalizedC && classEquals(cls, normalizedC)) {
|
|
129
130
|
classMatches = true;
|
|
130
131
|
break;
|
|
131
132
|
}
|
|
132
133
|
if (clsId && typeof clsId === 'string') {
|
|
133
|
-
|
|
134
|
-
if (normalizedId === normalizedC) {
|
|
134
|
+
if (classEquals(normalizeClass(clsId), normalizedC)) {
|
|
135
135
|
classMatches = true;
|
|
136
136
|
break;
|
|
137
137
|
}
|
|
@@ -161,9 +161,10 @@ export const filterModelForViewport = (
|
|
|
161
161
|
highlight?: {
|
|
162
162
|
gids: Set<string> | null;
|
|
163
163
|
styles?: {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
color?: string;
|
|
165
|
+
widthMm?: number;
|
|
166
|
+
opacity?: number;
|
|
167
|
+
dash?: string;
|
|
167
168
|
fill?: string;
|
|
168
169
|
fillOpacity?: number;
|
|
169
170
|
};
|
|
@@ -248,14 +249,17 @@ export const filterModelForViewport = (
|
|
|
248
249
|
// Apply highlight styling when requested
|
|
249
250
|
if (shouldHighlight) {
|
|
250
251
|
const styledPath: SectionPath = { ...p };
|
|
251
|
-
if (highlight?.styles?.
|
|
252
|
-
styledPath.stroke = highlight.styles.
|
|
252
|
+
if (highlight?.styles?.color !== undefined) {
|
|
253
|
+
styledPath.stroke = highlight.styles.color;
|
|
253
254
|
}
|
|
254
|
-
if (highlight?.styles?.
|
|
255
|
-
styledPath.strokeWidth = highlight.styles.
|
|
255
|
+
if (highlight?.styles?.widthMm !== undefined) {
|
|
256
|
+
styledPath.strokeWidth = highlight.styles.widthMm;
|
|
256
257
|
}
|
|
257
|
-
if (highlight?.styles?.
|
|
258
|
-
styledPath.strokeOpacity = highlight.styles.
|
|
258
|
+
if (highlight?.styles?.opacity !== undefined) {
|
|
259
|
+
styledPath.strokeOpacity = highlight.styles.opacity;
|
|
260
|
+
}
|
|
261
|
+
if (highlight?.styles?.dash !== undefined) {
|
|
262
|
+
styledPath.dash = highlight.styles.dash;
|
|
259
263
|
}
|
|
260
264
|
if (highlight?.styles?.fill !== undefined) {
|
|
261
265
|
styledPath.fill = highlight.styles.fill;
|
|
@@ -304,55 +308,6 @@ export const filterModelForViewport = (
|
|
|
304
308
|
filteredLabels.push(l);
|
|
305
309
|
});
|
|
306
310
|
|
|
307
|
-
// Count paths by IFC class before and after filtering
|
|
308
|
-
const classCountsBefore = new Map<string, number>();
|
|
309
|
-
const classCountsAfter = new Map<string, number>();
|
|
310
|
-
const excluded: Array<{ cls: string | null; clsId: unknown }> = [];
|
|
311
|
-
|
|
312
|
-
model.rawPaths.forEach((p: SectionPath) => {
|
|
313
|
-
const rawMeta = getProp(p, 'meta');
|
|
314
|
-
const meta = resolveMeta(rawMeta, metadataById);
|
|
315
|
-
const metaFromMap = (() => {
|
|
316
|
-
const pid =
|
|
317
|
-
getProp(meta, 'productId') ??
|
|
318
|
-
getProp(meta, 'gid') ??
|
|
319
|
-
getProp(meta, 'id') ??
|
|
320
|
-
getProp(rawMeta, 'gid') ??
|
|
321
|
-
getProp(rawMeta, 'id');
|
|
322
|
-
return pid && metadataById ? metadataById[String(pid)] : null;
|
|
323
|
-
})();
|
|
324
|
-
const cls = normalizeClass(getProp(meta, 'ifcClass') ?? getProp(rawMeta, 'ifcClass') ?? metaFromMap?.ifcClass);
|
|
325
|
-
const clsId = getProp(meta, 'ifcClassId') ?? getProp(rawMeta, 'ifcClassId') ?? metaFromMap?.ifcClassId ?? null;
|
|
326
|
-
const key = cls ?? String(clsId) ?? 'unknown';
|
|
327
|
-
classCountsBefore.set(key, (classCountsBefore.get(key) ?? 0) + 1);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
filteredPaths.forEach((p: SectionPath) => {
|
|
331
|
-
const rawMeta = getProp(p, 'meta');
|
|
332
|
-
const meta = resolveMeta(rawMeta, metadataById);
|
|
333
|
-
const metaFromMap = (() => {
|
|
334
|
-
const pid =
|
|
335
|
-
getProp(meta, 'productId') ??
|
|
336
|
-
getProp(meta, 'gid') ??
|
|
337
|
-
getProp(meta, 'id') ??
|
|
338
|
-
getProp(rawMeta, 'gid') ??
|
|
339
|
-
getProp(rawMeta, 'id');
|
|
340
|
-
return pid && metadataById ? metadataById[String(pid)] : null;
|
|
341
|
-
})();
|
|
342
|
-
const cls = normalizeClass(getProp(meta, 'ifcClass') ?? getProp(rawMeta, 'ifcClass') ?? metaFromMap?.ifcClass);
|
|
343
|
-
const clsId = getProp(meta, 'ifcClassId') ?? getProp(rawMeta, 'ifcClassId') ?? metaFromMap?.ifcClassId ?? null;
|
|
344
|
-
const key = cls ?? String(clsId) ?? 'unknown';
|
|
345
|
-
classCountsAfter.set(key, (classCountsAfter.get(key) ?? 0) + 1);
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
// Find excluded classes
|
|
349
|
-
classCountsBefore.forEach((countBefore, cls) => {
|
|
350
|
-
const countAfter = classCountsAfter.get(cls) ?? 0;
|
|
351
|
-
if (countAfter < countBefore) {
|
|
352
|
-
excluded.push({ cls, clsId: `${countBefore - countAfter} paths removed` });
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
|
|
356
311
|
if (
|
|
357
312
|
!styleMutated &&
|
|
358
313
|
filteredPaths.length === model.rawPaths.length &&
|
package/src/layout/options.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { ModelMetadata } from './metadata';
|
|
|
3
3
|
export interface RenderLayoutOptions {
|
|
4
4
|
scaleDenominators?: number[];
|
|
5
5
|
maxViewports?: number;
|
|
6
|
-
|
|
6
|
+
allowRemoteResources?: boolean;
|
|
7
7
|
allowDataUrls?: boolean;
|
|
8
8
|
qrContext?: {
|
|
9
9
|
host?: string | null;
|
|
@@ -11,7 +11,6 @@ export interface RenderLayoutOptions {
|
|
|
11
11
|
floorName?: string | null;
|
|
12
12
|
planeHeight?: number | null;
|
|
13
13
|
};
|
|
14
|
-
legendSources?: Record<string, any[]>;
|
|
15
14
|
/** Optional metadata map keyed by productId referenced from path/label meta */
|
|
16
15
|
metadataById?: ModelMetadata;
|
|
17
16
|
}
|