@ifc-lite/drawing-2d 1.19.0 → 1.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dxf-exporter.d.ts","sourceRoot":"","sources":["../src/dxf-exporter.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAA6C,OAAO,EAAE,MAAM,YAAY,CAAC;AAGhG,OAAO,EAAyB,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAG5F,MAAM,WAAW,gBAAgB;IAC/B,kFAAkF;IAClF,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iIAAiI;IACjI,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,qFAAqF;IACrF,SAAS,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACjC;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;IAC9C;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,0EAA0E;AAC1E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,WAAW,CAAC;IACtB,yDAAyD;IACzD,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,8EAA8E;IAC9E,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAaD,yEAAyE;AACzE,qBAAa,WAAW;IACtB,MAAM,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM;IA2ClE,OAAO,CAAC,SAAS;IAwBjB,OAAO,CAAC,oBAAoB;IAe5B,OAAO,CAAC,aAAa;CA+DtB;AAED,mDAAmD;AACnD,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAElF"}
@@ -0,0 +1,137 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ import { getLineStyle, getHatchPattern } from './styles.js';
5
+ import { applyDxfPlacement } from './dxf/convert.js';
6
+ import { DEFAULT_DXF_PLACEMENT } from './dxf/types.js';
7
+ import { DxfWriter } from './dxf/writer.js';
8
+ const CATEGORY_LAYER = {
9
+ cut: 'IFC-CUT',
10
+ projection: 'IFC-PROJECTION',
11
+ hidden: 'IFC-HIDDEN',
12
+ silhouette: 'IFC-SILHOUETTE',
13
+ crease: 'IFC-CREASE',
14
+ boundary: 'IFC-BOUNDARY',
15
+ annotation: 'IFC-ANNOTATION',
16
+ };
17
+ const FILL_LAYER = 'IFC-FILL';
18
+ /** Builds a DXF document from a `Drawing2D`, mirroring `SVGExporter`. */
19
+ export class DXFExporter {
20
+ export(drawing, options = {}) {
21
+ const { showHiddenLines = true, showHatching = true, underlays = [], coordinateTransform = (p) => p, metadataComment, } = options;
22
+ const writer = new DxfWriter({ headerComment: metadataComment });
23
+ const map = (p) => coordinateTransform(p);
24
+ // Layer per line category, DASHED linetype for the hidden layer only —
25
+ // matches how SVGExporter groups its <g> layers. Lines whose
26
+ // `visibility` is 'hidden' are routed onto the hidden layer regardless
27
+ // of category — see writeLine.
28
+ const categoryLayers = new Map();
29
+ for (const [category, name] of Object.entries(CATEGORY_LAYER)) {
30
+ const linetype = category === 'hidden' ? 'DASHED' : 'CONTINUOUS';
31
+ categoryLayers.set(category, writer.layer(name, getLineStyle(category).color, linetype));
32
+ }
33
+ const fillLayer = writer.layer(FILL_LAYER, '#666666');
34
+ // Hatching first (bottom-most, like SVGExporter's hatching layer).
35
+ if (showHatching) {
36
+ for (const polygon of drawing.cutPolygons) {
37
+ this.writePolygonBoundary(writer, polygon, fillLayer, map);
38
+ }
39
+ }
40
+ // Underlays beneath the drawing lines (SVGExporter renders them first too).
41
+ for (const underlayOptions of underlays) {
42
+ this.writeUnderlay(writer, underlayOptions, map);
43
+ }
44
+ for (const line of drawing.lines) {
45
+ if (!showHiddenLines && line.visibility === 'hidden')
46
+ continue;
47
+ this.writeLine(writer, line, categoryLayers, map);
48
+ }
49
+ return writer.toString();
50
+ }
51
+ writeLine(writer, line, categoryLayers, map) {
52
+ // Visibility wins over category (PR #1871 review, P2): hidden-line
53
+ // removal / construction projection can mark e.g. a `projection` line
54
+ // `visibility: 'hidden'`. Mapping by category alone would land it on the
55
+ // CONTINUOUS `IFC-PROJECTION` layer and occluded/overhead edges would
56
+ // read as solid in CAD. Mirror `exportToSVG`'s hidden-lines grouping
57
+ // (and the viewer canvas's dashed override): every `visibility ===
58
+ // 'hidden'` line joins the DASHED `IFC-HIDDEN` layer with the hidden
59
+ // line style. No new layer/LTYPE names — `IFC-HIDDEN` and `DASHED` are
60
+ // already declared in TABLES, so the output stays R12-legal.
61
+ const styleCategory = line.visibility === 'hidden' ? 'hidden' : line.category;
62
+ const layer = categoryLayers.get(styleCategory);
63
+ if (!layer)
64
+ return;
65
+ const style = getLineStyle(styleCategory, line.ifcType);
66
+ const start = map(line.line.start);
67
+ const end = map(line.line.end);
68
+ writer.addLine(start, end, layer, style.color);
69
+ }
70
+ writePolygonBoundary(writer, polygon, layer, map) {
71
+ const pattern = getHatchPattern(polygon.ifcType);
72
+ if (pattern.type === 'none')
73
+ return;
74
+ const outer = polygon.polygon.outer.map(map);
75
+ writer.addPolyline(outer, layer, true, pattern.strokeColor);
76
+ for (const hole of polygon.polygon.holes) {
77
+ writer.addPolyline(hole.map(map), layer, true, pattern.strokeColor);
78
+ }
79
+ }
80
+ writeUnderlay(writer, options, map) {
81
+ const { underlay, placement = DEFAULT_DXF_PLACEMENT, layerVisibility = {} } = options;
82
+ // Underlay points are already in world plan coordinates (metres, +Y
83
+ // north) — the same convention DXF itself uses, so (unlike SVG, which
84
+ // is +Y down) no sign flip is needed before the placement transform.
85
+ const mapPoint = (p) => map(applyDxfPlacement(p, placement));
86
+ const underlayPrefix = `DXF_${underlay.name}`;
87
+ for (const dxfLayer of underlay.layers) {
88
+ const visible = layerVisibility[dxfLayer.name] ?? dxfLayer.visible;
89
+ if (!visible)
90
+ continue;
91
+ const layer = writer.layer(`${underlayPrefix}_${dxfLayer.name}`, dxfLayer.color);
92
+ for (const fill of dxfLayer.fills) {
93
+ const outer = fill.polygon.outer.map(mapPoint);
94
+ writer.addPolyline(outer, layer, true, fill.color);
95
+ for (const hole of fill.polygon.holes) {
96
+ writer.addPolyline(hole.map(mapPoint), layer, true, fill.color);
97
+ }
98
+ }
99
+ for (const path of dxfLayer.paths) {
100
+ if (path.points.length < 2)
101
+ continue;
102
+ writer.addPolyline(path.points.map(mapPoint), layer, path.closed, path.color);
103
+ }
104
+ for (const text of dxfLayer.texts) {
105
+ if (!text.text.trim())
106
+ continue;
107
+ const anchor = mapPoint(text.position);
108
+ const tip = mapPoint({ x: text.position.x + text.dirX, y: text.position.y + text.dirY });
109
+ const angle = (Math.atan2(tip.y - anchor.y, tip.x - anchor.x) * 180) / Math.PI;
110
+ // Placement scales the geometry through mapPoint; the glyph height
111
+ // must follow or labels detach from their linework (same rule as
112
+ // SVGExporter's underlay text).
113
+ const height = text.height * placement.scale;
114
+ const lines = text.text.split('\n');
115
+ for (let i = 0; i < lines.length; i++) {
116
+ // Stack subsequent lines by offsetting in LOCAL space, then map:
117
+ // the stacking direction must rotate with the text when
118
+ // placement.rotationDeg is non-zero or coordinateTransform itself
119
+ // rotates (georeferenced rotated grid) — exactly like
120
+ // SVGExporter's rotate() wrapper around its tspan stack. An
121
+ // output-space `anchor.y - offset` would shift lines straight
122
+ // down regardless of the text's actual direction (PR #1871
123
+ // review).
124
+ const lineAnchor = i === 0
125
+ ? anchor
126
+ : mapPoint({ x: text.position.x, y: text.position.y - i * text.height * 1.3 });
127
+ writer.addText(lineAnchor, lines[i], height, layer, { rotationDeg: angle, hAlign: text.align, vAlign: text.valign, colorOverride: text.color });
128
+ }
129
+ }
130
+ }
131
+ }
132
+ }
133
+ /** Export a `Drawing2D` to an ASCII DXF string. */
134
+ export function exportToDXF(drawing, options) {
135
+ return new DXFExporter().export(drawing, options);
136
+ }
137
+ //# sourceMappingURL=dxf-exporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dxf-exporter.js","sourceRoot":"","sources":["../src/dxf-exporter.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -32,10 +32,14 @@ export { HatchGenerator } from './hatch-generator.js';
32
32
  export type { HatchLine, HatchResult, CustomHatchSettings } from './hatch-generator.js';
33
33
  export { SVGExporter, exportToSVG } from './svg-exporter.js';
34
34
  export type { SVGExportOptions } from './svg-exporter.js';
35
+ export { computePdfScaleLayout, worldPointToPdfMm, worldLengthToPdfMm, flipBounds2D, formatScaleFactorLabel, } from './pdf-scale.js';
36
+ export type { PdfScaleTransform, PdfPage, PdfScaleLayout, AxisFlip } from './pdf-scale.js';
37
+ export { DXFExporter, exportToDXF } from './dxf-exporter.js';
38
+ export type { DXFExportOptions, DXFUnderlayOptions } from './dxf-exporter.js';
35
39
  export { GPUSectionCutter, isGPUComputeAvailable } from './gpu-section-cutter.js';
36
40
  export { Drawing2DGenerator, createSectionConfig, generateFloorPlan, generateSection, } from './drawing-generator.js';
37
41
  export type { GeneratorOptions, GeneratorProgress } from './drawing-generator.js';
38
- export { EPSILON, vec3, vec3Add, vec3Sub, vec3Scale, vec3Dot, vec3Cross, vec3Length, vec3Normalize, vec3Lerp, vec3Equals, vec3Distance, point2D, point2DAdd, point2DSub, point2DScale, point2DDot, point2DLength, point2DDistance, point2DLerp, point2DEquals, point2DNormalize, point2DCross, lineLength, lineMidpoint, lineDirection, linesCollinear, projectPointOnLine, boundsEmpty, boundsExtendPoint, boundsExtendLine, boundsCenter, boundsSize, boundsValid, signedDistanceToPlane, getAxisNormal, getProjectionAxes, projectTo2D, polygonSignedArea, isCounterClockwise, reversePolygon, ensureCCW, ensureCW, } from './math.js';
42
+ export { EPSILON, vec3, vec3Add, vec3Sub, vec3Scale, vec3Dot, vec3Cross, vec3Length, vec3Normalize, vec3Lerp, vec3Equals, vec3Distance, point2D, point2DAdd, point2DSub, point2DScale, point2DDot, point2DLength, point2DDistance, point2DLerp, point2DEquals, point2DNormalize, point2DCross, lineLength, lineMidpoint, lineDirection, linesCollinear, projectPointOnLine, boundsEmpty, boundsExtendPoint, boundsExtendLine, boundsCenter, boundsSize, boundsValid, signedDistanceToPlane, getAxisNormal, getProjectionAxes, projectTo2D, projectTo2DBasis, polygonSignedArea, isCounterClockwise, reversePolygon, ensureCCW, ensureCW, } from './math.js';
39
43
  export { OpeningRelationshipBuilder, OpeningFilter, buildOpeningRelationships, getOpeningsForHost, getFillingElement, isOpeningElement, isDoorOrWindow, } from './openings/index.js';
40
44
  export type { OpeningInfo, OpeningRelationships, VoidRelationship, FillRelationship, DoorOperationType, WindowPartitioningType, EntityMetadata, DrawingContext, } from './types.js';
41
45
  export { DoorSymbolGenerator, WindowSymbolGenerator, SymbolRenderer, generateDoorSymbol, generateWindowSymbol, generateStairArrow, } from './symbols/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -53,6 +53,18 @@ export { HatchGenerator } from './hatch-generator.js';
53
53
  // SVG EXPORT
54
54
  // ═══════════════════════════════════════════════════════════════════════════
55
55
  export { SVGExporter, exportToSVG } from './svg-exporter.js';
56
+ // Scaled PDF export (issue #2042): pure scale/extent arithmetic only —
57
+ // the PDF is assembled by the viewer using jsPDF, which is not a
58
+ // dependency of this package.
59
+ export { computePdfScaleLayout, worldPointToPdfMm, worldLengthToPdfMm, flipBounds2D, formatScaleFactorLabel, } from './pdf-scale.js';
60
+ // ═══════════════════════════════════════════════════════════════════════════
61
+ // DXF EXPORT (issue #1861)
62
+ // ═══════════════════════════════════════════════════════════════════════════
63
+ // Only the exporter facade is public. The low-level writer internals
64
+ // (DxfWriter, sanitizeDxfLayerName, cssToAci, the linetype/justification
65
+ // types) stay package-private: no external consumer exists, and an unused
66
+ // public export is permanent semver liability (PR #1871 review).
67
+ export { DXFExporter, exportToDXF } from './dxf-exporter.js';
56
68
  // ═══════════════════════════════════════════════════════════════════════════
57
69
  // GPU ACCELERATION
58
70
  // ═══════════════════════════════════════════════════════════════════════════
@@ -76,7 +88,7 @@ lineLength, lineMidpoint, lineDirection, linesCollinear, projectPointOnLine,
76
88
  // Bounds operations
77
89
  boundsEmpty, boundsExtendPoint, boundsExtendLine, boundsCenter, boundsSize, boundsValid,
78
90
  // Plane operations
79
- signedDistanceToPlane, getAxisNormal, getProjectionAxes, projectTo2D,
91
+ signedDistanceToPlane, getAxisNormal, getProjectionAxes, projectTo2D, projectTo2DBasis,
80
92
  // Polygon operations
81
93
  polygonSignedArea, isCounterClockwise, reversePolygon, ensureCCW, ensureCW, } from './math.js';
82
94
  // ═══════════════════════════════════════════════════════════════════════════
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Pure scale/extent arithmetic for exporting a section drawing to a
3
+ * dimensionally accurate ("to scale") PDF page (issue #2042).
4
+ *
5
+ * The drawing model (`Drawing2D.bounds`) carries its own real-world extent
6
+ * in metres (see `types.ts`). To print "1:100" correctly, 1 metre in the
7
+ * world must become exactly 10mm on paper — no rounding, no silent re-fit.
8
+ *
9
+ * This module deliberately sizes the PAGE to the drawing extent at the
10
+ * chosen scale (plus a margin), rather than fitting the drawing into a
11
+ * fixed named paper size (A3, A4, ...). That avoids the failure mode in
12
+ * `sheet/sheet-types.ts`'s `calculateDrawingTransform`, which silently
13
+ * shrinks the drawing (`fitScale = min(scaleX, scaleY, 1)`) when it does
14
+ * not fit the configured viewport at the nominal scale — correct for an
15
+ * on-screen preview, but wrong for a PDF whose entire purpose is to be
16
+ * measured at the scale the user explicitly picked.
17
+ */
18
+ import type { Bounds2D, Point2D } from './types.js';
19
+ /** World-to-paper transform: maps a world-space point (metres) to a point
20
+ * on the PDF page (millimetres), with Y flipped (paper Y grows downward,
21
+ * world Y grows "up"/"north" depending on section axis). */
22
+ export interface PdfScaleTransform {
23
+ /** mm on paper per metre in the world. E.g. 1:100 -> 10; 1:50 -> 20. */
24
+ worldToMm: number;
25
+ offsetXMm: number;
26
+ offsetYMm: number;
27
+ }
28
+ export interface PdfPage {
29
+ widthMm: number;
30
+ heightMm: number;
31
+ }
32
+ export interface PdfScaleLayout {
33
+ transform: PdfScaleTransform;
34
+ page: PdfPage;
35
+ }
36
+ /**
37
+ * Compute the page size and world->paper-mm transform for exporting
38
+ * `bounds` (metres) at an exact `scaleFactor` (the "100" in "1:100"),
39
+ * with `marginMm` of blank paper around the drawing on every side.
40
+ *
41
+ * Throws on a non-finite/non-positive scale factor, a non-finite/negative
42
+ * margin, or degenerate bounds — silently producing a mis-scaled or
43
+ * zero-size PDF would look plausible and be wrong, which is the failure
44
+ * that matters most for a document someone measures from.
45
+ */
46
+ export declare function computePdfScaleLayout(bounds: Bounds2D, scaleFactor: number, marginMm?: number): PdfScaleLayout;
47
+ /** Which axes a caller intends to negate before mapping a point (matches the
48
+ * "as displayed" flips `useDrawingExport.ts` applies per section axis —
49
+ * `front`/`side` mirror the world X and/or Y before mapping to paper). */
50
+ export interface AxisFlip {
51
+ flipX: boolean;
52
+ flipY: boolean;
53
+ }
54
+ /**
55
+ * Flip `bounds` around world zero on the requested axes — i.e. the bounds
56
+ * of the geometry *as it will actually be drawn* after a caller negates
57
+ * point coordinates per `flip`.
58
+ *
59
+ * This exists because `computePdfScaleLayout`'s offsets are derived from
60
+ * `bounds.min`, which only lands the drawing on the page if the points
61
+ * handed to `worldPointToPdfMm` are drawn in that same, un-negated frame.
62
+ * A caller that negates X/Y when mapping points (as `front`/`side` section
63
+ * exports do) but derives the layout from the *original* bounds gets
64
+ * offsets computed for a frame the geometry was never actually drawn in —
65
+ * correct only when `bounds` happens to be symmetric about zero, and
66
+ * silently off-page for the common case of a model at positive world
67
+ * coordinates. Always deriving the layout from `flipBounds2D(bounds, flip)`
68
+ * keeps the two in sync regardless of where the drawing sits in world space.
69
+ */
70
+ export declare function flipBounds2D(bounds: Bounds2D, flip: AxisFlip): Bounds2D;
71
+ /**
72
+ * Format a "1:N" scale factor for a filename/label, rounding float noise
73
+ * away rather than truncating it: 2 decimal places, trailing zeros (and a
74
+ * bare trailing dot) stripped, so `99.5` reads "99.5" and `100.0000001`
75
+ * reads "100" rather than either carrying noise or rounding to a materially
76
+ * different scale.
77
+ *
78
+ * Extracted from `SVGExporter`'s title-block scale label (PR #2131, "stop
79
+ * the title block from lying about a clamped scale") so every place that
80
+ * turns a scale factor into a human-readable "N" — the title block, and the
81
+ * PDF export filename below — agrees on one rounding rule instead of each
82
+ * re-deriving (and potentially disagreeing on) its own.
83
+ */
84
+ export declare function formatScaleFactorLabel(factor: number): string;
85
+ /** Map one world-space point (metres) to paper space (mm) via `transform`. */
86
+ export declare function worldPointToPdfMm(point: Point2D, transform: PdfScaleTransform): Point2D;
87
+ /** Convert a line weight / length already expressed in mm-on-paper terms
88
+ * is a no-op; this converts a MODEL-space length (metres) to mm on paper
89
+ * at the given transform's scale — e.g. for stroke widths authored in
90
+ * world units. Most line/hatch weights in this codebase are already
91
+ * mm-on-paper constants, so this is only needed where a length is
92
+ * genuinely world-space (kept separate from `worldPointToPdfMm` so
93
+ * callers can't accidentally apply the Y-flip/offset to a scalar).
94
+ */
95
+ export declare function worldLengthToPdfMm(lengthMeters: number, transform: PdfScaleTransform): number;
96
+ //# sourceMappingURL=pdf-scale.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf-scale.d.ts","sourceRoot":"","sources":["../src/pdf-scale.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEpD;;6DAE6D;AAC7D,MAAM,WAAW,iBAAiB;IAChC,wEAAwE;IACxE,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,iBAAiB,CAAC;IAC7B,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,QAAQ,EAChB,WAAW,EAAE,MAAM,EACnB,QAAQ,SAAK,GACZ,cAAc,CA6DhB;AAED;;2EAE2E;AAC3E,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAWvE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED,8EAA8E;AAC9E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,iBAAiB,GAAG,OAAO,CAKvF;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,iBAAiB,GAAG,MAAM,CAE7F"}
@@ -0,0 +1,123 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ /**
5
+ * Compute the page size and world->paper-mm transform for exporting
6
+ * `bounds` (metres) at an exact `scaleFactor` (the "100" in "1:100"),
7
+ * with `marginMm` of blank paper around the drawing on every side.
8
+ *
9
+ * Throws on a non-finite/non-positive scale factor, a non-finite/negative
10
+ * margin, or degenerate bounds — silently producing a mis-scaled or
11
+ * zero-size PDF would look plausible and be wrong, which is the failure
12
+ * that matters most for a document someone measures from.
13
+ */
14
+ export function computePdfScaleLayout(bounds, scaleFactor, marginMm = 10) {
15
+ if (!Number.isFinite(scaleFactor) || scaleFactor <= 0) {
16
+ throw new Error(`Invalid PDF export scale factor: ${scaleFactor}. Expected a positive finite number (the "N" in "1:N").`);
17
+ }
18
+ if (!Number.isFinite(marginMm) || marginMm < 0) {
19
+ throw new Error(`Invalid PDF export margin: ${marginMm}mm. Expected a non-negative finite number.`);
20
+ }
21
+ const { min, max } = bounds;
22
+ if (!Number.isFinite(min.x) || !Number.isFinite(min.y) ||
23
+ !Number.isFinite(max.x) || !Number.isFinite(max.y)) {
24
+ throw new Error('Invalid drawing bounds for PDF export: bounds must be finite.');
25
+ }
26
+ const width = max.x - min.x;
27
+ const height = max.y - min.y;
28
+ if (width < 0 || height < 0) {
29
+ throw new Error('Invalid drawing bounds for PDF export: max must be >= min on both axes.');
30
+ }
31
+ // mm on paper per metre in the world. 1:100 means 1m -> 10mm on paper.
32
+ const worldToMm = 1000 / scaleFactor;
33
+ const drawingWidthMm = width * worldToMm;
34
+ const drawingHeightMm = height * worldToMm;
35
+ const page = {
36
+ widthMm: drawingWidthMm + marginMm * 2,
37
+ heightMm: drawingHeightMm + marginMm * 2,
38
+ };
39
+ // x: bounds.min.x -> marginMm (left edge of drawing area)
40
+ const offsetXMm = marginMm - min.x * worldToMm;
41
+ // y: flipped. bounds.min.y (world "bottom") -> page.heightMm - marginMm
42
+ // (paper bottom edge of drawing area); bounds.max.y -> marginMm (top).
43
+ const offsetYMm = page.heightMm - marginMm + min.y * worldToMm;
44
+ // Inputs were validated finite above, but finite inputs can still
45
+ // multiply/divide out to a non-finite output — e.g. a vanishingly small
46
+ // (but positive, so it passes the scaleFactor check) scale factor makes
47
+ // `worldToMm` overflow to Infinity, or enormous bounds combined with a
48
+ // large worldToMm overflow `page.widthMm`/`heightMm`. These are exactly
49
+ // the numbers that reach jsPDF's page-size/coordinate arguments, so catch
50
+ // it here rather than let jsPDF fail (or silently emit a garbage page).
51
+ if (!Number.isFinite(worldToMm) ||
52
+ !Number.isFinite(page.widthMm) || !Number.isFinite(page.heightMm) ||
53
+ !Number.isFinite(offsetXMm) || !Number.isFinite(offsetYMm)) {
54
+ throw new Error(`PDF export layout produced non-finite dimensions (worldToMm=${worldToMm}, ` +
55
+ `page=${page.widthMm}x${page.heightMm}mm) from scaleFactor=${scaleFactor} and bounds ` +
56
+ `${width}x${height}. Choose a less extreme scale.`);
57
+ }
58
+ return { transform: { worldToMm, offsetXMm, offsetYMm }, page };
59
+ }
60
+ /**
61
+ * Flip `bounds` around world zero on the requested axes — i.e. the bounds
62
+ * of the geometry *as it will actually be drawn* after a caller negates
63
+ * point coordinates per `flip`.
64
+ *
65
+ * This exists because `computePdfScaleLayout`'s offsets are derived from
66
+ * `bounds.min`, which only lands the drawing on the page if the points
67
+ * handed to `worldPointToPdfMm` are drawn in that same, un-negated frame.
68
+ * A caller that negates X/Y when mapping points (as `front`/`side` section
69
+ * exports do) but derives the layout from the *original* bounds gets
70
+ * offsets computed for a frame the geometry was never actually drawn in —
71
+ * correct only when `bounds` happens to be symmetric about zero, and
72
+ * silently off-page for the common case of a model at positive world
73
+ * coordinates. Always deriving the layout from `flipBounds2D(bounds, flip)`
74
+ * keeps the two in sync regardless of where the drawing sits in world space.
75
+ */
76
+ export function flipBounds2D(bounds, flip) {
77
+ return {
78
+ min: {
79
+ x: flip.flipX ? -bounds.max.x : bounds.min.x,
80
+ y: flip.flipY ? -bounds.max.y : bounds.min.y,
81
+ },
82
+ max: {
83
+ x: flip.flipX ? -bounds.min.x : bounds.max.x,
84
+ y: flip.flipY ? -bounds.min.y : bounds.max.y,
85
+ },
86
+ };
87
+ }
88
+ /**
89
+ * Format a "1:N" scale factor for a filename/label, rounding float noise
90
+ * away rather than truncating it: 2 decimal places, trailing zeros (and a
91
+ * bare trailing dot) stripped, so `99.5` reads "99.5" and `100.0000001`
92
+ * reads "100" rather than either carrying noise or rounding to a materially
93
+ * different scale.
94
+ *
95
+ * Extracted from `SVGExporter`'s title-block scale label (PR #2131, "stop
96
+ * the title block from lying about a clamped scale") so every place that
97
+ * turns a scale factor into a human-readable "N" — the title block, and the
98
+ * PDF export filename below — agrees on one rounding rule instead of each
99
+ * re-deriving (and potentially disagreeing on) its own.
100
+ */
101
+ export function formatScaleFactorLabel(factor) {
102
+ const rounded = Math.round(factor * 100) / 100;
103
+ return rounded % 1 === 0 ? rounded.toFixed(0) : rounded.toFixed(2).replace(/0+$/, '').replace(/\.$/, '');
104
+ }
105
+ /** Map one world-space point (metres) to paper space (mm) via `transform`. */
106
+ export function worldPointToPdfMm(point, transform) {
107
+ return {
108
+ x: point.x * transform.worldToMm + transform.offsetXMm,
109
+ y: -point.y * transform.worldToMm + transform.offsetYMm,
110
+ };
111
+ }
112
+ /** Convert a line weight / length already expressed in mm-on-paper terms
113
+ * is a no-op; this converts a MODEL-space length (metres) to mm on paper
114
+ * at the given transform's scale — e.g. for stroke widths authored in
115
+ * world units. Most line/hatch weights in this codebase are already
116
+ * mm-on-paper constants, so this is only needed where a length is
117
+ * genuinely world-space (kept separate from `worldPointToPdfMm` so
118
+ * callers can't accidentally apply the Y-flip/offset to a scalar).
119
+ */
120
+ export function worldLengthToPdfMm(lengthMeters, transform) {
121
+ return lengthMeters * transform.worldToMm;
122
+ }
123
+ //# sourceMappingURL=pdf-scale.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf-scale.js","sourceRoot":"","sources":["../src/pdf-scale.ts"],"names":[],"mappings":"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"}
@@ -122,8 +122,16 @@ export declare class PolygonBuilder {
122
122
  */
123
123
  private findConnectingSegment;
124
124
  /**
125
- * Classify loops as outer boundaries or holes
126
- * Uses containment testing and area sign
125
+ * Classify loops as outer boundaries or holes.
126
+ *
127
+ * Nesting can go more than one level deep — an island (e.g. a mullion
128
+ * cross-section, or a column stub) fully inside a hole (a window opening,
129
+ * a shaft) must become its OWN solid outer polygon, not a second hole of
130
+ * the outermost boundary. Each loop's classification is therefore relative
131
+ * to its NEAREST containing ancestor (the smallest-area loop that still
132
+ * contains it), not the top-level outer: walk the ancestor chain to get a
133
+ * nesting depth, then even depth (0, 2, 4, …) = solid outer boundary, odd
134
+ * depth (1, 3, …) = hole of its immediate (even-depth) parent.
127
135
  */
128
136
  private classifyLoops;
129
137
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"polygon-builder.d.ts","sourceRoot":"","sources":["../src/polygon-builder.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAa,UAAU,EAAE,cAAc,EAAa,MAAM,YAAY,CAAC;AA8B5F,qBAAa,cAAc;IACzB,oCAAoC;IACpC,OAAO,CAAC,SAAS,CAAS;gBAEd,SAAS,GAAE,MAAe;IAItC;;;OAGG;IACH,aAAa,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,cAAc,EAAE;IAuBvD;;;;;;;;;;;;OAYG;IACH,iBAAiB,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,cAAc,EAAE;IAsB3D;;;;;;;;;OASG;IACH,OAAO,CAAC,mBAAmB;IA0B3B;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAqC/B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,UAAU;IA8BlB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc;IAKtB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,oBAAoB;IAwF5B;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IA4BrB;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;IA8CxB;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,eAAe;IAuDvB;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA2B7B;;;OAGG;IACH,OAAO,CAAC,aAAa;IAwCrB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAMzB;;OAEG;IACH,OAAO,CAAC,cAAc;CAkBvB;AAMD;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,SAAS,GAAE,MAAc,GAAG,OAAO,EAAE,CAiBvF;AAWD;;GAEG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,OAAO,EAAE,GAChB;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,OAAO,CAAA;CAAE,CAiBhC"}
1
+ {"version":3,"file":"polygon-builder.d.ts","sourceRoot":"","sources":["../src/polygon-builder.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAa,UAAU,EAAE,cAAc,EAAa,MAAM,YAAY,CAAC;AA8B5F,qBAAa,cAAc;IACzB,oCAAoC;IACpC,OAAO,CAAC,SAAS,CAAS;gBAEd,SAAS,GAAE,MAAe;IAItC;;;OAGG;IACH,aAAa,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,cAAc,EAAE;IAuBvD;;;;;;;;;;;;OAYG;IACH,iBAAiB,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,cAAc,EAAE;IAsB3D;;;;;;;;;OASG;IACH,OAAO,CAAC,mBAAmB;IA0B3B;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAqC/B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,UAAU;IA8BlB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc;IAKtB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,oBAAoB;IAwF5B;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IA4BrB;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;IA8CxB;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,eAAe;IAuDvB;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA2B7B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,aAAa;IAyDrB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAMzB;;OAEG;IACH,OAAO,CAAC,cAAc;CAkBvB;AAMD;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,SAAS,GAAE,MAAc,GAAG,OAAO,EAAE,CAiBvF;AAWD;;GAEG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,OAAO,EAAE,GAChB;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,OAAO,CAAA;CAAE,CAiBhC"}
@@ -476,36 +476,62 @@ export class PolygonBuilder {
476
476
  return bestIdx;
477
477
  }
478
478
  /**
479
- * Classify loops as outer boundaries or holes
480
- * Uses containment testing and area sign
479
+ * Classify loops as outer boundaries or holes.
480
+ *
481
+ * Nesting can go more than one level deep — an island (e.g. a mullion
482
+ * cross-section, or a column stub) fully inside a hole (a window opening,
483
+ * a shaft) must become its OWN solid outer polygon, not a second hole of
484
+ * the outermost boundary. Each loop's classification is therefore relative
485
+ * to its NEAREST containing ancestor (the smallest-area loop that still
486
+ * contains it), not the top-level outer: walk the ancestor chain to get a
487
+ * nesting depth, then even depth (0, 2, 4, …) = solid outer boundary, odd
488
+ * depth (1, 3, …) = hole of its immediate (even-depth) parent.
481
489
  */
482
490
  classifyLoops(loops) {
483
491
  if (loops.length === 0)
484
492
  return [];
485
- // Sort by absolute area (largest first)
493
+ // Sort by absolute area (largest first). The parent search below only
494
+ // considers EARLIER (larger-or-equal-area) loops as containers: a genuine
495
+ // container can never be smaller than what it contains, and restricting
496
+ // parents to earlier indices keeps the relation acyclic even when
497
+ // overlapping or duplicate loops make the single-point containment test
498
+ // mutual (A "contains" B's start point and B "contains" A's) — without it
499
+ // the ancestor walk below would spin forever on such input.
486
500
  const sorted = [...loops].sort((a, b) => Math.abs(b.area) - Math.abs(a.area));
501
+ const n = sorted.length;
502
+ // Nearest containing ancestor: walking earlier loops from smallest area
503
+ // upward, the first one that contains this loop. -1 = top-level.
504
+ const parent = new Array(n).fill(-1);
505
+ for (let i = 0; i < n; i++) {
506
+ for (let j = i - 1; j >= 0; j--) {
507
+ if (this.isLoopContainedIn(sorted[i].points, sorted[j].points)) {
508
+ parent[i] = j;
509
+ break;
510
+ }
511
+ }
512
+ }
513
+ // Nesting depth = length of the ancestor chain to the top level.
514
+ const depth = new Array(n).fill(0);
515
+ for (let i = 0; i < n; i++) {
516
+ let d = 0;
517
+ let p = parent[i];
518
+ while (p !== -1) {
519
+ d++;
520
+ p = parent[p];
521
+ }
522
+ depth[i] = d;
523
+ }
487
524
  const result = [];
488
- const assigned = new Set();
489
- for (let i = 0; i < sorted.length; i++) {
490
- if (assigned.has(i))
491
- continue;
492
- const outer = sorted[i];
493
- // Ensure outer boundary is CCW
494
- const outerPoints = ensureCCW(outer.points);
495
- // Find holes (smaller loops contained within this one)
525
+ for (let i = 0; i < n; i++) {
526
+ if (depth[i] % 2 !== 0)
527
+ continue; // holes are emitted as part of their parent below
528
+ const outerPoints = ensureCCW(sorted[i].points);
496
529
  const holes = [];
497
- for (let j = i + 1; j < sorted.length; j++) {
498
- if (assigned.has(j))
499
- continue;
500
- const inner = sorted[j];
501
- // Check if inner is contained in outer
502
- if (this.isLoopContainedIn(inner.points, outerPoints)) {
503
- // Ensure hole is CW (opposite winding)
504
- holes.push(ensureCW(inner.points));
505
- assigned.add(j);
530
+ for (let j = 0; j < n; j++) {
531
+ if (parent[j] === i && depth[j] % 2 === 1) {
532
+ holes.push(ensureCW(sorted[j].points));
506
533
  }
507
534
  }
508
- assigned.add(i);
509
535
  result.push({ outer: outerPoints, holes });
510
536
  }
511
537
  return result;