@twinfinity/printing 5.0.3-ci.28538-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.
Files changed (209) hide show
  1. package/README.md +208 -0
  2. package/dist/BuildInfo.d.ts +25 -0
  3. package/dist/BuildInfo.d.ts.map +1 -0
  4. package/dist/BuildInfo.js +15 -0
  5. package/dist/BuildInfo.js.map +1 -0
  6. package/dist/index.d.ts +7 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +9 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/layout/analyzeTemplate.d.ts +29 -0
  11. package/dist/layout/analyzeTemplate.d.ts.map +1 -0
  12. package/dist/layout/analyzeTemplate.js +168 -0
  13. package/dist/layout/analyzeTemplate.js.map +1 -0
  14. package/dist/layout/clipUtils.d.ts +18 -0
  15. package/dist/layout/clipUtils.d.ts.map +1 -0
  16. package/dist/layout/clipUtils.js +165 -0
  17. package/dist/layout/clipUtils.js.map +1 -0
  18. package/dist/layout/colorConstants.d.ts +11 -0
  19. package/dist/layout/colorConstants.d.ts.map +1 -0
  20. package/dist/layout/colorConstants.js +14 -0
  21. package/dist/layout/colorConstants.js.map +1 -0
  22. package/dist/layout/elementKinds.d.ts +21 -0
  23. package/dist/layout/elementKinds.d.ts.map +1 -0
  24. package/dist/layout/elementKinds.js +17 -0
  25. package/dist/layout/elementKinds.js.map +1 -0
  26. package/dist/layout/exportDrawing.d.ts +143 -0
  27. package/dist/layout/exportDrawing.d.ts.map +1 -0
  28. package/dist/layout/exportDrawing.js +146 -0
  29. package/dist/layout/exportDrawing.js.map +1 -0
  30. package/dist/layout/filters.d.ts +33 -0
  31. package/dist/layout/filters.d.ts.map +1 -0
  32. package/dist/layout/filters.js +77 -0
  33. package/dist/layout/filters.js.map +1 -0
  34. package/dist/layout/geometryConstants.d.ts +8 -0
  35. package/dist/layout/geometryConstants.d.ts.map +1 -0
  36. package/dist/layout/geometryConstants.js +10 -0
  37. package/dist/layout/geometryConstants.js.map +1 -0
  38. package/dist/layout/index.d.ts +86 -0
  39. package/dist/layout/index.d.ts.map +1 -0
  40. package/dist/layout/index.js +363 -0
  41. package/dist/layout/index.js.map +1 -0
  42. package/dist/layout/itemBuilders.d.ts +29 -0
  43. package/dist/layout/itemBuilders.d.ts.map +1 -0
  44. package/dist/layout/itemBuilders.js +135 -0
  45. package/dist/layout/itemBuilders.js.map +1 -0
  46. package/dist/layout/labelsRender.d.ts +29 -0
  47. package/dist/layout/labelsRender.d.ts.map +1 -0
  48. package/dist/layout/labelsRender.js +109 -0
  49. package/dist/layout/labelsRender.js.map +1 -0
  50. package/dist/layout/legendConstants.d.ts +12 -0
  51. package/dist/layout/legendConstants.d.ts.map +1 -0
  52. package/dist/layout/legendConstants.js +12 -0
  53. package/dist/layout/legendConstants.js.map +1 -0
  54. package/dist/layout/legendRender.d.ts +13 -0
  55. package/dist/layout/legendRender.d.ts.map +1 -0
  56. package/dist/layout/legendRender.js +424 -0
  57. package/dist/layout/legendRender.js.map +1 -0
  58. package/dist/layout/legendRows.d.ts +26 -0
  59. package/dist/layout/legendRows.d.ts.map +1 -0
  60. package/dist/layout/legendRows.js +45 -0
  61. package/dist/layout/legendRows.js.map +1 -0
  62. package/dist/layout/legendUtils.d.ts +21 -0
  63. package/dist/layout/legendUtils.d.ts.map +1 -0
  64. package/dist/layout/legendUtils.js +110 -0
  65. package/dist/layout/legendUtils.js.map +1 -0
  66. package/dist/layout/metadata.d.ts +43 -0
  67. package/dist/layout/metadata.d.ts.map +1 -0
  68. package/dist/layout/metadata.js +191 -0
  69. package/dist/layout/metadata.js.map +1 -0
  70. package/dist/layout/model.d.ts +15 -0
  71. package/dist/layout/model.d.ts.map +1 -0
  72. package/dist/layout/model.js +310 -0
  73. package/dist/layout/model.js.map +1 -0
  74. package/dist/layout/options.d.ts +17 -0
  75. package/dist/layout/options.d.ts.map +1 -0
  76. package/dist/layout/options.js +2 -0
  77. package/dist/layout/options.js.map +1 -0
  78. package/dist/layout/presets.d.ts +23 -0
  79. package/dist/layout/presets.d.ts.map +1 -0
  80. package/dist/layout/presets.js +41 -0
  81. package/dist/layout/presets.js.map +1 -0
  82. package/dist/layout/qrRender.d.ts +13 -0
  83. package/dist/layout/qrRender.d.ts.map +1 -0
  84. package/dist/layout/qrRender.js +97 -0
  85. package/dist/layout/qrRender.js.map +1 -0
  86. package/dist/layout/renderDrawing.d.ts +62 -0
  87. package/dist/layout/renderDrawing.d.ts.map +1 -0
  88. package/dist/layout/renderDrawing.js +555 -0
  89. package/dist/layout/renderDrawing.js.map +1 -0
  90. package/dist/layout/renderUtils.d.ts +36 -0
  91. package/dist/layout/renderUtils.d.ts.map +1 -0
  92. package/dist/layout/renderUtils.js +117 -0
  93. package/dist/layout/renderUtils.js.map +1 -0
  94. package/dist/layout/sectionValidation.d.ts +3 -0
  95. package/dist/layout/sectionValidation.d.ts.map +1 -0
  96. package/dist/layout/sectionValidation.js +2 -0
  97. package/dist/layout/sectionValidation.js.map +1 -0
  98. package/dist/layout/templateVariables.d.ts +137 -0
  99. package/dist/layout/templateVariables.d.ts.map +1 -0
  100. package/dist/layout/templateVariables.js +295 -0
  101. package/dist/layout/templateVariables.js.map +1 -0
  102. package/dist/layout/typeGuards.d.ts +5 -0
  103. package/dist/layout/typeGuards.d.ts.map +1 -0
  104. package/dist/layout/typeGuards.js +9 -0
  105. package/dist/layout/typeGuards.js.map +1 -0
  106. package/dist/layout/types.d.ts +816 -0
  107. package/dist/layout/types.d.ts.map +1 -0
  108. package/dist/layout/types.js +2 -0
  109. package/dist/layout/types.js.map +1 -0
  110. package/dist/layout/utils.d.ts +9 -0
  111. package/dist/layout/utils.d.ts.map +1 -0
  112. package/dist/layout/utils.js +84 -0
  113. package/dist/layout/utils.js.map +1 -0
  114. package/dist/layout/validation.d.ts +7 -0
  115. package/dist/layout/validation.d.ts.map +1 -0
  116. package/dist/layout/validation.js +352 -0
  117. package/dist/layout/validation.js.map +1 -0
  118. package/dist/layout/validationHelpers.d.ts +15 -0
  119. package/dist/layout/validationHelpers.d.ts.map +1 -0
  120. package/dist/layout/validationHelpers.js +43 -0
  121. package/dist/layout/validationHelpers.js.map +1 -0
  122. package/dist/layout/validationUtils.d.ts +5 -0
  123. package/dist/layout/validationUtils.d.ts.map +1 -0
  124. package/dist/layout/validationUtils.js +5 -0
  125. package/dist/layout/validationUtils.js.map +1 -0
  126. package/dist/layout/viewportPrep.d.ts +47 -0
  127. package/dist/layout/viewportPrep.d.ts.map +1 -0
  128. package/dist/layout/viewportPrep.js +275 -0
  129. package/dist/layout/viewportPrep.js.map +1 -0
  130. package/dist/layout/viewportRender.d.ts +42 -0
  131. package/dist/layout/viewportRender.d.ts.map +1 -0
  132. package/dist/layout/viewportRender.js +337 -0
  133. package/dist/layout/viewportRender.js.map +1 -0
  134. package/dist/layout/viewportUtils.d.ts +9 -0
  135. package/dist/layout/viewportUtils.d.ts.map +1 -0
  136. package/dist/layout/viewportUtils.js +40 -0
  137. package/dist/layout/viewportUtils.js.map +1 -0
  138. package/dist/layout/warnings.d.ts +2 -0
  139. package/dist/layout/warnings.d.ts.map +1 -0
  140. package/dist/layout/warnings.js +4 -0
  141. package/dist/layout/warnings.js.map +1 -0
  142. package/dist/printToPdf.d.ts +38 -0
  143. package/dist/printToPdf.d.ts.map +1 -0
  144. package/dist/printToPdf.js +79 -0
  145. package/dist/printToPdf.js.map +1 -0
  146. package/dist/printToSvg.d.ts +11 -0
  147. package/dist/printToSvg.d.ts.map +1 -0
  148. package/dist/printToSvg.js +168 -0
  149. package/dist/printToSvg.js.map +1 -0
  150. package/dist/sections.d.ts +118 -0
  151. package/dist/sections.d.ts.map +1 -0
  152. package/dist/sections.js +839 -0
  153. package/dist/sections.js.map +1 -0
  154. package/dist/templates/default-template.json +137 -0
  155. package/dist/types.d.ts +54 -0
  156. package/dist/types.d.ts.map +1 -0
  157. package/dist/types.js +11 -0
  158. package/dist/types.js.map +1 -0
  159. package/package.json +47 -0
  160. package/src/BuildInfo.ts +41 -0
  161. package/src/index.ts +10 -0
  162. package/src/layout/analyzeTemplate.ts +218 -0
  163. package/src/layout/clipUtils.ts +193 -0
  164. package/src/layout/colorConstants.ts +16 -0
  165. package/src/layout/elementKinds.ts +35 -0
  166. package/src/layout/exportDrawing.ts +206 -0
  167. package/src/layout/filters.ts +80 -0
  168. package/src/layout/geometryConstants.ts +11 -0
  169. package/src/layout/index.ts +671 -0
  170. package/src/layout/itemBuilders.ts +159 -0
  171. package/src/layout/labelsRender.ts +170 -0
  172. package/src/layout/legendConstants.ts +11 -0
  173. package/src/layout/legendRender.ts +543 -0
  174. package/src/layout/legendRows.ts +62 -0
  175. package/src/layout/legendUtils.ts +144 -0
  176. package/src/layout/metadata.ts +210 -0
  177. package/src/layout/model.ts +372 -0
  178. package/src/layout/options.ts +17 -0
  179. package/src/layout/presets.ts +51 -0
  180. package/src/layout/qrRender.ts +126 -0
  181. package/src/layout/qrcode.d.ts +2 -0
  182. package/src/layout/renderDrawing.ts +710 -0
  183. package/src/layout/renderUtils.ts +138 -0
  184. package/src/layout/sectionValidation.ts +2 -0
  185. package/src/layout/templateVariables.ts +317 -0
  186. package/src/layout/typeGuards.ts +9 -0
  187. package/src/layout/types.ts +835 -0
  188. package/src/layout/utils.ts +85 -0
  189. package/src/layout/validation.ts +392 -0
  190. package/src/layout/validationHelpers.ts +59 -0
  191. package/src/layout/validationUtils.ts +6 -0
  192. package/src/layout/viewportPrep.ts +351 -0
  193. package/src/layout/viewportRender.ts +442 -0
  194. package/src/layout/viewportUtils.ts +50 -0
  195. package/src/layout/warnings.ts +3 -0
  196. package/src/printToPdf.ts +108 -0
  197. package/src/printToSvg.ts +188 -0
  198. package/src/sections.ts +1019 -0
  199. package/src/templates/default-template.json +137 -0
  200. package/src/types.ts +49 -0
  201. package/templates/default-a0-landscape.json +209 -0
  202. package/templates/default-a0-portrait.json +195 -0
  203. package/templates/default-a1-landscape.json +167 -0
  204. package/templates/default-a1-portrait.json +153 -0
  205. package/templates/default-a3-landscape.json +111 -0
  206. package/templates/default-a3-portrait.json +125 -0
  207. package/templates/default-a4-landscape.json +97 -0
  208. package/templates/default-a4-portrait.json +97 -0
  209. package/templates/template-schema.json +1395 -0
@@ -0,0 +1,310 @@
1
+ import { resolveIncludeClasses, resolveMeta } from './metadata';
2
+ import { normalizeClass, matchesWhere } from './filters';
3
+ import { escapeAttr, escapeText } from './renderUtils';
4
+ import { BUILTIN_LABEL_DEFAULTS } from './renderDrawing';
5
+ import { getProp, isRecord } from './typeGuards';
6
+ export const buildModelViewFromRaw = (rawPaths, rawLabels, transform, geometryTransform, labelStyle) => {
7
+ if (!rawPaths || !transform || !geometryTransform) {
8
+ return null;
9
+ }
10
+ const labelFill = labelStyle?.fill ?? BUILTIN_LABEL_DEFAULTS.fill;
11
+ const labelFontFamily = labelStyle?.fontFamily ?? BUILTIN_LABEL_DEFAULTS.fontFamily;
12
+ const labelFontWeight = labelStyle?.fontWeight ?? BUILTIN_LABEL_DEFAULTS.fontWeight;
13
+ const escapedFontFamily = escapeAttr(labelFontFamily);
14
+ const parts = [];
15
+ parts.push(`<g transform="${escapeAttr(geometryTransform)}">`);
16
+ rawPaths.forEach((path) => {
17
+ parts.push(`<path d="${escapeAttr(path.d)}"` +
18
+ (path.fill ? ` fill="${escapeAttr(path.fill)}"` : ' fill="none"') +
19
+ (path.fillOpacity !== undefined ? ` fill-opacity="${path.fillOpacity}"` : '') +
20
+ (path.stroke ? ` stroke="${escapeAttr(path.stroke)}"` : '') +
21
+ (path.strokeWidth ? ` stroke-width="${path.strokeWidth}"` : '') +
22
+ (path.strokeOpacity !== undefined ? ` stroke-opacity="${path.strokeOpacity}"` : '') +
23
+ (path.fillRule ? ` fill-rule="${escapeAttr(path.fillRule)}"` : '') +
24
+ ` stroke-linejoin="round" stroke-linecap="round"` +
25
+ ` />`);
26
+ });
27
+ parts.push(`</g>`);
28
+ if (rawLabels && rawLabels.length) {
29
+ parts.push(`<g pointer-events="none">`);
30
+ rawLabels.forEach((label) => {
31
+ const viewX = label.x - transform.viewMinX;
32
+ const viewY = transform.viewMaxZ - label.z;
33
+ if (!Number.isFinite(viewX) || !Number.isFinite(viewY)) {
34
+ return;
35
+ }
36
+ parts.push(`<text x="${viewX}" y="${viewY}" font-size="${label.fontSize}" text-anchor="middle" dominant-baseline="middle" fill="${escapeAttr(labelFill)}" font-family="${escapedFontFamily}" font-weight="${labelFontWeight}">${escapeText(label.text)}</text>`);
37
+ });
38
+ parts.push(`</g>`);
39
+ }
40
+ const svg = `<g>${parts.join('')}</g>`;
41
+ return {
42
+ groupSvg: svg,
43
+ widthModel: transform.viewWidth,
44
+ heightModel: transform.viewHeight,
45
+ transform,
46
+ geometryTransform,
47
+ rawPaths,
48
+ rawLabels,
49
+ labelStyle
50
+ };
51
+ };
52
+ /**
53
+ * Check whether a path's resolved class matches any of the filter classes.
54
+ * Used to determine which SVG paths are subject to GID-based include/exclude.
55
+ */
56
+ const matchesFilterClass = (cls, clsId, clsFromMap, filterClasses) => {
57
+ if (!filterClasses.size)
58
+ return false;
59
+ if (cls && filterClasses.has(cls))
60
+ return true;
61
+ if (clsFromMap && filterClasses.has(clsFromMap))
62
+ return true;
63
+ if (clsId && typeof clsId === 'string') {
64
+ const normalized = normalizeClass(clsId);
65
+ if (normalized && filterClasses.has(normalized))
66
+ return true;
67
+ }
68
+ return false;
69
+ };
70
+ /**
71
+ * Check if an item matches any of the include filters.
72
+ *
73
+ * @param includeFilters - Array of filters (strings or class+where objects)
74
+ * @param cls - Normalized IFC class name
75
+ * @param clsId - Predefined IFC class ID
76
+ * @param meta - Item metadata (for property matching)
77
+ * @returns true if the item matches any filter, false otherwise
78
+ */
79
+ const matchesIncludeFilter = (includeFilters, cls, clsId, meta) => {
80
+ for (const filter of includeFilters) {
81
+ // String filter: class name only
82
+ if (typeof filter === 'string') {
83
+ const normalizedFilter = normalizeClass(filter);
84
+ if (cls && normalizedFilter === cls)
85
+ return true;
86
+ if (clsId && typeof clsId === 'string') {
87
+ const normalizedId = normalizeClass(clsId);
88
+ if (normalizedId === normalizedFilter)
89
+ return true;
90
+ }
91
+ continue;
92
+ }
93
+ // Object filter: class + where
94
+ if (isRecord(filter)) {
95
+ const filterClass = filter.class;
96
+ const filterWhere = filter.where;
97
+ // Check class match (if specified)
98
+ let classMatches = true;
99
+ if (filterClass !== undefined) {
100
+ const classes = Array.isArray(filterClass) ? filterClass : [filterClass];
101
+ classMatches = false;
102
+ for (const c of classes) {
103
+ const normalizedC = normalizeClass(c);
104
+ if (normalizedC && cls === normalizedC) {
105
+ classMatches = true;
106
+ break;
107
+ }
108
+ if (clsId && typeof clsId === 'string') {
109
+ const normalizedId = normalizeClass(clsId);
110
+ if (normalizedId === normalizedC) {
111
+ classMatches = true;
112
+ break;
113
+ }
114
+ }
115
+ }
116
+ }
117
+ // Check where condition (if specified)
118
+ let whereMatches = true;
119
+ if (filterWhere && Object.keys(filterWhere).length > 0) {
120
+ whereMatches = matchesWhere(meta, filterWhere);
121
+ }
122
+ if (classMatches && whereMatches)
123
+ return true;
124
+ }
125
+ }
126
+ return false;
127
+ };
128
+ export const filterModelForViewport = (model, vp, metadataById, includePresets, allowedGids, highlight, filterClasses) => {
129
+ const resolvedIncludeClasses = resolveIncludeClasses(vp.includeClasses, includePresets ?? {});
130
+ const includeFilters = resolvedIncludeClasses ?? null;
131
+ // Classes subject to GID-based filtering (highlight/clip include/exclude).
132
+ // Derived from template highlight.class and clip.class by the caller.
133
+ const effectiveFilterClasses = filterClasses ?? new Set();
134
+ let styleMutated = false;
135
+ if (!model.rawPaths || !model.geometryTransform || !model.transform) {
136
+ return model;
137
+ }
138
+ const filteredPaths = [];
139
+ model.rawPaths.forEach((p) => {
140
+ const rawMeta = getProp(p, 'meta');
141
+ const meta = resolveMeta(rawMeta, metadataById);
142
+ const metaFromMap = (() => {
143
+ const pid = getProp(meta, 'productId') ??
144
+ getProp(meta, 'gid') ??
145
+ getProp(meta, 'id') ??
146
+ getProp(rawMeta, 'gid') ??
147
+ getProp(rawMeta, 'id');
148
+ return pid && metadataById ? metadataById[String(pid)] : null;
149
+ })();
150
+ const productIdForMeta = getProp(meta, 'productId') ??
151
+ getProp(meta, 'gid') ??
152
+ getProp(meta, 'id') ??
153
+ getProp(rawMeta, 'gid') ??
154
+ getProp(rawMeta, 'id') ??
155
+ metaFromMap?.productId ??
156
+ metaFromMap?.gid ??
157
+ null;
158
+ const pid = productIdForMeta ? String(productIdForMeta) : null;
159
+ const cls = normalizeClass(getProp(meta, 'ifcClass') ?? getProp(rawMeta, 'ifcClass') ?? metaFromMap?.ifcClass);
160
+ const clsId = getProp(meta, 'ifcClassId') ?? getProp(rawMeta, 'ifcClassId') ?? metaFromMap?.ifcClassId ?? null;
161
+ const clsFromMap = normalizeClass(metaFromMap?.ifcClass);
162
+ const isFilterable = matchesFilterClass(cls, clsId, clsFromMap, effectiveFilterClasses);
163
+ const shouldHighlight = !!highlight?.gids && highlight.gids.size > 0 && !!pid && highlight.gids.has(pid);
164
+ if (includeFilters && includeFilters.length > 0) {
165
+ if (isFilterable) {
166
+ // Highlight/clip class: only check includeClasses if no GID filtering will be applied
167
+ const hasGidFiltering = (highlight?.gids && highlight.gids.size > 0) || (allowedGids && allowedGids.size > 0);
168
+ if (!hasGidFiltering) {
169
+ // No GID filtering active, so apply includeClasses filter
170
+ const matches = matchesIncludeFilter(includeFilters, cls, clsId, meta);
171
+ if (!matches) {
172
+ return;
173
+ }
174
+ }
175
+ }
176
+ else {
177
+ // Not a highlight/clip class: always apply includeClasses filter
178
+ const matches = matchesIncludeFilter(includeFilters, cls, clsId, meta);
179
+ if (!matches) {
180
+ return;
181
+ }
182
+ }
183
+ }
184
+ if (isFilterable) {
185
+ if (highlight?.gids && highlight.gids.size > 0) {
186
+ if (!pid || !highlight.gids.has(pid)) {
187
+ return;
188
+ }
189
+ }
190
+ // Clip filtering logic: null means no filtering, empty Set means filtering active but no matches
191
+ const allowed = !allowedGids || (allowedGids.size > 0 && !!(pid && allowedGids.has(pid))) || shouldHighlight;
192
+ if (!allowed)
193
+ return;
194
+ }
195
+ // CRITICAL: Exclude space fills from model - buildFillSvg will handle them with matchClipFilter support
196
+ // Only keep space segments (outlines) in the model
197
+ // EXCEPT: Keep highlighted space fills so they can be styled with highlight colors
198
+ const isFill = getProp(meta, 'isFill') ?? getProp(rawMeta, 'isFill');
199
+ if (isFilterable && isFill && !shouldHighlight) {
200
+ return;
201
+ }
202
+ // Apply highlight styling when requested
203
+ if (shouldHighlight) {
204
+ const styledPath = { ...p };
205
+ if (highlight?.styles?.stroke !== undefined) {
206
+ styledPath.stroke = highlight.styles.stroke;
207
+ }
208
+ if (highlight?.styles?.strokeWidthMm !== undefined) {
209
+ styledPath.strokeWidth = highlight.styles.strokeWidthMm;
210
+ }
211
+ if (highlight?.styles?.strokeOpacity !== undefined) {
212
+ styledPath.strokeOpacity = highlight.styles.strokeOpacity;
213
+ }
214
+ if (highlight?.styles?.fill !== undefined) {
215
+ styledPath.fill = highlight.styles.fill;
216
+ }
217
+ if (highlight?.styles?.fillOpacity !== undefined) {
218
+ styledPath.fillOpacity = highlight.styles.fillOpacity;
219
+ }
220
+ styleMutated = true;
221
+ filteredPaths.push(styledPath);
222
+ }
223
+ else {
224
+ filteredPaths.push(p);
225
+ }
226
+ });
227
+ const filteredLabels = [];
228
+ (model.rawLabels ?? []).forEach((l) => {
229
+ const rawMeta = getProp(l, 'meta');
230
+ const meta = resolveMeta(rawMeta, metadataById);
231
+ const cls = normalizeClass(getProp(meta, 'ifcClass'));
232
+ const clsId = getProp(meta, 'ifcClassId') ?? getProp(rawMeta, 'ifcClassId') ?? null;
233
+ const productIdForMeta = getProp(meta, 'productId') ??
234
+ getProp(meta, 'gid') ??
235
+ getProp(meta, 'id') ??
236
+ getProp(rawMeta, 'gid') ??
237
+ getProp(rawMeta, 'id') ??
238
+ null;
239
+ if (includeFilters && includeFilters.length > 0) {
240
+ if (!matchesIncludeFilter(includeFilters, cls, clsId, meta)) {
241
+ return;
242
+ }
243
+ }
244
+ if (matchesFilterClass(cls, clsId, null, effectiveFilterClasses)) {
245
+ const pid = productIdForMeta ? String(productIdForMeta) : null;
246
+ if (highlight?.gids && highlight.gids.size > 0) {
247
+ if (!pid || !highlight.gids.has(pid)) {
248
+ return;
249
+ }
250
+ }
251
+ if (allowedGids && allowedGids.size > 0) {
252
+ if (!pid || !allowedGids.has(pid)) {
253
+ return;
254
+ }
255
+ }
256
+ }
257
+ filteredLabels.push(l);
258
+ });
259
+ // Count paths by IFC class before and after filtering
260
+ const classCountsBefore = new Map();
261
+ const classCountsAfter = new Map();
262
+ const excluded = [];
263
+ model.rawPaths.forEach((p) => {
264
+ const rawMeta = getProp(p, 'meta');
265
+ const meta = resolveMeta(rawMeta, metadataById);
266
+ const metaFromMap = (() => {
267
+ const pid = getProp(meta, 'productId') ??
268
+ getProp(meta, 'gid') ??
269
+ getProp(meta, 'id') ??
270
+ getProp(rawMeta, 'gid') ??
271
+ getProp(rawMeta, 'id');
272
+ return pid && metadataById ? metadataById[String(pid)] : null;
273
+ })();
274
+ const cls = normalizeClass(getProp(meta, 'ifcClass') ?? getProp(rawMeta, 'ifcClass') ?? metaFromMap?.ifcClass);
275
+ const clsId = getProp(meta, 'ifcClassId') ?? getProp(rawMeta, 'ifcClassId') ?? metaFromMap?.ifcClassId ?? null;
276
+ const key = cls ?? String(clsId) ?? 'unknown';
277
+ classCountsBefore.set(key, (classCountsBefore.get(key) ?? 0) + 1);
278
+ });
279
+ filteredPaths.forEach((p) => {
280
+ const rawMeta = getProp(p, 'meta');
281
+ const meta = resolveMeta(rawMeta, metadataById);
282
+ const metaFromMap = (() => {
283
+ const pid = getProp(meta, 'productId') ??
284
+ getProp(meta, 'gid') ??
285
+ getProp(meta, 'id') ??
286
+ getProp(rawMeta, 'gid') ??
287
+ getProp(rawMeta, 'id');
288
+ return pid && metadataById ? metadataById[String(pid)] : null;
289
+ })();
290
+ const cls = normalizeClass(getProp(meta, 'ifcClass') ?? getProp(rawMeta, 'ifcClass') ?? metaFromMap?.ifcClass);
291
+ const clsId = getProp(meta, 'ifcClassId') ?? getProp(rawMeta, 'ifcClassId') ?? metaFromMap?.ifcClassId ?? null;
292
+ const key = cls ?? String(clsId) ?? 'unknown';
293
+ classCountsAfter.set(key, (classCountsAfter.get(key) ?? 0) + 1);
294
+ });
295
+ // Find excluded classes
296
+ classCountsBefore.forEach((countBefore, cls) => {
297
+ const countAfter = classCountsAfter.get(cls) ?? 0;
298
+ if (countAfter < countBefore) {
299
+ excluded.push({ cls, clsId: `${countBefore - countAfter} paths removed` });
300
+ }
301
+ });
302
+ if (!styleMutated &&
303
+ filteredPaths.length === model.rawPaths.length &&
304
+ filteredLabels.length === (model.rawLabels?.length ?? 0)) {
305
+ return model;
306
+ }
307
+ const rebuilt = buildModelViewFromRaw(filteredPaths, filteredLabels, model.transform, model.geometryTransform, model.labelStyle);
308
+ return rebuilt ?? model;
309
+ };
310
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"/","sources":["layout/model.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SectionPath, SectionLabel } from '@twinfinity/geometry';\r\nimport type { SvgModelView, ViewportElement, LabelStyle, ResolvedIncludeFilter } from './types';\r\nimport type { ModelMetadata } from './metadata';\r\nimport { resolveIncludeClasses, resolveMeta } from './metadata';\r\nimport { normalizeClass, matchesWhere } from './filters';\r\nimport { escapeAttr, escapeText } from './renderUtils';\r\nimport { BUILTIN_LABEL_DEFAULTS } from './renderDrawing';\r\n\r\nimport { getProp, isRecord } from './typeGuards';\r\n\r\nexport const buildModelViewFromRaw = (\r\n rawPaths: SectionPath[] | undefined,\r\n rawLabels: SectionLabel[] | undefined,\r\n transform: SvgModelView['transform'] | undefined,\r\n geometryTransform?: string,\r\n labelStyle?: LabelStyle\r\n): SvgModelView | null => {\r\n if (!rawPaths || !transform || !geometryTransform) {\r\n return null;\r\n }\r\n const labelFill = labelStyle?.fill ?? BUILTIN_LABEL_DEFAULTS.fill;\r\n const labelFontFamily = labelStyle?.fontFamily ?? BUILTIN_LABEL_DEFAULTS.fontFamily;\r\n const labelFontWeight = labelStyle?.fontWeight ?? BUILTIN_LABEL_DEFAULTS.fontWeight;\r\n const escapedFontFamily = escapeAttr(labelFontFamily);\r\n\r\n const parts: string[] = [];\r\n parts.push(`<g transform=\"${escapeAttr(geometryTransform)}\">`);\r\n rawPaths.forEach((path) => {\r\n parts.push(\r\n `<path d=\"${escapeAttr(path.d)}\"` +\r\n (path.fill ? ` fill=\"${escapeAttr(path.fill)}\"` : ' fill=\"none\"') +\r\n (path.fillOpacity !== undefined ? ` fill-opacity=\"${path.fillOpacity}\"` : '') +\r\n (path.stroke ? ` stroke=\"${escapeAttr(path.stroke)}\"` : '') +\r\n (path.strokeWidth ? ` stroke-width=\"${path.strokeWidth}\"` : '') +\r\n (path.strokeOpacity !== undefined ? ` stroke-opacity=\"${path.strokeOpacity}\"` : '') +\r\n (path.fillRule ? ` fill-rule=\"${escapeAttr(path.fillRule)}\"` : '') +\r\n ` stroke-linejoin=\"round\" stroke-linecap=\"round\"` +\r\n ` />`\r\n );\r\n });\r\n parts.push(`</g>`);\r\n if (rawLabels && rawLabels.length) {\r\n parts.push(`<g pointer-events=\"none\">`);\r\n rawLabels.forEach((label) => {\r\n const viewX = label.x - transform.viewMinX;\r\n const viewY = transform.viewMaxZ - label.z;\r\n if (!Number.isFinite(viewX) || !Number.isFinite(viewY)) {\r\n return;\r\n }\r\n parts.push(\r\n `<text x=\"${viewX}\" y=\"${viewY}\" font-size=\"${label.fontSize}\" text-anchor=\"middle\" dominant-baseline=\"middle\" fill=\"${escapeAttr(labelFill)}\" font-family=\"${escapedFontFamily}\" font-weight=\"${labelFontWeight}\">${escapeText(label.text)}</text>`\r\n );\r\n });\r\n parts.push(`</g>`);\r\n }\r\n const svg = `<g>${parts.join('')}</g>`;\r\n return {\r\n groupSvg: svg,\r\n widthModel: transform.viewWidth,\r\n heightModel: transform.viewHeight,\r\n transform,\r\n geometryTransform,\r\n rawPaths,\r\n rawLabels,\r\n labelStyle\r\n };\r\n};\r\n\r\n/**\r\n * Check whether a path's resolved class matches any of the filter classes.\r\n * Used to determine which SVG paths are subject to GID-based include/exclude.\r\n */\r\nconst matchesFilterClass = (\r\n cls: string | null,\r\n clsId: unknown,\r\n clsFromMap: string | null,\r\n filterClasses: Set<string>\r\n): boolean => {\r\n if (!filterClasses.size) return false;\r\n if (cls && filterClasses.has(cls)) return true;\r\n if (clsFromMap && filterClasses.has(clsFromMap)) return true;\r\n if (clsId && typeof clsId === 'string') {\r\n const normalized = normalizeClass(clsId);\r\n if (normalized && filterClasses.has(normalized)) return true;\r\n }\r\n return false;\r\n};\r\n\r\n/**\r\n * Check if an item matches any of the include filters.\r\n *\r\n * @param includeFilters - Array of filters (strings or class+where objects)\r\n * @param cls - Normalized IFC class name\r\n * @param clsId - Predefined IFC class ID\r\n * @param meta - Item metadata (for property matching)\r\n * @returns true if the item matches any filter, false otherwise\r\n */\r\nconst matchesIncludeFilter = (\r\n includeFilters: ResolvedIncludeFilter[],\r\n cls: string | null,\r\n clsId: unknown,\r\n meta: unknown\r\n): boolean => {\r\n for (const filter of includeFilters) {\r\n // String filter: class name only\r\n if (typeof filter === 'string') {\r\n const normalizedFilter = normalizeClass(filter);\r\n if (cls && normalizedFilter === cls) return true;\r\n if (clsId && typeof clsId === 'string') {\r\n const normalizedId = normalizeClass(clsId);\r\n if (normalizedId === normalizedFilter) return true;\r\n }\r\n continue;\r\n }\r\n\r\n // Object filter: class + where\r\n if (isRecord(filter)) {\r\n const filterClass = filter.class;\r\n const filterWhere = filter.where;\r\n\r\n // Check class match (if specified)\r\n let classMatches = true;\r\n if (filterClass !== undefined) {\r\n const classes = Array.isArray(filterClass) ? filterClass : [filterClass];\r\n classMatches = false;\r\n for (const c of classes) {\r\n const normalizedC = normalizeClass(c);\r\n if (normalizedC && cls === normalizedC) {\r\n classMatches = true;\r\n break;\r\n }\r\n if (clsId && typeof clsId === 'string') {\r\n const normalizedId = normalizeClass(clsId);\r\n if (normalizedId === normalizedC) {\r\n classMatches = true;\r\n break;\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Check where condition (if specified)\r\n let whereMatches = true;\r\n if (filterWhere && Object.keys(filterWhere).length > 0) {\r\n whereMatches = matchesWhere(meta, filterWhere);\r\n }\r\n\r\n if (classMatches && whereMatches) return true;\r\n }\r\n }\r\n\r\n return false;\r\n};\r\n\r\nexport const filterModelForViewport = (\r\n model: SvgModelView,\r\n vp: ViewportElement,\r\n metadataById?: ModelMetadata,\r\n includePresets?: Record<string, string[]>,\r\n allowedGids?: Set<string> | null,\r\n highlight?: {\r\n gids: Set<string> | null;\r\n styles?: {\r\n stroke?: string;\r\n strokeWidthMm?: number;\r\n strokeOpacity?: number;\r\n fill?: string;\r\n fillOpacity?: number;\r\n };\r\n },\r\n filterClasses?: Set<string> | null\r\n): SvgModelView => {\r\n const resolvedIncludeClasses = resolveIncludeClasses(vp.includeClasses as unknown, includePresets ?? {});\r\n const includeFilters = resolvedIncludeClasses ?? null;\r\n // Classes subject to GID-based filtering (highlight/clip include/exclude).\r\n // Derived from template highlight.class and clip.class by the caller.\r\n const effectiveFilterClasses = filterClasses ?? new Set<string>();\r\n let styleMutated = false;\r\n if (!model.rawPaths || !model.geometryTransform || !model.transform) {\r\n return model;\r\n }\r\n const filteredPaths: SectionPath[] = [];\r\n model.rawPaths.forEach((p: SectionPath) => {\r\n const rawMeta = getProp(p, 'meta');\r\n const meta = resolveMeta(rawMeta, metadataById);\r\n const metaFromMap = (() => {\r\n const pid =\r\n getProp(meta, 'productId') ??\r\n getProp(meta, 'gid') ??\r\n getProp(meta, 'id') ??\r\n getProp(rawMeta, 'gid') ??\r\n getProp(rawMeta, 'id');\r\n return pid && metadataById ? metadataById[String(pid)] : null;\r\n })();\r\n const productIdForMeta =\r\n getProp(meta, 'productId') ??\r\n getProp(meta, 'gid') ??\r\n getProp(meta, 'id') ??\r\n getProp(rawMeta, 'gid') ??\r\n getProp(rawMeta, 'id') ??\r\n metaFromMap?.productId ??\r\n metaFromMap?.gid ??\r\n null;\r\n const pid = productIdForMeta ? String(productIdForMeta) : null;\r\n const cls = normalizeClass(getProp(meta, 'ifcClass') ?? getProp(rawMeta, 'ifcClass') ?? metaFromMap?.ifcClass);\r\n const clsId = getProp(meta, 'ifcClassId') ?? getProp(rawMeta, 'ifcClassId') ?? metaFromMap?.ifcClassId ?? null;\r\n const clsFromMap = normalizeClass(metaFromMap?.ifcClass);\r\n const isFilterable = matchesFilterClass(cls, clsId, clsFromMap, effectiveFilterClasses);\r\n const shouldHighlight = !!highlight?.gids && highlight.gids.size > 0 && !!pid && highlight.gids.has(pid);\r\n if (includeFilters && includeFilters.length > 0) {\r\n if (isFilterable) {\r\n // Highlight/clip class: only check includeClasses if no GID filtering will be applied\r\n const hasGidFiltering =\r\n (highlight?.gids && highlight.gids.size > 0) || (allowedGids && allowedGids.size > 0);\r\n if (!hasGidFiltering) {\r\n // No GID filtering active, so apply includeClasses filter\r\n const matches = matchesIncludeFilter(includeFilters, cls, clsId, meta);\r\n if (!matches) {\r\n return;\r\n }\r\n }\r\n } else {\r\n // Not a highlight/clip class: always apply includeClasses filter\r\n const matches = matchesIncludeFilter(includeFilters, cls, clsId, meta);\r\n if (!matches) {\r\n return;\r\n }\r\n }\r\n }\r\n if (isFilterable) {\r\n if (highlight?.gids && highlight.gids.size > 0) {\r\n if (!pid || !highlight.gids.has(pid)) {\r\n return;\r\n }\r\n }\r\n // Clip filtering logic: null means no filtering, empty Set means filtering active but no matches\r\n const allowed =\r\n !allowedGids || (allowedGids.size > 0 && !!(pid && allowedGids.has(pid))) || shouldHighlight;\r\n if (!allowed) return;\r\n }\r\n // CRITICAL: Exclude space fills from model - buildFillSvg will handle them with matchClipFilter support\r\n // Only keep space segments (outlines) in the model\r\n // EXCEPT: Keep highlighted space fills so they can be styled with highlight colors\r\n const isFill = getProp(meta, 'isFill') ?? getProp(rawMeta, 'isFill');\r\n if (isFilterable && isFill && !shouldHighlight) {\r\n return;\r\n }\r\n // Apply highlight styling when requested\r\n if (shouldHighlight) {\r\n const styledPath: SectionPath = { ...p };\r\n if (highlight?.styles?.stroke !== undefined) {\r\n styledPath.stroke = highlight.styles.stroke;\r\n }\r\n if (highlight?.styles?.strokeWidthMm !== undefined) {\r\n styledPath.strokeWidth = highlight.styles.strokeWidthMm;\r\n }\r\n if (highlight?.styles?.strokeOpacity !== undefined) {\r\n styledPath.strokeOpacity = highlight.styles.strokeOpacity;\r\n }\r\n if (highlight?.styles?.fill !== undefined) {\r\n styledPath.fill = highlight.styles.fill;\r\n }\r\n if (highlight?.styles?.fillOpacity !== undefined) {\r\n styledPath.fillOpacity = highlight.styles.fillOpacity;\r\n }\r\n styleMutated = true;\r\n filteredPaths.push(styledPath);\r\n } else {\r\n filteredPaths.push(p);\r\n }\r\n });\r\n\r\n const filteredLabels: SectionLabel[] = [];\r\n (model.rawLabels ?? []).forEach((l: SectionLabel) => {\r\n const rawMeta = getProp(l, 'meta');\r\n const meta = resolveMeta(rawMeta, metadataById);\r\n const cls = normalizeClass(getProp(meta, 'ifcClass'));\r\n const clsId = getProp(meta, 'ifcClassId') ?? getProp(rawMeta, 'ifcClassId') ?? null;\r\n const productIdForMeta =\r\n getProp(meta, 'productId') ??\r\n getProp(meta, 'gid') ??\r\n getProp(meta, 'id') ??\r\n getProp(rawMeta, 'gid') ??\r\n getProp(rawMeta, 'id') ??\r\n null;\r\n if (includeFilters && includeFilters.length > 0) {\r\n if (!matchesIncludeFilter(includeFilters, cls, clsId, meta)) {\r\n return;\r\n }\r\n }\r\n if (matchesFilterClass(cls, clsId, null, effectiveFilterClasses)) {\r\n const pid = productIdForMeta ? String(productIdForMeta) : null;\r\n if (highlight?.gids && highlight.gids.size > 0) {\r\n if (!pid || !highlight.gids.has(pid)) {\r\n return;\r\n }\r\n }\r\n if (allowedGids && allowedGids.size > 0) {\r\n if (!pid || !allowedGids.has(pid)) {\r\n return;\r\n }\r\n }\r\n }\r\n filteredLabels.push(l);\r\n });\r\n\r\n // Count paths by IFC class before and after filtering\r\n const classCountsBefore = new Map<string, number>();\r\n const classCountsAfter = new Map<string, number>();\r\n const excluded: Array<{ cls: string | null; clsId: unknown }> = [];\r\n\r\n model.rawPaths.forEach((p: SectionPath) => {\r\n const rawMeta = getProp(p, 'meta');\r\n const meta = resolveMeta(rawMeta, metadataById);\r\n const metaFromMap = (() => {\r\n const pid =\r\n getProp(meta, 'productId') ??\r\n getProp(meta, 'gid') ??\r\n getProp(meta, 'id') ??\r\n getProp(rawMeta, 'gid') ??\r\n getProp(rawMeta, 'id');\r\n return pid && metadataById ? metadataById[String(pid)] : null;\r\n })();\r\n const cls = normalizeClass(getProp(meta, 'ifcClass') ?? getProp(rawMeta, 'ifcClass') ?? metaFromMap?.ifcClass);\r\n const clsId = getProp(meta, 'ifcClassId') ?? getProp(rawMeta, 'ifcClassId') ?? metaFromMap?.ifcClassId ?? null;\r\n const key = cls ?? String(clsId) ?? 'unknown';\r\n classCountsBefore.set(key, (classCountsBefore.get(key) ?? 0) + 1);\r\n });\r\n\r\n filteredPaths.forEach((p: SectionPath) => {\r\n const rawMeta = getProp(p, 'meta');\r\n const meta = resolveMeta(rawMeta, metadataById);\r\n const metaFromMap = (() => {\r\n const pid =\r\n getProp(meta, 'productId') ??\r\n getProp(meta, 'gid') ??\r\n getProp(meta, 'id') ??\r\n getProp(rawMeta, 'gid') ??\r\n getProp(rawMeta, 'id');\r\n return pid && metadataById ? metadataById[String(pid)] : null;\r\n })();\r\n const cls = normalizeClass(getProp(meta, 'ifcClass') ?? getProp(rawMeta, 'ifcClass') ?? metaFromMap?.ifcClass);\r\n const clsId = getProp(meta, 'ifcClassId') ?? getProp(rawMeta, 'ifcClassId') ?? metaFromMap?.ifcClassId ?? null;\r\n const key = cls ?? String(clsId) ?? 'unknown';\r\n classCountsAfter.set(key, (classCountsAfter.get(key) ?? 0) + 1);\r\n });\r\n\r\n // Find excluded classes\r\n classCountsBefore.forEach((countBefore, cls) => {\r\n const countAfter = classCountsAfter.get(cls) ?? 0;\r\n if (countAfter < countBefore) {\r\n excluded.push({ cls, clsId: `${countBefore - countAfter} paths removed` });\r\n }\r\n });\r\n\r\n if (\r\n !styleMutated &&\r\n filteredPaths.length === model.rawPaths.length &&\r\n filteredLabels.length === (model.rawLabels?.length ?? 0)\r\n ) {\r\n return model;\r\n }\r\n\r\n const rebuilt = buildModelViewFromRaw(\r\n filteredPaths,\r\n filteredLabels,\r\n model.transform,\r\n model.geometryTransform,\r\n model.labelStyle\r\n );\r\n return rebuilt ?? model;\r\n};\r\n"]}
@@ -0,0 +1,17 @@
1
+ import { ModelMetadata } from './metadata';
2
+ export interface RenderLayoutOptions {
3
+ scaleDenominators?: number[];
4
+ maxViewports?: number;
5
+ allowRemoteAssets?: boolean;
6
+ allowDataUrls?: boolean;
7
+ qrContext?: {
8
+ host?: string | null;
9
+ containerName?: string | null;
10
+ floorName?: string | null;
11
+ planeHeight?: number | null;
12
+ };
13
+ legendSources?: Record<string, any[]>;
14
+ /** Optional metadata map keyed by productId referenced from path/label meta */
15
+ metadataById?: ModelMetadata;
16
+ }
17
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"/","sources":["layout/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,WAAW,mBAAmB;IAChC,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC/B,CAAC;IACF,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;IACtC,+EAA+E;IAC/E,YAAY,CAAC,EAAE,aAAa,CAAC;CAChC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"/","sources":["layout/options.ts"],"names":[],"mappings":"","sourcesContent":["import { ModelMetadata } from './metadata';\r\n\r\nexport interface RenderLayoutOptions {\r\n scaleDenominators?: number[];\r\n maxViewports?: number;\r\n allowRemoteAssets?: boolean;\r\n allowDataUrls?: boolean;\r\n qrContext?: {\r\n host?: string | null;\r\n containerName?: string | null;\r\n floorName?: string | null;\r\n planeHeight?: number | null;\r\n };\r\n legendSources?: Record<string, any[]>;\r\n /** Optional metadata map keyed by productId referenced from path/label meta */\r\n metadataById?: ModelMetadata;\r\n}\r\n"]}
@@ -0,0 +1,23 @@
1
+ export declare const DEFAULT_SCALE_DENOMS: readonly [2, 10, 25, 50, 100, 250, 500, 1000, 2500];
2
+ /**
3
+ * Example building class preset. Not auto-applied — consumers must include it
4
+ * in their template's `presets.includeClasses` if they want to use it.
5
+ *
6
+ * ```json
7
+ * { "presets": { "includeClasses": { "defaultBuilding": [...] } } }
8
+ * ```
9
+ */
10
+ export declare const EXAMPLE_BUILDING_CLASSES: string[];
11
+ export interface ResolvedPresets {
12
+ includeClasses: Record<string, string[]>;
13
+ scales: Record<string, {
14
+ preferredScales?: number[];
15
+ }>;
16
+ }
17
+ export declare const resolvePresets: (presets?: {
18
+ includeClasses?: Record<string, string[]>;
19
+ scales?: Record<string, {
20
+ preferredScales?: number[];
21
+ }>;
22
+ }) => ResolvedPresets;
23
+ //# sourceMappingURL=presets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presets.d.ts","sourceRoot":"/","sources":["layout/presets.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,qDAAsD,CAAC;AAExF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAM,EAuB5C,CAAC;AAMF,MAAM,WAAW,eAAe;IAC5B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACzC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CAC1D;AAED,eAAO,MAAM,cAAc,GAAI,UAAU;IACrC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CAC3D,KAAG,eAGF,CAAC"}
@@ -0,0 +1,41 @@
1
+ export const DEFAULT_SCALE_DENOMS = [2, 10, 25, 50, 100, 250, 500, 1000, 2500];
2
+ /**
3
+ * Example building class preset. Not auto-applied — consumers must include it
4
+ * in their template's `presets.includeClasses` if they want to use it.
5
+ *
6
+ * ```json
7
+ * { "presets": { "includeClasses": { "defaultBuilding": [...] } } }
8
+ * ```
9
+ */
10
+ export const EXAMPLE_BUILDING_CLASSES = [
11
+ 'space',
12
+ 'ifcspace',
13
+ 'wall',
14
+ 'walls',
15
+ 'ifcwall',
16
+ 'ifcwallstandardcase',
17
+ 'window',
18
+ 'windows',
19
+ 'ifcwindow',
20
+ 'door',
21
+ 'doors',
22
+ 'ifcdoor',
23
+ 'beam',
24
+ 'ifcbeam',
25
+ 'column',
26
+ 'columns',
27
+ 'ifccolumn',
28
+ 'stair',
29
+ 'stairs',
30
+ 'ifcstair',
31
+ 'slab',
32
+ 'ifcslab'
33
+ ];
34
+ const defaultScalePresets = {
35
+ default: { preferredScales: Array.from(DEFAULT_SCALE_DENOMS) }
36
+ };
37
+ export const resolvePresets = (presets) => ({
38
+ includeClasses: presets?.includeClasses ?? {},
39
+ scales: { ...defaultScalePresets, ...(presets?.scales ?? {}) }
40
+ });
41
+ //# sourceMappingURL=presets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presets.js","sourceRoot":"/","sources":["layout/presets.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAU,CAAC;AAExF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAa;IAC9C,OAAO;IACP,UAAU;IACV,MAAM;IACN,OAAO;IACP,SAAS;IACT,qBAAqB;IACrB,QAAQ;IACR,SAAS;IACT,WAAW;IACX,MAAM;IACN,OAAO;IACP,SAAS;IACT,MAAM;IACN,SAAS;IACT,QAAQ;IACR,SAAS;IACT,WAAW;IACX,OAAO;IACP,QAAQ;IACR,UAAU;IACV,MAAM;IACN,SAAS;CACZ,CAAC;AAEF,MAAM,mBAAmB,GAAmD;IACxE,OAAO,EAAE,EAAE,eAAe,EAAE,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,EAAE;CACjE,CAAC;AAOF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAG9B,EAAmB,EAAE,CAAC,CAAC;IACpB,cAAc,EAAE,OAAO,EAAE,cAAc,IAAI,EAAE;IAC7C,MAAM,EAAE,EAAE,GAAG,mBAAmB,EAAE,GAAG,CAAC,OAAO,EAAE,MAAM,IAAI,EAAE,CAAC,EAAE;CACjE,CAAC,CAAC","sourcesContent":["export const DEFAULT_SCALE_DENOMS = [2, 10, 25, 50, 100, 250, 500, 1000, 2500] as const;\r\n\r\n/**\r\n * Example building class preset. Not auto-applied — consumers must include it\r\n * in their template's `presets.includeClasses` if they want to use it.\r\n *\r\n * ```json\r\n * { \"presets\": { \"includeClasses\": { \"defaultBuilding\": [...] } } }\r\n * ```\r\n */\r\nexport const EXAMPLE_BUILDING_CLASSES: string[] = [\r\n 'space',\r\n 'ifcspace',\r\n 'wall',\r\n 'walls',\r\n 'ifcwall',\r\n 'ifcwallstandardcase',\r\n 'window',\r\n 'windows',\r\n 'ifcwindow',\r\n 'door',\r\n 'doors',\r\n 'ifcdoor',\r\n 'beam',\r\n 'ifcbeam',\r\n 'column',\r\n 'columns',\r\n 'ifccolumn',\r\n 'stair',\r\n 'stairs',\r\n 'ifcstair',\r\n 'slab',\r\n 'ifcslab'\r\n];\r\n\r\nconst defaultScalePresets: Record<string, { preferredScales?: number[] }> = {\r\n default: { preferredScales: Array.from(DEFAULT_SCALE_DENOMS) }\r\n};\r\n\r\nexport interface ResolvedPresets {\r\n includeClasses: Record<string, string[]>;\r\n scales: Record<string, { preferredScales?: number[] }>;\r\n}\r\n\r\nexport const resolvePresets = (presets?: {\r\n includeClasses?: Record<string, string[]>;\r\n scales?: Record<string, { preferredScales?: number[] }>;\r\n}): ResolvedPresets => ({\r\n includeClasses: presets?.includeClasses ?? {},\r\n scales: { ...defaultScalePresets, ...(presets?.scales ?? {}) }\r\n});\r\n"]}
@@ -0,0 +1,13 @@
1
+ import { QrElement, SvgModelView } from './types';
2
+ export interface QrContext {
3
+ host?: string | null;
4
+ containerName?: string | null;
5
+ floorName?: string | null;
6
+ planeHeight?: number | null;
7
+ }
8
+ export declare const renderQrElements: (qrElements: QrElement[], viewports: any[], viewportScales: Record<string, {
9
+ mmPerModelUnit: number;
10
+ }>, models: Record<string, SvgModelView | undefined>, options: {
11
+ qrContext?: QrContext;
12
+ }, dataWithViewport: any, warnings: string[]) => Promise<string[]>;
13
+ //# sourceMappingURL=qrRender.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qrRender.d.ts","sourceRoot":"/","sources":["layout/qrRender.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAUlD,MAAM,WAAW,SAAS;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,eAAO,MAAM,gBAAgB,GACzB,YAAY,SAAS,EAAE,EACvB,WAAW,GAAG,EAAE,EAChB,gBAAgB,MAAM,CAAC,MAAM,EAAE;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC,EAC1D,QAAQ,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,CAAC,EAChD,SAAS;IAAE,SAAS,CAAC,EAAE,SAAS,CAAA;CAAE,EAClC,kBAAkB,GAAG,EACrB,UAAU,MAAM,EAAE,KACnB,OAAO,CAAC,MAAM,EAAE,CA6FlB,CAAC"}
@@ -0,0 +1,97 @@
1
+ import { escapeAttr } from './renderUtils';
2
+ import { interpolate } from './renderUtils';
3
+ async function getQRCode() {
4
+ try {
5
+ return await import('qrcode');
6
+ }
7
+ catch {
8
+ throw new Error('qrcode is required for QR code rendering. Install it: bun add qrcode');
9
+ }
10
+ }
11
+ export const renderQrElements = async (qrElements, viewports, viewportScales, models, options, dataWithViewport, warnings) => {
12
+ const parts = [];
13
+ for (const qr of qrElements) {
14
+ const boundViewportId = qr.viewport ?? viewports[0]?.id ?? null;
15
+ const viewportScale = boundViewportId ? viewportScales[boundViewportId] : null;
16
+ const boundViewportDef = boundViewportId ? viewports.find((v) => v.id === boundViewportId) : null;
17
+ // In v2, viewport id IS the model key
18
+ const boundModel = boundViewportDef ? (models[boundViewportDef.id] ?? null) : null;
19
+ let coordinate = null;
20
+ if (boundViewportDef && viewportScale && boundModel?.transform && (boundViewportDef.rotationDeg ?? 0) === 0) {
21
+ const mmPerModel = viewportScale.mmPerModelUnit;
22
+ const contentWidthMm = boundModel.widthModel * mmPerModel;
23
+ const contentHeightMm = boundModel.heightModel * mmPerModel;
24
+ const centerX = boundViewportDef.align === 'topLeft'
25
+ ? boundViewportDef.xMm + contentWidthMm * 0.5
26
+ : boundViewportDef.xMm + boundViewportDef.widthMm * 0.5;
27
+ const centerY = boundViewportDef.align === 'topLeft'
28
+ ? boundViewportDef.yMm + contentHeightMm * 0.5
29
+ : boundViewportDef.yMm + boundViewportDef.heightMm * 0.5;
30
+ const pointMmX = qr.xMm + qr.sizeMm * 0.5;
31
+ const pointMmY = qr.yMm + qr.sizeMm * 0.5;
32
+ const modelX = (pointMmX - centerX) / mmPerModel + boundModel.widthModel * 0.5;
33
+ const modelY = (pointMmY - centerY) / mmPerModel + boundModel.heightModel * 0.5;
34
+ const t = boundModel.transform;
35
+ const worldX = t.viewMinX + modelX;
36
+ const worldZ = t.viewMaxZ - modelY;
37
+ const planeHeight = options?.qrContext?.planeHeight ?? 0;
38
+ coordinate = { x: formatNumber(worldX), y: formatNumber(planeHeight ?? 0), z: formatNumber(worldZ) };
39
+ }
40
+ let payload = qr.dataTemplate ? interpolate(qr.dataTemplate, dataWithViewport) : null;
41
+ if (!payload || typeof payload === 'string') {
42
+ if (qr.role === 'anchor' && coordinate) {
43
+ const corner = qr.anchorCorner === 'bottom-left'
44
+ ? 'bl'
45
+ : qr.anchorCorner === 'bottom-right'
46
+ ? 'br'
47
+ : qr.anchorCorner === 'top-left'
48
+ ? 'tl'
49
+ : qr.anchorCorner === 'top-right'
50
+ ? 'tr'
51
+ : 'na';
52
+ payload = { v: 1, c: corner, p: coordinate };
53
+ }
54
+ else if (qr.role === 'main') {
55
+ const cornerShort = qr.anchorCorner === 'bottom-left'
56
+ ? 'bl'
57
+ : qr.anchorCorner === 'bottom-right'
58
+ ? 'br'
59
+ : qr.anchorCorner === 'top-left'
60
+ ? 'tl'
61
+ : qr.anchorCorner === 'top-right'
62
+ ? 'tr'
63
+ : 'bl';
64
+ payload = {
65
+ v: 1,
66
+ c: cornerShort,
67
+ h: options?.qrContext?.host ?? null,
68
+ a: options?.qrContext?.containerName ?? null,
69
+ fn: options?.qrContext?.floorName ?? null,
70
+ p: coordinate
71
+ };
72
+ }
73
+ }
74
+ if (!payload) {
75
+ warnings.push(`QR '${qr.id}' has no payload.`);
76
+ continue;
77
+ }
78
+ try {
79
+ const QRCode = await getQRCode();
80
+ const dataUrl = await QRCode.toDataURL(typeof payload === 'string' ? payload : JSON.stringify(payload), {
81
+ margin: 0,
82
+ errorCorrectionLevel: 'M'
83
+ });
84
+ parts.push(`<image href="${escapeAttr(dataUrl)}" x="${qr.xMm}" y="${qr.yMm}" width="${qr.sizeMm}" height="${qr.sizeMm}" />`);
85
+ }
86
+ catch (error) {
87
+ warnings.push(`Failed to generate QR '${qr.id}': ${error.message}`);
88
+ }
89
+ }
90
+ return parts;
91
+ };
92
+ const formatNumber = (value) => {
93
+ if (!Number.isFinite(value))
94
+ return 0;
95
+ return Number.parseFloat(value.toFixed(3));
96
+ };
97
+ //# sourceMappingURL=qrRender.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qrRender.js","sourceRoot":"/","sources":["layout/qrRender.ts"],"names":[],"mappings":"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","sourcesContent":["import { escapeAttr } from './renderUtils';\r\nimport { interpolate } from './renderUtils';\r\nimport { QrElement, SvgModelView } from './types';\r\n\r\nasync function getQRCode(): Promise<typeof import('qrcode')> {\r\n try {\r\n return await import('qrcode');\r\n } catch {\r\n throw new Error('qrcode is required for QR code rendering. Install it: bun add qrcode');\r\n }\r\n}\r\n\r\nexport interface QrContext {\r\n host?: string | null;\r\n containerName?: string | null;\r\n floorName?: string | null;\r\n planeHeight?: number | null;\r\n}\r\n\r\nexport const renderQrElements = async (\r\n qrElements: QrElement[],\r\n viewports: any[],\r\n viewportScales: Record<string, { mmPerModelUnit: number }>,\r\n models: Record<string, SvgModelView | undefined>,\r\n options: { qrContext?: QrContext },\r\n dataWithViewport: any,\r\n warnings: string[]\r\n): Promise<string[]> => {\r\n const parts: string[] = [];\r\n\r\n for (const qr of qrElements) {\r\n const boundViewportId = qr.viewport ?? viewports[0]?.id ?? null;\r\n const viewportScale = boundViewportId ? viewportScales[boundViewportId] : null;\r\n const boundViewportDef = boundViewportId ? viewports.find((v) => v.id === boundViewportId) : null;\r\n // In v2, viewport id IS the model key\r\n const boundModel = boundViewportDef ? (models[boundViewportDef.id] ?? null) : null;\r\n\r\n let coordinate: { x: number; y: number; z: number } | null = null;\r\n if (boundViewportDef && viewportScale && boundModel?.transform && (boundViewportDef.rotationDeg ?? 0) === 0) {\r\n const mmPerModel = viewportScale.mmPerModelUnit;\r\n const contentWidthMm = boundModel.widthModel * mmPerModel;\r\n const contentHeightMm = boundModel.heightModel * mmPerModel;\r\n const centerX =\r\n boundViewportDef.align === 'topLeft'\r\n ? boundViewportDef.xMm + contentWidthMm * 0.5\r\n : boundViewportDef.xMm + boundViewportDef.widthMm * 0.5;\r\n const centerY =\r\n boundViewportDef.align === 'topLeft'\r\n ? boundViewportDef.yMm + contentHeightMm * 0.5\r\n : boundViewportDef.yMm + boundViewportDef.heightMm * 0.5;\r\n\r\n const pointMmX = qr.xMm + qr.sizeMm * 0.5;\r\n const pointMmY = qr.yMm + qr.sizeMm * 0.5;\r\n\r\n const modelX = (pointMmX - centerX) / mmPerModel + boundModel.widthModel * 0.5;\r\n const modelY = (pointMmY - centerY) / mmPerModel + boundModel.heightModel * 0.5;\r\n\r\n const t = boundModel.transform;\r\n const worldX = t.viewMinX + modelX;\r\n const worldZ = t.viewMaxZ - modelY;\r\n const planeHeight = options?.qrContext?.planeHeight ?? 0;\r\n coordinate = { x: formatNumber(worldX), y: formatNumber(planeHeight ?? 0), z: formatNumber(worldZ) };\r\n }\r\n\r\n let payload: unknown = qr.dataTemplate ? interpolate(qr.dataTemplate, dataWithViewport) : null;\r\n if (!payload || typeof payload === 'string') {\r\n if (qr.role === 'anchor' && coordinate) {\r\n const corner =\r\n qr.anchorCorner === 'bottom-left'\r\n ? 'bl'\r\n : qr.anchorCorner === 'bottom-right'\r\n ? 'br'\r\n : qr.anchorCorner === 'top-left'\r\n ? 'tl'\r\n : qr.anchorCorner === 'top-right'\r\n ? 'tr'\r\n : 'na';\r\n payload = { v: 1, c: corner, p: coordinate };\r\n } else if (qr.role === 'main') {\r\n const cornerShort =\r\n qr.anchorCorner === 'bottom-left'\r\n ? 'bl'\r\n : qr.anchorCorner === 'bottom-right'\r\n ? 'br'\r\n : qr.anchorCorner === 'top-left'\r\n ? 'tl'\r\n : qr.anchorCorner === 'top-right'\r\n ? 'tr'\r\n : 'bl';\r\n payload = {\r\n v: 1,\r\n c: cornerShort,\r\n h: options?.qrContext?.host ?? null,\r\n a: options?.qrContext?.containerName ?? null,\r\n fn: options?.qrContext?.floorName ?? null,\r\n p: coordinate\r\n };\r\n }\r\n }\r\n\r\n if (!payload) {\r\n warnings.push(`QR '${qr.id}' has no payload.`);\r\n continue;\r\n }\r\n\r\n try {\r\n const QRCode = await getQRCode();\r\n const dataUrl = await QRCode.toDataURL(typeof payload === 'string' ? payload : JSON.stringify(payload), {\r\n margin: 0,\r\n errorCorrectionLevel: 'M'\r\n });\r\n parts.push(\r\n `<image href=\"${escapeAttr(dataUrl)}\" x=\"${qr.xMm}\" y=\"${qr.yMm}\" width=\"${qr.sizeMm}\" height=\"${qr.sizeMm}\" />`\r\n );\r\n } catch (error) {\r\n warnings.push(`Failed to generate QR '${qr.id}': ${(error as Error).message}`);\r\n }\r\n }\r\n\r\n return parts;\r\n};\r\n\r\nconst formatNumber = (value: number): number => {\r\n if (!Number.isFinite(value)) return 0;\r\n return Number.parseFloat(value.toFixed(3));\r\n};\r\n"]}