@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
package/dist/BuildInfo.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -4,4 +4,5 @@ export { printToSvg } from './printToSvg';
|
|
|
4
4
|
export { printToPdf, printToSvgFile, type PrintToPdfOptions } from './printToPdf';
|
|
5
5
|
export * from './layout/index';
|
|
6
6
|
export * from './sections';
|
|
7
|
+
export { sanitizeTemplateSchemaSnippets } from './schemaUtils';
|
|
7
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"/","sources":["index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGlF,cAAc,gBAAgB,CAAC;AAG/B,cAAc,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"/","sources":["index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGlF,cAAc,gBAAgB,CAAC;AAG/B,cAAc,YAAY,CAAC;AAI3B,OAAO,EAAE,8BAA8B,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -6,4 +6,7 @@ export { printToPdf, printToSvgFile } from './printToPdf';
|
|
|
6
6
|
export * from './layout/index';
|
|
7
7
|
// Sections SVG viewport module
|
|
8
8
|
export * from './sections';
|
|
9
|
+
// Template-schema utilities (snippet-placeholder sanitizing for consumers
|
|
10
|
+
// that parse the shipped template-schema.json)
|
|
11
|
+
export { sanitizeTemplateSchemaSnippets } from './schemaUtils';
|
|
9
12
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"/","sources":["index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,cAAc,EAA0B,MAAM,cAAc,CAAC;AAElF,yDAAyD;AACzD,cAAc,gBAAgB,CAAC;AAE/B,+BAA+B;AAC/B,cAAc,YAAY,CAAC","sourcesContent":["export * from './BuildInfo';\nexport * from './types';\nexport { printToSvg } from './printToSvg';\nexport { printToPdf, printToSvgFile, type PrintToPdfOptions } from './printToPdf';\n\n// Print-layout module (template-driven page composition)\nexport * from './layout/index';\n\n// Sections SVG viewport module\nexport * from './sections';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"/","sources":["index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,cAAc,EAA0B,MAAM,cAAc,CAAC;AAElF,yDAAyD;AACzD,cAAc,gBAAgB,CAAC;AAE/B,+BAA+B;AAC/B,cAAc,YAAY,CAAC;AAE3B,0EAA0E;AAC1E,+CAA+C;AAC/C,OAAO,EAAE,8BAA8B,EAAE,MAAM,eAAe,CAAC","sourcesContent":["export * from './BuildInfo';\nexport * from './types';\nexport { printToSvg } from './printToSvg';\nexport { printToPdf, printToSvgFile, type PrintToPdfOptions } from './printToPdf';\n\n// Print-layout module (template-driven page composition)\nexport * from './layout/index';\n\n// Sections SVG viewport module\nexport * from './sections';\n\n// Template-schema utilities (snippet-placeholder sanitizing for consumers\n// that parse the shipped template-schema.json)\nexport { sanitizeTemplateSchemaSnippets } from './schemaUtils';\n"]}
|
|
@@ -11,12 +11,12 @@ export interface TemplateRequirements {
|
|
|
11
11
|
propertyPaths: string[];
|
|
12
12
|
/** Class filters referenced (e.g. "space", "walls"). Already lowercase. */
|
|
13
13
|
classFilters: string[];
|
|
14
|
-
/**
|
|
15
|
-
|
|
14
|
+
/** Resource IDs referenced by image/northArrow elements. */
|
|
15
|
+
resourceIds: string[];
|
|
16
16
|
/** Text interpolation variables (e.g. "viewport.main.scaleText"). */
|
|
17
17
|
interpolationVars: string[];
|
|
18
|
-
/** Whether the template references remote (http/https)
|
|
19
|
-
|
|
18
|
+
/** Whether the template references remote (http/https) resource URLs. */
|
|
19
|
+
usesRemoteResources: boolean;
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
22
|
* Scan a layout template and return its input requirements.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzeTemplate.d.ts","sourceRoot":"/","sources":["layout/analyzeTemplate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"analyzeTemplate.d.ts","sourceRoot":"/","sources":["layout/analyzeTemplate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACR,cAAc,EAWjB,MAAM,SAAS,CAAC;AAEjB;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACjC,0CAA0C;IAC1C,WAAW,EAAE,MAAM,EAAE,CAAC;IAEtB;wEACoE;IACpE,aAAa,EAAE,MAAM,EAAE,CAAC;IAExB,2EAA2E;IAC3E,YAAY,EAAE,MAAM,EAAE,CAAC;IAEvB,4DAA4D;IAC5D,WAAW,EAAE,MAAM,EAAE,CAAC;IAEtB,qEAAqE;IACrE,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAE5B,yEAAyE;IACzE,mBAAmB,EAAE,OAAO,CAAC;CAChC;AAkCD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,cAAc,GAAG,oBAAoB,CA2I9E"}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { normalizeClass } from './filters';
|
|
2
|
+
import { splitPlaceholder } from './renderUtils';
|
|
2
3
|
const INTERPOLATION_RE = /\{\{\s*([^}]+?)\s*\}\}/g;
|
|
3
|
-
/** Extract `{{path}}` tokens from a template string
|
|
4
|
+
/** Extract `{{path}}` tokens from a template string (format filters like
|
|
5
|
+
* `| round:1` are part of the placeholder, not the referenced path). */
|
|
4
6
|
function extractInterpolationVars(text) {
|
|
5
7
|
const vars = [];
|
|
6
8
|
let m;
|
|
7
9
|
INTERPOLATION_RE.lastIndex = 0;
|
|
8
10
|
while ((m = INTERPOLATION_RE.exec(text)) !== null) {
|
|
9
|
-
vars.push(m[1]);
|
|
11
|
+
vars.push(splitPlaceholder(m[1]).path);
|
|
10
12
|
}
|
|
11
13
|
return vars;
|
|
12
14
|
}
|
|
@@ -40,14 +42,14 @@ export function analyzeTemplate(template) {
|
|
|
40
42
|
const viewportIds = new Set();
|
|
41
43
|
const propertyPaths = new Set();
|
|
42
44
|
const classFilters = new Set();
|
|
43
|
-
const
|
|
45
|
+
const resourceIds = new Set();
|
|
44
46
|
const interpolationVars = new Set();
|
|
45
|
-
let
|
|
46
|
-
// Check
|
|
47
|
-
if (template.
|
|
48
|
-
for (const
|
|
49
|
-
if (
|
|
50
|
-
|
|
47
|
+
let usesRemoteResources = false;
|
|
48
|
+
// Check resources for remote URLs
|
|
49
|
+
if (template.resources) {
|
|
50
|
+
for (const resource of template.resources) {
|
|
51
|
+
if (resource.src && /^https?:\/\//i.test(resource.src)) {
|
|
52
|
+
usesRemoteResources = true;
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
}
|
|
@@ -79,9 +81,9 @@ export function analyzeTemplate(template) {
|
|
|
79
81
|
}
|
|
80
82
|
// Preset references are resolved at render time, nothing to collect
|
|
81
83
|
}
|
|
82
|
-
// North arrow
|
|
83
|
-
if (vp.northArrow?.
|
|
84
|
-
|
|
84
|
+
// North arrow resource
|
|
85
|
+
if (vp.northArrow?.resourceId) {
|
|
86
|
+
resourceIds.add(vp.northArrow.resourceId);
|
|
85
87
|
}
|
|
86
88
|
break;
|
|
87
89
|
}
|
|
@@ -96,8 +98,8 @@ export function analyzeTemplate(template) {
|
|
|
96
98
|
}
|
|
97
99
|
case 'image': {
|
|
98
100
|
const img = el;
|
|
99
|
-
if (img.
|
|
100
|
-
|
|
101
|
+
if (img.resourceId)
|
|
102
|
+
resourceIds.add(img.resourceId);
|
|
101
103
|
break;
|
|
102
104
|
}
|
|
103
105
|
case 'qr': {
|
|
@@ -160,9 +162,9 @@ export function analyzeTemplate(template) {
|
|
|
160
162
|
viewportIds: [...viewportIds],
|
|
161
163
|
propertyPaths: [...propertyPaths],
|
|
162
164
|
classFilters: [...classFilters],
|
|
163
|
-
|
|
165
|
+
resourceIds: [...resourceIds],
|
|
164
166
|
interpolationVars: [...interpolationVars],
|
|
165
|
-
|
|
167
|
+
usesRemoteResources
|
|
166
168
|
};
|
|
167
169
|
}
|
|
168
170
|
//# sourceMappingURL=analyzeTemplate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzeTemplate.js","sourceRoot":"/","sources":["layout/analyzeTemplate.ts"],"names":[],"mappings":"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","sourcesContent":["import { normalizeClass } from './filters';\nimport type {\n LayoutTemplate,\n LayoutElement,\n ViewportElement,\n TextElement,\n ImageElement,\n QrElement,\n LabelsElement,\n LegendElement,\n GroupElement,\n ScaleBarElement,\n WhereCondition\n} from './types';\n\n/**\n * Describes the input requirements inferred from a layout template.\n * Use `analyzeTemplate()` to scan a template and discover what data it needs.\n */\nexport interface TemplateRequirements {\n /** Viewport IDs the template declares. */\n viewportIds: string[];\n\n /** Property paths referenced by legends, where-filters, and labels.\n * Short-path format: \"SetName.propName\" (e.g. \"BIP.spacename\"). */\n propertyPaths: string[];\n\n /** Class filters referenced (e.g. \"space\", \"walls\"). Already lowercase. */\n classFilters: string[];\n\n /** Asset IDs referenced by image/northArrow elements. */\n assetIds: string[];\n\n /** Text interpolation variables (e.g. \"viewport.main.scaleText\"). */\n interpolationVars: string[];\n\n /** Whether the template references remote (http/https) asset URLs. */\n usesRemoteAssets: boolean;\n}\n\nconst INTERPOLATION_RE = /\\{\\{\\s*([^}]+?)\\s*\\}\\}/g;\n\n/** Extract `{{path}}` tokens from a template string. */\nfunction extractInterpolationVars(text: string): string[] {\n const vars: string[] = [];\n let m: RegExpExecArray | null;\n INTERPOLATION_RE.lastIndex = 0;\n while ((m = INTERPOLATION_RE.exec(text)) !== null) {\n vars.push(m[1]);\n }\n return vars;\n}\n\n/** Collect keys from a WhereCondition (each key is a property short-path). */\nfunction collectWherePaths(where: WhereCondition | undefined, out: Set<string>): void {\n if (!where || typeof where !== 'object') return;\n for (const key of Object.keys(where)) {\n out.add(key);\n }\n}\n\n/** Collect class filter values, normalizing to lowercase. */\nfunction collectClassFilter(classFilter: string | string[] | undefined, out: Set<string>): void {\n if (!classFilter) return;\n const classes = Array.isArray(classFilter) ? classFilter : [classFilter];\n for (const c of classes) {\n const normalized = normalizeClass(c);\n if (normalized) out.add(normalized);\n }\n}\n\n/**\n * Scan a layout template and return its input requirements.\n *\n * This is a pure, read-only analysis — it does not modify the template or\n * perform any rendering. Use the result to validate inputs before calling\n * `renderDrawing()` or `renderLayout()`.\n */\nexport function analyzeTemplate(template: LayoutTemplate): TemplateRequirements {\n const viewportIds = new Set<string>();\n const propertyPaths = new Set<string>();\n const classFilters = new Set<string>();\n const assetIds = new Set<string>();\n const interpolationVars = new Set<string>();\n let usesRemoteAssets = false;\n\n // Check assets for remote URLs\n if (template.assets) {\n for (const asset of template.assets) {\n if (asset.src && /^https?:\\/\\//i.test(asset.src)) {\n usesRemoteAssets = true;\n }\n }\n }\n\n // Walk all elements (recursing into groups)\n const walk = (elements: LayoutElement[]): void => {\n for (const el of elements) {\n switch (el.kind) {\n case 'viewport': {\n const vp = el as ViewportElement;\n viewportIds.add(vp.id);\n\n // Clip filter\n if (vp.clip) {\n collectClassFilter(vp.clip.class, classFilters);\n collectWherePaths(vp.clip.where, propertyPaths);\n }\n\n // Highlight filter\n if (vp.highlight) {\n collectClassFilter(vp.highlight.class, classFilters);\n collectWherePaths(vp.highlight.where, propertyPaths);\n }\n\n // includeClasses\n if (vp.includeClasses) {\n if (Array.isArray(vp.includeClasses)) {\n for (const c of vp.includeClasses) {\n const normalized = normalizeClass(c);\n if (normalized) classFilters.add(normalized);\n }\n }\n // Preset references are resolved at render time, nothing to collect\n }\n\n // North arrow asset\n if (vp.northArrow?.assetId) {\n assetIds.add(vp.northArrow.assetId);\n }\n break;\n }\n\n case 'text': {\n const txt = el as TextElement;\n if (txt.text) {\n for (const v of extractInterpolationVars(txt.text)) {\n interpolationVars.add(v);\n }\n }\n break;\n }\n\n case 'image': {\n const img = el as ImageElement;\n if (img.assetId) assetIds.add(img.assetId);\n break;\n }\n\n case 'qr': {\n const qr = el as QrElement;\n if (qr.dataTemplate) {\n for (const v of extractInterpolationVars(qr.dataTemplate)) {\n interpolationVars.add(v);\n }\n }\n break;\n }\n\n case 'labels': {\n const sl = el as LabelsElement;\n collectClassFilter(sl.class, classFilters);\n collectWherePaths(sl.where, propertyPaths);\n\n // Extract property paths from text interpolation (skip viewport.* which are computed)\n if (sl.text) {\n for (const v of extractInterpolationVars(sl.text)) {\n if (!v.startsWith('viewport.')) {\n propertyPaths.add(v);\n } else {\n interpolationVars.add(v);\n }\n }\n }\n break;\n }\n\n case 'legend': {\n const leg = el as LegendElement;\n collectClassFilter(leg.class, classFilters);\n collectWherePaths(leg.where, propertyPaths);\n\n // Column property paths\n if (leg.columns) {\n for (const col of leg.columns) {\n if (col.property) propertyPaths.add(col.property);\n }\n }\n break;\n }\n\n case 'scaleBar': {\n // scaleBar references a viewport by ID (informational)\n const sb = el as ScaleBarElement;\n if (sb.viewport) viewportIds.add(sb.viewport);\n break;\n }\n\n case 'group': {\n const grp = el as GroupElement;\n if (grp.children) walk(grp.children);\n break;\n }\n }\n }\n };\n\n walk(template.elements);\n\n return {\n viewportIds: [...viewportIds],\n propertyPaths: [...propertyPaths],\n classFilters: [...classFilters],\n assetIds: [...assetIds],\n interpolationVars: [...interpolationVars],\n usesRemoteAssets\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"analyzeTemplate.js","sourceRoot":"/","sources":["layout/analyzeTemplate.ts"],"names":[],"mappings":"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","sourcesContent":["import { normalizeClass } from './filters';\nimport { splitPlaceholder } from './renderUtils';\nimport type {\n LayoutTemplate,\n LayoutElement,\n ViewportElement,\n TextElement,\n ImageElement,\n QrElement,\n LabelsElement,\n LegendElement,\n GroupElement,\n ScaleBarElement,\n WhereCondition\n} from './types';\n\n/**\n * Describes the input requirements inferred from a layout template.\n * Use `analyzeTemplate()` to scan a template and discover what data it needs.\n */\nexport interface TemplateRequirements {\n /** Viewport IDs the template declares. */\n viewportIds: string[];\n\n /** Property paths referenced by legends, where-filters, and labels.\n * Short-path format: \"SetName.propName\" (e.g. \"BIP.spacename\"). */\n propertyPaths: string[];\n\n /** Class filters referenced (e.g. \"space\", \"walls\"). Already lowercase. */\n classFilters: string[];\n\n /** Resource IDs referenced by image/northArrow elements. */\n resourceIds: string[];\n\n /** Text interpolation variables (e.g. \"viewport.main.scaleText\"). */\n interpolationVars: string[];\n\n /** Whether the template references remote (http/https) resource URLs. */\n usesRemoteResources: boolean;\n}\n\nconst INTERPOLATION_RE = /\\{\\{\\s*([^}]+?)\\s*\\}\\}/g;\n\n/** Extract `{{path}}` tokens from a template string (format filters like\n * `| round:1` are part of the placeholder, not the referenced path). */\nfunction extractInterpolationVars(text: string): string[] {\n const vars: string[] = [];\n let m: RegExpExecArray | null;\n INTERPOLATION_RE.lastIndex = 0;\n while ((m = INTERPOLATION_RE.exec(text)) !== null) {\n vars.push(splitPlaceholder(m[1]).path);\n }\n return vars;\n}\n\n/** Collect keys from a WhereCondition (each key is a property short-path). */\nfunction collectWherePaths(where: WhereCondition | undefined, out: Set<string>): void {\n if (!where || typeof where !== 'object') return;\n for (const key of Object.keys(where)) {\n out.add(key);\n }\n}\n\n/** Collect class filter values, normalizing to lowercase. */\nfunction collectClassFilter(classFilter: string | string[] | undefined, out: Set<string>): void {\n if (!classFilter) return;\n const classes = Array.isArray(classFilter) ? classFilter : [classFilter];\n for (const c of classes) {\n const normalized = normalizeClass(c);\n if (normalized) out.add(normalized);\n }\n}\n\n/**\n * Scan a layout template and return its input requirements.\n *\n * This is a pure, read-only analysis — it does not modify the template or\n * perform any rendering. Use the result to validate inputs before calling\n * `renderDrawing()` or `renderLayout()`.\n */\nexport function analyzeTemplate(template: LayoutTemplate): TemplateRequirements {\n const viewportIds = new Set<string>();\n const propertyPaths = new Set<string>();\n const classFilters = new Set<string>();\n const resourceIds = new Set<string>();\n const interpolationVars = new Set<string>();\n let usesRemoteResources = false;\n\n // Check resources for remote URLs\n if (template.resources) {\n for (const resource of template.resources) {\n if (resource.src && /^https?:\\/\\//i.test(resource.src)) {\n usesRemoteResources = true;\n }\n }\n }\n\n // Walk all elements (recursing into groups)\n const walk = (elements: LayoutElement[]): void => {\n for (const el of elements) {\n switch (el.kind) {\n case 'viewport': {\n const vp = el as ViewportElement;\n viewportIds.add(vp.id);\n\n // Clip filter\n if (vp.clip) {\n collectClassFilter(vp.clip.class, classFilters);\n collectWherePaths(vp.clip.where, propertyPaths);\n }\n\n // Highlight filter\n if (vp.highlight) {\n collectClassFilter(vp.highlight.class, classFilters);\n collectWherePaths(vp.highlight.where, propertyPaths);\n }\n\n // includeClasses\n if (vp.includeClasses) {\n if (Array.isArray(vp.includeClasses)) {\n for (const c of vp.includeClasses) {\n const normalized = normalizeClass(c);\n if (normalized) classFilters.add(normalized);\n }\n }\n // Preset references are resolved at render time, nothing to collect\n }\n\n // North arrow resource\n if (vp.northArrow?.resourceId) {\n resourceIds.add(vp.northArrow.resourceId);\n }\n break;\n }\n\n case 'text': {\n const txt = el as TextElement;\n if (txt.text) {\n for (const v of extractInterpolationVars(txt.text)) {\n interpolationVars.add(v);\n }\n }\n break;\n }\n\n case 'image': {\n const img = el as ImageElement;\n if (img.resourceId) resourceIds.add(img.resourceId);\n break;\n }\n\n case 'qr': {\n const qr = el as QrElement;\n if (qr.dataTemplate) {\n for (const v of extractInterpolationVars(qr.dataTemplate)) {\n interpolationVars.add(v);\n }\n }\n break;\n }\n\n case 'labels': {\n const sl = el as LabelsElement;\n collectClassFilter(sl.class, classFilters);\n collectWherePaths(sl.where, propertyPaths);\n\n // Extract property paths from text interpolation (skip viewport.* which are computed)\n if (sl.text) {\n for (const v of extractInterpolationVars(sl.text)) {\n if (!v.startsWith('viewport.')) {\n propertyPaths.add(v);\n } else {\n interpolationVars.add(v);\n }\n }\n }\n break;\n }\n\n case 'legend': {\n const leg = el as LegendElement;\n collectClassFilter(leg.class, classFilters);\n collectWherePaths(leg.where, propertyPaths);\n\n // Column property paths\n if (leg.columns) {\n for (const col of leg.columns) {\n if (col.property) propertyPaths.add(col.property);\n }\n }\n break;\n }\n\n case 'scaleBar': {\n // scaleBar references a viewport by ID (informational)\n const sb = el as ScaleBarElement;\n if (sb.viewport) viewportIds.add(sb.viewport);\n break;\n }\n\n case 'group': {\n const grp = el as GroupElement;\n if (grp.children) walk(grp.children);\n break;\n }\n }\n }\n };\n\n walk(template.elements);\n\n return {\n viewportIds: [...viewportIds],\n propertyPaths: [...propertyPaths],\n classFilters: [...classFilters],\n resourceIds: [...resourceIds],\n interpolationVars: [...interpolationVars],\n usesRemoteResources\n };\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-viewport compute-class closure (TWINS-3784 perf + semantics): the set of classes a
|
|
3
|
+
* single viewport's drawing generation may touch — its resolved includeClasses, its
|
|
4
|
+
* highlight/clip classes, its fill rules, and any labels/legend elements bound to it.
|
|
5
|
+
*
|
|
6
|
+
* The contract (decided with TWINS-3784): only the viewport's include classes take part
|
|
7
|
+
* in that viewport's drawing generation, HIDDEN LINES INCLUDED — geometry outside the
|
|
8
|
+
* closure neither draws nor occludes there (an undrawable pipe run must not hide the
|
|
9
|
+
* wall behind it), and does not pay compute cost. Sections are cached per
|
|
10
|
+
* (sectionDef, closure), so viewports sharing both share the geometry.
|
|
11
|
+
*
|
|
12
|
+
* Returns null when the viewport's closure cannot be bounded by class: no includeClasses
|
|
13
|
+
* (the viewport draws everything) or any feature filtered only by a where-condition. The
|
|
14
|
+
* caller then computes that viewport with all products — the previous behaviour.
|
|
15
|
+
*/
|
|
16
|
+
import type { LayoutTemplate } from './types';
|
|
17
|
+
export declare function computeViewportClassClosure(vp: Record<string, unknown>, template: LayoutTemplate): Set<string> | null;
|
|
18
|
+
/** Per-class drawing-generation behavior extracted from includeClasses entries. */
|
|
19
|
+
export interface ClassBehaviorRule {
|
|
20
|
+
class: string | string[];
|
|
21
|
+
occludes?: boolean;
|
|
22
|
+
classify?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Collects per-class behavior flags (TWINS-3784) from the viewport's includeClasses
|
|
26
|
+
* entries: `{ "class": "pipes", "occludes": false, "classify": false }`. Flags apply
|
|
27
|
+
* class-wide — a `where` on the same entry narrows what is DRAWN, not the behavior.
|
|
28
|
+
*/
|
|
29
|
+
export declare function collectClassBehavior(vp: Record<string, unknown>, template: LayoutTemplate): ClassBehaviorRule[];
|
|
30
|
+
/**
|
|
31
|
+
* Filters products to the closure. Only products that can self-classify via `class.is`
|
|
32
|
+
* participate — adapter products without a class matcher always stay, because their raw
|
|
33
|
+
* class names may not use the friendly vocabulary templates speak (dropping them on a
|
|
34
|
+
* name mismatch would silently blank the sheet).
|
|
35
|
+
*/
|
|
36
|
+
export declare function filterProductsByClosure<TProduct extends {
|
|
37
|
+
class?: {
|
|
38
|
+
is(c: unknown): boolean;
|
|
39
|
+
};
|
|
40
|
+
}>(products: TProduct[], closure: ReadonlySet<string>): TProduct[];
|
|
41
|
+
//# sourceMappingURL=classClosure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classClosure.d.ts","sourceRoot":"/","sources":["layout/classClosure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,wBAAgB,2BAA2B,CAAC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,EAAE,cAAc,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAsErH;AAED,mFAAmF;AACnF,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,EAAE,cAAc,GAAG,iBAAiB,EAAE,CAgB/G;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,SAAS;IAAE,KAAK,CAAC,EAAE;QAAE,EAAE,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;KAAE,CAAA;CAAE,EAC5F,QAAQ,EAAE,QAAQ,EAAE,EACpB,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAC7B,QAAQ,EAAE,CASZ"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-viewport compute-class closure (TWINS-3784 perf + semantics): the set of classes a
|
|
3
|
+
* single viewport's drawing generation may touch — its resolved includeClasses, its
|
|
4
|
+
* highlight/clip classes, its fill rules, and any labels/legend elements bound to it.
|
|
5
|
+
*
|
|
6
|
+
* The contract (decided with TWINS-3784): only the viewport's include classes take part
|
|
7
|
+
* in that viewport's drawing generation, HIDDEN LINES INCLUDED — geometry outside the
|
|
8
|
+
* closure neither draws nor occludes there (an undrawable pipe run must not hide the
|
|
9
|
+
* wall behind it), and does not pay compute cost. Sections are cached per
|
|
10
|
+
* (sectionDef, closure), so viewports sharing both share the geometry.
|
|
11
|
+
*
|
|
12
|
+
* Returns null when the viewport's closure cannot be bounded by class: no includeClasses
|
|
13
|
+
* (the viewport draws everything) or any feature filtered only by a where-condition. The
|
|
14
|
+
* caller then computes that viewport with all products — the previous behaviour.
|
|
15
|
+
*/
|
|
16
|
+
import { normalizeClass } from './filters';
|
|
17
|
+
import { resolveIncludeClasses } from './metadata';
|
|
18
|
+
import { isRecord, getProp } from './typeGuards';
|
|
19
|
+
export function computeViewportClassClosure(vp, template) {
|
|
20
|
+
const presets = template.presets?.includeClasses ?? {};
|
|
21
|
+
const out = new Set();
|
|
22
|
+
/** Add class name(s); false = value present but not class-expressible. */
|
|
23
|
+
const addClasses = (cls) => {
|
|
24
|
+
if (typeof cls === 'string') {
|
|
25
|
+
const n = normalizeClass(cls);
|
|
26
|
+
if (n)
|
|
27
|
+
out.add(n);
|
|
28
|
+
return n !== null;
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray(cls)) {
|
|
31
|
+
if (!cls.length)
|
|
32
|
+
return false;
|
|
33
|
+
let ok = true;
|
|
34
|
+
for (const c of cls)
|
|
35
|
+
ok = addClasses(c) && ok;
|
|
36
|
+
return ok;
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
};
|
|
40
|
+
const resolved = resolveIncludeClasses(getProp(vp, 'includeClasses'), presets);
|
|
41
|
+
if (!resolved)
|
|
42
|
+
return null; // no includeClasses ⇒ the viewport draws everything
|
|
43
|
+
for (const f of resolved) {
|
|
44
|
+
if (typeof f === 'string') {
|
|
45
|
+
if (!addClasses(f))
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
else if (f.class !== undefined) {
|
|
49
|
+
// A where-condition narrows WITHIN the class — class superset is safe.
|
|
50
|
+
if (!addClasses(f.class))
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
return null; // where-only include filter — class-unbounded
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const key of ['highlight', 'clip']) {
|
|
58
|
+
const cfg = getProp(vp, key);
|
|
59
|
+
if (cfg == null)
|
|
60
|
+
continue;
|
|
61
|
+
if (!isRecord(cfg) || !addClasses(getProp(cfg, 'class')))
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const fills = getProp(vp, 'fills');
|
|
65
|
+
if (Array.isArray(fills)) {
|
|
66
|
+
for (const rule of fills) {
|
|
67
|
+
if (!isRecord(rule) || !addClasses(getProp(rule, 'class')))
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Labels/legend elements bound to this viewport (recursing into groups). An element
|
|
72
|
+
// without a viewport binding is treated as bound to every viewport — conservative.
|
|
73
|
+
const vpId = getProp(vp, 'id');
|
|
74
|
+
let bounded = true;
|
|
75
|
+
const visit = (elements) => {
|
|
76
|
+
if (!Array.isArray(elements) || !bounded)
|
|
77
|
+
return;
|
|
78
|
+
for (const el of elements) {
|
|
79
|
+
if (!isRecord(el))
|
|
80
|
+
continue;
|
|
81
|
+
const kind = getProp(el, 'kind');
|
|
82
|
+
if (kind === 'labels' || kind === 'legend') {
|
|
83
|
+
const boundTo = getProp(el, 'viewport');
|
|
84
|
+
if (boundTo === undefined || boundTo === vpId) {
|
|
85
|
+
if (!addClasses(getProp(el, 'class'))) {
|
|
86
|
+
bounded = false;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const children = getProp(el, 'children');
|
|
92
|
+
if (children !== undefined)
|
|
93
|
+
visit(children);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
visit(template.elements);
|
|
97
|
+
if (!bounded)
|
|
98
|
+
return null;
|
|
99
|
+
return out.size > 0 ? out : null;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Collects per-class behavior flags (TWINS-3784) from the viewport's includeClasses
|
|
103
|
+
* entries: `{ "class": "pipes", "occludes": false, "classify": false }`. Flags apply
|
|
104
|
+
* class-wide — a `where` on the same entry narrows what is DRAWN, not the behavior.
|
|
105
|
+
*/
|
|
106
|
+
export function collectClassBehavior(vp, template) {
|
|
107
|
+
const presets = template.presets?.includeClasses ?? {};
|
|
108
|
+
const resolved = resolveIncludeClasses(getProp(vp, 'includeClasses'), presets);
|
|
109
|
+
if (!resolved)
|
|
110
|
+
return [];
|
|
111
|
+
const rules = [];
|
|
112
|
+
for (const f of resolved) {
|
|
113
|
+
if (typeof f === 'string' || f.class === undefined)
|
|
114
|
+
continue;
|
|
115
|
+
if (f.occludes === false || f.classify === false) {
|
|
116
|
+
rules.push({
|
|
117
|
+
class: f.class,
|
|
118
|
+
...(f.occludes === false ? { occludes: false } : {}),
|
|
119
|
+
...(f.classify === false ? { classify: false } : {})
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return rules;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Filters products to the closure. Only products that can self-classify via `class.is`
|
|
127
|
+
* participate — adapter products without a class matcher always stay, because their raw
|
|
128
|
+
* class names may not use the friendly vocabulary templates speak (dropping them on a
|
|
129
|
+
* name mismatch would silently blank the sheet).
|
|
130
|
+
*/
|
|
131
|
+
export function filterProductsByClosure(products, closure) {
|
|
132
|
+
return products.filter((p) => {
|
|
133
|
+
const is = p.class?.is;
|
|
134
|
+
if (typeof is !== 'function')
|
|
135
|
+
return true;
|
|
136
|
+
for (const c of closure) {
|
|
137
|
+
if (p.class.is(c))
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=classClosure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classClosure.js","sourceRoot":"/","sources":["layout/classClosure.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Per-viewport compute-class closure (TWINS-3784 perf + semantics): the set of classes a\n * single viewport's drawing generation may touch — its resolved includeClasses, its\n * highlight/clip classes, its fill rules, and any labels/legend elements bound to it.\n *\n * The contract (decided with TWINS-3784): only the viewport's include classes take part\n * in that viewport's drawing generation, HIDDEN LINES INCLUDED — geometry outside the\n * closure neither draws nor occludes there (an undrawable pipe run must not hide the\n * wall behind it), and does not pay compute cost. Sections are cached per\n * (sectionDef, closure), so viewports sharing both share the geometry.\n *\n * Returns null when the viewport's closure cannot be bounded by class: no includeClasses\n * (the viewport draws everything) or any feature filtered only by a where-condition. The\n * caller then computes that viewport with all products — the previous behaviour.\n */\n\nimport { normalizeClass } from './filters';\nimport { resolveIncludeClasses } from './metadata';\nimport { isRecord, getProp } from './typeGuards';\nimport type { LayoutTemplate } from './types';\n\nexport function computeViewportClassClosure(vp: Record<string, unknown>, template: LayoutTemplate): Set<string> | null {\n const presets = template.presets?.includeClasses ?? {};\n const out = new Set<string>();\n\n /** Add class name(s); false = value present but not class-expressible. */\n const addClasses = (cls: unknown): boolean => {\n if (typeof cls === 'string') {\n const n = normalizeClass(cls);\n if (n) out.add(n);\n return n !== null;\n }\n if (Array.isArray(cls)) {\n if (!cls.length) return false;\n let ok = true;\n for (const c of cls) ok = addClasses(c) && ok;\n return ok;\n }\n return false;\n };\n\n const resolved = resolveIncludeClasses(getProp(vp, 'includeClasses'), presets);\n if (!resolved) return null; // no includeClasses ⇒ the viewport draws everything\n for (const f of resolved) {\n if (typeof f === 'string') {\n if (!addClasses(f)) return null;\n } else if (f.class !== undefined) {\n // A where-condition narrows WITHIN the class — class superset is safe.\n if (!addClasses(f.class)) return null;\n } else {\n return null; // where-only include filter — class-unbounded\n }\n }\n for (const key of ['highlight', 'clip']) {\n const cfg = getProp(vp, key);\n if (cfg == null) continue;\n if (!isRecord(cfg) || !addClasses(getProp(cfg, 'class'))) return null;\n }\n const fills = getProp(vp, 'fills');\n if (Array.isArray(fills)) {\n for (const rule of fills) {\n if (!isRecord(rule) || !addClasses(getProp(rule, 'class'))) return null;\n }\n }\n\n // Labels/legend elements bound to this viewport (recursing into groups). An element\n // without a viewport binding is treated as bound to every viewport — conservative.\n const vpId = getProp(vp, 'id');\n let bounded = true;\n const visit = (elements: unknown): void => {\n if (!Array.isArray(elements) || !bounded) return;\n for (const el of elements) {\n if (!isRecord(el)) continue;\n const kind = getProp(el, 'kind');\n if (kind === 'labels' || kind === 'legend') {\n const boundTo = getProp(el, 'viewport');\n if (boundTo === undefined || boundTo === vpId) {\n if (!addClasses(getProp(el, 'class'))) {\n bounded = false;\n return;\n }\n }\n }\n const children = getProp(el, 'children');\n if (children !== undefined) visit(children);\n }\n };\n visit(template.elements);\n if (!bounded) return null;\n\n return out.size > 0 ? out : null;\n}\n\n/** Per-class drawing-generation behavior extracted from includeClasses entries. */\nexport interface ClassBehaviorRule {\n class: string | string[];\n occludes?: boolean;\n classify?: boolean;\n}\n\n/**\n * Collects per-class behavior flags (TWINS-3784) from the viewport's includeClasses\n * entries: `{ \"class\": \"pipes\", \"occludes\": false, \"classify\": false }`. Flags apply\n * class-wide — a `where` on the same entry narrows what is DRAWN, not the behavior.\n */\nexport function collectClassBehavior(vp: Record<string, unknown>, template: LayoutTemplate): ClassBehaviorRule[] {\n const presets = template.presets?.includeClasses ?? {};\n const resolved = resolveIncludeClasses(getProp(vp, 'includeClasses'), presets);\n if (!resolved) return [];\n const rules: ClassBehaviorRule[] = [];\n for (const f of resolved) {\n if (typeof f === 'string' || f.class === undefined) continue;\n if (f.occludes === false || f.classify === false) {\n rules.push({\n class: f.class,\n ...(f.occludes === false ? { occludes: false } : {}),\n ...(f.classify === false ? { classify: false } : {})\n });\n }\n }\n return rules;\n}\n\n/**\n * Filters products to the closure. Only products that can self-classify via `class.is`\n * participate — adapter products without a class matcher always stay, because their raw\n * class names may not use the friendly vocabulary templates speak (dropping them on a\n * name mismatch would silently blank the sheet).\n */\nexport function filterProductsByClosure<TProduct extends { class?: { is(c: unknown): boolean } }>(\n products: TProduct[],\n closure: ReadonlySet<string>\n): TProduct[] {\n return products.filter((p) => {\n const is = p.class?.is;\n if (typeof is !== 'function') return true;\n for (const c of closure) {\n if (p.class!.is(c)) return true;\n }\n return false;\n });\n}\n"]}
|
|
@@ -10,6 +10,17 @@ export declare const filterEntriesByClip: (entries: unknown[], polygons: Array<A
|
|
|
10
10
|
x: number;
|
|
11
11
|
z: number;
|
|
12
12
|
}>>) => unknown[];
|
|
13
|
+
/**
|
|
14
|
+
* Union polygons so overlapping ones merge into a single outline (disjoint
|
|
15
|
+
* polygons pass through unchanged). Used on expanded clip polygons — two
|
|
16
|
+
* adjacent clip targets whose expansions overlap should read as one region,
|
|
17
|
+
* not as outlines crossing through each other.
|
|
18
|
+
*
|
|
19
|
+
* The result is a flat ring list that may include hole rings (e.g. several
|
|
20
|
+
* regions enclosing a courtyard) — consumers rendering fills or clip paths
|
|
21
|
+
* from these rings must use even-odd semantics.
|
|
22
|
+
*/
|
|
23
|
+
export declare const unionPolygons: (polys: Polygon[]) => Polygon[];
|
|
13
24
|
export declare const offsetPolygons: (polys: Polygon[], delta: number) => Polygon[];
|
|
14
25
|
export declare const projectPolygonsToLocal: (polygonsSource: unknown, xField: string, yField: string, model: SvgModelView, rotationDeg: number, contentOffsetX: number, contentOffsetY: number, mmPerModel: number) => {
|
|
15
26
|
local: Polygon[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clipUtils.d.ts","sourceRoot":"/","sources":["layout/clipUtils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"clipUtils.d.ts","sourceRoot":"/","sources":["layout/clipUtils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG5C,MAAM,MAAM,KAAK,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAC7C,MAAM,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC;AAI9B,eAAO,MAAM,cAAc,GAAI,SAAS,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,OAWvE,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAC9B,OAAO,OAAO,EAAE,EAChB,UAAU,OAAO,EAAE,EACnB,0BAAY,EACZ,0BAAY,EACZ,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,KACtD,OAAO,EAUT,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC5B,SAAS,OAAO,EAAE,EAClB,UAAU,KAAK,CAAC,KAAK,CAAC;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,KACjD,OAAO,EA2CT,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,OAAO,EAAE,KAAG,OAAO,EAyBvD,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,EAAE,EAAE,OAAO,MAAM,KAAG,OAAO,EA4DvE,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAC/B,gBAAgB,OAAO,EACvB,QAAQ,MAAM,EACd,QAAQ,MAAM,EACd,OAAO,YAAY,EACnB,aAAa,MAAM,EACnB,gBAAgB,MAAM,EACtB,gBAAgB,MAAM,EACtB,YAAY,MAAM,KACnB;IAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IAAC,IAAI,EAAE,OAAO,EAAE,CAAA;CAuDrC,CAAC"}
|
package/dist/layout/clipUtils.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import polygonClipping from 'polygon-clipping';
|
|
1
2
|
import { EPSILON_DIVISION_SAFETY, CENTER_FRACTION } from './geometryConstants';
|
|
2
3
|
import { isRecord, getProp } from './typeGuards';
|
|
3
4
|
export const pointInPolygon = (polygon, x, y) => {
|
|
@@ -63,6 +64,46 @@ export const filterEntriesByClip = (entries, polygons) => {
|
|
|
63
64
|
return polygons.some((poly) => pointInPolygon(poly.map((p) => ({ x: p.x, y: p.z })), x, z));
|
|
64
65
|
});
|
|
65
66
|
};
|
|
67
|
+
/**
|
|
68
|
+
* Union polygons so overlapping ones merge into a single outline (disjoint
|
|
69
|
+
* polygons pass through unchanged). Used on expanded clip polygons — two
|
|
70
|
+
* adjacent clip targets whose expansions overlap should read as one region,
|
|
71
|
+
* not as outlines crossing through each other.
|
|
72
|
+
*
|
|
73
|
+
* The result is a flat ring list that may include hole rings (e.g. several
|
|
74
|
+
* regions enclosing a courtyard) — consumers rendering fills or clip paths
|
|
75
|
+
* from these rings must use even-odd semantics.
|
|
76
|
+
*/
|
|
77
|
+
export const unionPolygons = (polys) => {
|
|
78
|
+
if (!Array.isArray(polys) || polys.length < 2)
|
|
79
|
+
return polys ?? [];
|
|
80
|
+
try {
|
|
81
|
+
const input = polys
|
|
82
|
+
.filter((p) => p.length >= 3)
|
|
83
|
+
.map((p) => [[...p.map((pt) => [pt.x, pt.y]), [p[0].x, p[0].y]]]);
|
|
84
|
+
if (input.length < 2)
|
|
85
|
+
return polys;
|
|
86
|
+
const result = polygonClipping.union(input[0], ...input.slice(1));
|
|
87
|
+
const rings = [];
|
|
88
|
+
for (const poly of result) {
|
|
89
|
+
for (const ring of poly) {
|
|
90
|
+
const r = ring.map(([x, y]) => ({ x, y }));
|
|
91
|
+
// Drop the duplicated closing point the library emits.
|
|
92
|
+
if (r.length > 1 && r[0].x === r[r.length - 1].x && r[0].y === r[r.length - 1].y) {
|
|
93
|
+
r.pop();
|
|
94
|
+
}
|
|
95
|
+
if (r.length >= 3)
|
|
96
|
+
rings.push(r);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return rings.length ? rings : polys;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Boolean ops can throw on degenerate input — fall back to the
|
|
103
|
+
// un-unioned polygons rather than losing the clip entirely.
|
|
104
|
+
return polys;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
66
107
|
export const offsetPolygons = (polys, delta) => {
|
|
67
108
|
if (!Array.isArray(polys) || !polys.length || !Number.isFinite(delta) || delta === 0)
|
|
68
109
|
return polys ?? [];
|
|
@@ -73,20 +114,40 @@ export const offsetPolygons = (polys, delta) => {
|
|
|
73
114
|
const t = ((q.x - p.x) * s.y - (q.y - p.y) * s.x) / rxs;
|
|
74
115
|
return { x: p.x + t * r.x, y: p.y + t * r.y };
|
|
75
116
|
};
|
|
117
|
+
// Vertices closer than this are treated as duplicates (coordinates are in
|
|
118
|
+
// model metres, so 0.1 mm is far below anything architectural).
|
|
119
|
+
const DUPLICATE_EPS = 1e-4;
|
|
76
120
|
const offsetPoly = (poly) => {
|
|
77
|
-
|
|
121
|
+
// Sanitize the ring first: cut polylines usually repeat the first point
|
|
122
|
+
// as a closing point, and mesh cutting can emit consecutive
|
|
123
|
+
// near-duplicates. A zero-length edge has a zero normal, which
|
|
124
|
+
// degenerates the miter intersection and leaves that vertex unoffset —
|
|
125
|
+
// visible as a collapsed/distorted corner in the expanded outline.
|
|
126
|
+
const pts = [];
|
|
127
|
+
for (const p of poly) {
|
|
128
|
+
const last = pts[pts.length - 1];
|
|
129
|
+
if (last && Math.abs(last.x - p.x) < DUPLICATE_EPS && Math.abs(last.y - p.y) < DUPLICATE_EPS)
|
|
130
|
+
continue;
|
|
131
|
+
pts.push(p);
|
|
132
|
+
}
|
|
133
|
+
while (pts.length > 1 &&
|
|
134
|
+
Math.abs(pts[0].x - pts[pts.length - 1].x) < DUPLICATE_EPS &&
|
|
135
|
+
Math.abs(pts[0].y - pts[pts.length - 1].y) < DUPLICATE_EPS) {
|
|
136
|
+
pts.pop();
|
|
137
|
+
}
|
|
138
|
+
const n = pts.length;
|
|
78
139
|
if (n < 3)
|
|
79
140
|
return poly;
|
|
80
|
-
const area =
|
|
81
|
-
const q =
|
|
141
|
+
const area = pts.reduce((sum, p, i) => {
|
|
142
|
+
const q = pts[(i + 1) % n];
|
|
82
143
|
return sum + p.x * q.y - q.x * p.y;
|
|
83
144
|
}, 0);
|
|
84
145
|
const orient = area >= 0 ? 1 : -1; // ccw => outward is right normal
|
|
85
146
|
const out = [];
|
|
86
147
|
for (let i = 0; i < n; i++) {
|
|
87
|
-
const p0 =
|
|
88
|
-
const p1 =
|
|
89
|
-
const p2 =
|
|
148
|
+
const p0 = pts[(i + n - 1) % n];
|
|
149
|
+
const p1 = pts[i];
|
|
150
|
+
const p2 = pts[(i + 1) % n];
|
|
90
151
|
const e1x = p1.x - p0.x;
|
|
91
152
|
const e1y = p1.y - p0.y;
|
|
92
153
|
const e2x = p2.x - p1.x;
|