@ifc-lite/drawing-2d 2.1.1 → 3.1.0
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/README.md +2 -3
- package/dist/dxf/convert.d.ts.map +1 -1
- package/dist/dxf/convert.js +16 -0
- package/dist/dxf/convert.js.map +1 -1
- package/dist/graphic-overrides/ifc-type-hierarchy.d.ts +38 -0
- package/dist/graphic-overrides/ifc-type-hierarchy.d.ts.map +1 -0
- package/dist/graphic-overrides/ifc-type-hierarchy.js +156 -0
- package/dist/graphic-overrides/ifc-type-hierarchy.js.map +1 -0
- package/dist/graphic-overrides/rule-engine.d.ts.map +1 -1
- package/dist/graphic-overrides/rule-engine.js +1 -34
- package/dist/graphic-overrides/rule-engine.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/openings/opening-filter.d.ts +10 -1
- package/dist/openings/opening-filter.d.ts.map +1 -1
- package/dist/openings/opening-filter.js.map +1 -1
- package/dist/sheet/index.d.ts +2 -4
- package/dist/sheet/index.d.ts.map +1 -1
- package/dist/sheet/index.js +1 -2
- package/dist/sheet/index.js.map +1 -1
- package/dist/sheet/scale-bar-types.d.ts +66 -36
- package/dist/sheet/scale-bar-types.d.ts.map +1 -1
- package/dist/sheet/scale-bar-types.js +79 -12
- package/dist/sheet/scale-bar-types.js.map +1 -1
- package/dist/sheet/sheet-types.d.ts +53 -0
- package/dist/sheet/sheet-types.d.ts.map +1 -1
- package/dist/sheet/sheet-types.js +53 -0
- package/dist/sheet/sheet-types.js.map +1 -1
- package/dist/sheet/title-block-renderer.d.ts.map +1 -1
- package/dist/sheet/title-block-renderer.js +111 -11
- package/dist/sheet/title-block-renderer.js.map +1 -1
- package/dist/styles.d.ts +22 -3
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +39 -8
- package/dist/styles.js.map +1 -1
- package/dist/svg-exporter.d.ts +9 -1
- package/dist/svg-exporter.d.ts.map +1 -1
- package/dist/svg-exporter.js.map +1 -1
- package/dist/symbols/door-symbol.d.ts +9 -1
- package/dist/symbols/door-symbol.d.ts.map +1 -1
- package/dist/symbols/door-symbol.js.map +1 -1
- package/package.json +5 -3
- package/dist/sheet/scale-bar-renderer.d.ts +0 -29
- package/dist/sheet/scale-bar-renderer.d.ts.map +0 -1
- package/dist/sheet/scale-bar-renderer.js +0 -287
- package/dist/sheet/scale-bar-renderer.js.map +0 -1
package/dist/sheet/index.d.ts
CHANGED
|
@@ -13,16 +13,14 @@ export type { FrameStyle, FrameBorderConfig, FrameMargins, DrawingFrame, } from
|
|
|
13
13
|
export { FRAME_PRESETS, createFrame, getDefaultFrame, } from './frame-types.js';
|
|
14
14
|
export type { TitleBlockPosition, TitleBlockLayout, TitleBlockField, TitleBlockLogo, RevisionEntry, TitleBlockConfig, } from './title-block-types.js';
|
|
15
15
|
export { DEFAULT_TITLE_BLOCK_FIELDS, TITLE_BLOCK_PRESETS, createTitleBlock, getDefaultTitleBlock, updateTitleBlockField, } from './title-block-types.js';
|
|
16
|
-
export type {
|
|
16
|
+
export type { ScaleBarConfig, NorthArrowStyle, NorthArrowConfig, } from './scale-bar-types.js';
|
|
17
17
|
export { DEFAULT_SCALE_BAR, DEFAULT_NORTH_ARROW, calculateOptimalScaleBarLength, calculateOptimalDivisions, } from './scale-bar-types.js';
|
|
18
18
|
export type { ScaleStampRect, ScaleStampText, ScaleStampBar, ScaleStamp, StampedSheetLayout, } from './scale-stamp.js';
|
|
19
19
|
export { addScaleStamp } from './scale-stamp.js';
|
|
20
20
|
export type { ViewportBounds, DrawingSheet, SheetCreationOptions, } from './sheet-types.js';
|
|
21
|
-
export { calculateViewportBounds, calculateDrawingTransform, } from './sheet-types.js';
|
|
21
|
+
export { calculateViewportBounds, calculateDrawingTransform, calculateDrawingTransformForAxis, } from './sheet-types.js';
|
|
22
22
|
export type { FrameRenderResult, } from './frame-renderer.js';
|
|
23
23
|
export { renderFrame } from './frame-renderer.js';
|
|
24
24
|
export type { FrameInnerBounds, TitleBlockRenderResult, TitleBlockExtras, } from './title-block-renderer.js';
|
|
25
25
|
export { renderTitleBlock } from './title-block-renderer.js';
|
|
26
|
-
export type { PositionMm } from './scale-bar-renderer.js';
|
|
27
|
-
export { renderScaleBar, renderNorthArrow } from './scale-bar-renderer.js';
|
|
28
26
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sheet/index.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AAGH,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,YAAY,EACZ,YAAY,GACb,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,aAAa,EACb,WAAW,EACX,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,aAAa,EACb,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,0BAA0B,EAC1B,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAGhC,YAAY,EACV,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sheet/index.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AAGH,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,YAAY,EACZ,YAAY,GACb,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,aAAa,EACb,WAAW,EACX,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,aAAa,EACb,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,0BAA0B,EAC1B,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAGhC,YAAY,EACV,cAAc,EACd,eAAe,EACf,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,8BAA8B,EAC9B,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAK9B,YAAY,EACV,cAAc,EACd,cAAc,EACd,aAAa,EACb,UAAU,EACV,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGjD,YAAY,EACV,cAAc,EACd,YAAY,EACZ,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EACzB,gCAAgC,GACjC,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EACV,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,YAAY,EACV,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC"}
|
package/dist/sheet/index.js
CHANGED
|
@@ -6,8 +6,7 @@ export { FRAME_PRESETS, createFrame, getDefaultFrame, } from './frame-types.js';
|
|
|
6
6
|
export { DEFAULT_TITLE_BLOCK_FIELDS, TITLE_BLOCK_PRESETS, createTitleBlock, getDefaultTitleBlock, updateTitleBlockField, } from './title-block-types.js';
|
|
7
7
|
export { DEFAULT_SCALE_BAR, DEFAULT_NORTH_ARROW, calculateOptimalScaleBarLength, calculateOptimalDivisions, } from './scale-bar-types.js';
|
|
8
8
|
export { addScaleStamp } from './scale-stamp.js';
|
|
9
|
-
export { calculateViewportBounds, calculateDrawingTransform, } from './sheet-types.js';
|
|
9
|
+
export { calculateViewportBounds, calculateDrawingTransform, calculateDrawingTransformForAxis, } from './sheet-types.js';
|
|
10
10
|
export { renderFrame } from './frame-renderer.js';
|
|
11
11
|
export { renderTitleBlock } from './title-block-renderer.js';
|
|
12
|
-
export { renderScaleBar, renderNorthArrow } from './scale-bar-renderer.js';
|
|
13
12
|
//# sourceMappingURL=index.js.map
|
package/dist/sheet/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sheet/index.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAkB/D,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAS1B,OAAO,EACL,aAAa,EACb,WAAW,EACX,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAW1B,OAAO,EACL,0BAA0B,EAC1B,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sheet/index.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAkB/D,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAS1B,OAAO,EACL,aAAa,EACb,WAAW,EACX,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAW1B,OAAO,EACL,0BAA0B,EAC1B,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAQhC,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,8BAA8B,EAC9B,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAY9B,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAQjD,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EACzB,gCAAgC,GACjC,MAAM,kBAAkB,CAAC;AAM1B,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAOlD,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC"}
|
|
@@ -1,44 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Scale Bar Types
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
4
|
+
* Configuration for the scale bar drawn in the sheet's title block
|
|
5
|
+
* (`renderScaleBarInTitleBlock` in `title-block-renderer.ts`, and its
|
|
6
|
+
* on-screen twin in the viewer's `Drawing2DCanvas`). Both draw one
|
|
7
|
+
* alternating-segment metric bar with `0` and end-distance labels; every
|
|
8
|
+
* field below is one that those renderers actually read.
|
|
8
9
|
*/
|
|
9
|
-
/** Scale bar style */
|
|
10
|
-
export type ScaleBarStyle = 'linear' | 'alternating' | 'single' | 'graphic';
|
|
11
|
-
/** Scale bar position relative to drawing viewport */
|
|
12
|
-
export type ScaleBarPosition = 'below-viewport' | 'above-viewport' | 'in-title-block' | 'custom';
|
|
13
|
-
/** Scale bar unit system */
|
|
14
|
-
export type ScaleBarUnits = 'metric' | 'imperial' | 'both';
|
|
15
10
|
/** Scale bar configuration */
|
|
16
11
|
export interface ScaleBarConfig {
|
|
17
12
|
/** Whether to show scale bar */
|
|
18
13
|
visible: boolean;
|
|
19
|
-
/** Scale bar style */
|
|
20
|
-
style: ScaleBarStyle;
|
|
21
|
-
/** Position */
|
|
22
|
-
position: ScaleBarPosition;
|
|
23
|
-
/** Custom position offset from default (mm) */
|
|
24
|
-
customOffset?: {
|
|
25
|
-
x: number;
|
|
26
|
-
y: number;
|
|
27
|
-
};
|
|
28
|
-
/** Unit system */
|
|
29
|
-
units: ScaleBarUnits;
|
|
30
14
|
/** Total length in model units (meters) */
|
|
31
15
|
totalLengthM: number;
|
|
32
16
|
/** Number of primary divisions */
|
|
33
17
|
primaryDivisions: number;
|
|
34
|
-
/**
|
|
35
|
-
subdivisions: number;
|
|
36
|
-
/** Bar height in mm */
|
|
18
|
+
/** Bar height in mm (clamped to 3mm by the renderer) */
|
|
37
19
|
heightMm: number;
|
|
38
|
-
/** Label font size in mm */
|
|
39
|
-
labelFontSize: number;
|
|
40
|
-
/** Show unit label (e.g., "meters") */
|
|
41
|
-
showUnitLabel: boolean;
|
|
42
20
|
/** Fill color for filled segments */
|
|
43
21
|
fillColor: string;
|
|
44
22
|
/** Stroke color */
|
|
@@ -48,6 +26,57 @@ export interface ScaleBarConfig {
|
|
|
48
26
|
}
|
|
49
27
|
/** Default scale bar configuration */
|
|
50
28
|
export declare const DEFAULT_SCALE_BAR: ScaleBarConfig;
|
|
29
|
+
/**
|
|
30
|
+
* Round distances a scale bar is allowed to span, ascending.
|
|
31
|
+
*
|
|
32
|
+
* Shared by the two things that pick a bar length — the sheet's default
|
|
33
|
+
* ("what should this bar be?") and the title-block renderer's snap ("largest
|
|
34
|
+
* one that fits this cell") — so they cannot disagree about what "nice" means.
|
|
35
|
+
* They were separate literals with identical contents, which is one edit away
|
|
36
|
+
* from divergence.
|
|
37
|
+
*
|
|
38
|
+
* Every entry survives the printed label's formatting exactly: whole
|
|
39
|
+
* centimetres below 1 m, whole metres above. That is the property that makes
|
|
40
|
+
* the snap able to promise an exact label, so a new entry has to keep it —
|
|
41
|
+
* 0.15 would print "15cm" correctly but 0.125 would print "13cm" and lie.
|
|
42
|
+
*
|
|
43
|
+
* Reaches 0.02 because `maxBarWidth` is `min(width * 0.3, 50)`. On the shipped
|
|
44
|
+
* compact preset (120 mm) that is 36 mm, and stopping the ladder at 0.1 left
|
|
45
|
+
* both 1:1 and 1:2 with no bar at all. At the full 50 mm only 1:1 fails, since
|
|
46
|
+
* 0.1 m at 1:2 is exactly 50 mm — so 36 is the binding number, which is what
|
|
47
|
+
* `scale-bar-ladder.test.ts` asserts against.
|
|
48
|
+
*/
|
|
49
|
+
export declare const NICE_BAR_LENGTHS_M: readonly number[];
|
|
50
|
+
/**
|
|
51
|
+
* Whether `m` survives the scale bar's label formatting without lying:
|
|
52
|
+
* whole centimetres below 1 m, whole metres above.
|
|
53
|
+
*/
|
|
54
|
+
export declare function barLabelIsExact(m: number): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Smallest SEGMENT worth drawing, in millimetres of paper.
|
|
57
|
+
*
|
|
58
|
+
* Segment widths are emitted through `toFixed(2)`, so anything under 0.005 mm
|
|
59
|
+
* prints `width="0.00"` — a label with no bar under it. That is reachable: at
|
|
60
|
+
* 1:1 on a 760 m model the transform gives 0.42 mm per metre, so a 5 cm bar is
|
|
61
|
+
* 0.02 mm wide and all five of its segments print zero. The old clamp-to-cell
|
|
62
|
+
* hid this by always stretching the bar to fill the cell; choosing the distance
|
|
63
|
+
* first removed that accident, so the floor has to be explicit.
|
|
64
|
+
*
|
|
65
|
+
* Deliberately a per-SEGMENT bound rather than a whole-bar one. A blanket bar
|
|
66
|
+
* minimum of 5 mm rejects a perfectly legible 4 mm bar at 1:500, which is a
|
|
67
|
+
* real configuration; what actually fails is a segment rounding to zero, and
|
|
68
|
+
* that is what this measures. 0.01 mm is twice the rounding threshold.
|
|
69
|
+
*
|
|
70
|
+
* A correction to an earlier version of this note, because it misattributed
|
|
71
|
+
* the mechanism: the old `Math.min(barLengthMm, maxBarWidth)` clamp could not
|
|
72
|
+
* ENLARGE anything. What filled the cell was the reciprocal-units bug — at
|
|
73
|
+
* 0.42 mm per metre the old formula computed `0.05 * 1000 / 0.42` = 119 mm and
|
|
74
|
+
* the clamp capped that at 50. So one bug was hiding another, which is why
|
|
75
|
+
* fixing the arithmetic is what exposed this.
|
|
76
|
+
*/
|
|
77
|
+
export declare const MIN_BAR_SEGMENT_MM = 0.01;
|
|
78
|
+
/** The end label the scale bar prints for a given distance. */
|
|
79
|
+
export declare function formatBarLabel(m: number): string;
|
|
51
80
|
/**
|
|
52
81
|
* Calculate optimal scale bar length based on drawing scale
|
|
53
82
|
* Returns length in model units (meters)
|
|
@@ -63,18 +92,19 @@ export declare function calculateOptimalScaleBarLength(scaleFactor: number, maxL
|
|
|
63
92
|
export declare function calculateOptimalDivisions(totalLengthM: number): number;
|
|
64
93
|
/** North arrow style */
|
|
65
94
|
export type NorthArrowStyle = 'simple' | 'compass' | 'decorative' | 'none';
|
|
66
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* North arrow configuration.
|
|
97
|
+
*
|
|
98
|
+
* The arrow is drawn at a fixed spot in the title block, so there is no
|
|
99
|
+
* position field: `style` only selects between drawn ('simple', 'compass',
|
|
100
|
+
* 'decorative' all render the same glyph today) and not drawn ('none').
|
|
101
|
+
*/
|
|
67
102
|
export interface NorthArrowConfig {
|
|
68
|
-
/** Arrow style */
|
|
103
|
+
/** Arrow style; 'none' suppresses the arrow */
|
|
69
104
|
style: NorthArrowStyle;
|
|
70
105
|
/** Rotation in degrees (0 = up) */
|
|
71
106
|
rotation: number;
|
|
72
|
-
/**
|
|
73
|
-
positionMm: {
|
|
74
|
-
x: number;
|
|
75
|
-
y: number;
|
|
76
|
-
};
|
|
77
|
-
/** Size in mm */
|
|
107
|
+
/** Size in mm (clamped by the renderer to 8mm and to the title block) */
|
|
78
108
|
sizeMm: number;
|
|
79
109
|
}
|
|
80
110
|
/** Default north arrow configuration */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scale-bar-types.d.ts","sourceRoot":"","sources":["../../src/sheet/scale-bar-types.ts"],"names":[],"mappings":"AAIA
|
|
1
|
+
{"version":3,"file":"scale-bar-types.d.ts","sourceRoot":"","sources":["../../src/sheet/scale-bar-types.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AAEH,8BAA8B;AAC9B,MAAM,WAAW,cAAc;IAC7B,gCAAgC;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,2CAA2C;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,kCAAkC;IAClC,gBAAgB,EAAE,MAAM,CAAC;IACzB,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,sCAAsC;AACtC,eAAO,MAAM,iBAAiB,EAAE,cAQ/B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAE/C,CAAC;AAEF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAsBlD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC,+DAA+D;AAC/D,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAC5C,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,MAAM,CAeR;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAMtE;AAED,wBAAwB;AACxB,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,GAAG,MAAM,CAAC;AAE3E;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+CAA+C;IAC/C,KAAK,EAAE,eAAe,CAAC;IACvB,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wCAAwC;AACxC,eAAO,MAAM,mBAAmB,EAAE,gBAIjC,CAAC"}
|
|
@@ -4,19 +4,91 @@
|
|
|
4
4
|
/** Default scale bar configuration */
|
|
5
5
|
export const DEFAULT_SCALE_BAR = {
|
|
6
6
|
visible: true,
|
|
7
|
-
style: 'alternating',
|
|
8
|
-
position: 'below-viewport',
|
|
9
|
-
units: 'metric',
|
|
10
7
|
totalLengthM: 5, // Will be auto-calculated based on scale
|
|
11
8
|
primaryDivisions: 5,
|
|
12
|
-
subdivisions: 2,
|
|
13
9
|
heightMm: 3,
|
|
14
|
-
labelFontSize: 2.5,
|
|
15
|
-
showUnitLabel: true,
|
|
16
10
|
fillColor: '#000000',
|
|
17
11
|
strokeColor: '#000000',
|
|
18
12
|
lineWeight: 0.25,
|
|
19
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* Round distances a scale bar is allowed to span, ascending.
|
|
16
|
+
*
|
|
17
|
+
* Shared by the two things that pick a bar length — the sheet's default
|
|
18
|
+
* ("what should this bar be?") and the title-block renderer's snap ("largest
|
|
19
|
+
* one that fits this cell") — so they cannot disagree about what "nice" means.
|
|
20
|
+
* They were separate literals with identical contents, which is one edit away
|
|
21
|
+
* from divergence.
|
|
22
|
+
*
|
|
23
|
+
* Every entry survives the printed label's formatting exactly: whole
|
|
24
|
+
* centimetres below 1 m, whole metres above. That is the property that makes
|
|
25
|
+
* the snap able to promise an exact label, so a new entry has to keep it —
|
|
26
|
+
* 0.15 would print "15cm" correctly but 0.125 would print "13cm" and lie.
|
|
27
|
+
*
|
|
28
|
+
* Reaches 0.02 because `maxBarWidth` is `min(width * 0.3, 50)`. On the shipped
|
|
29
|
+
* compact preset (120 mm) that is 36 mm, and stopping the ladder at 0.1 left
|
|
30
|
+
* both 1:1 and 1:2 with no bar at all. At the full 50 mm only 1:1 fails, since
|
|
31
|
+
* 0.1 m at 1:2 is exactly 50 mm — so 36 is the binding number, which is what
|
|
32
|
+
* `scale-bar-ladder.test.ts` asserts against.
|
|
33
|
+
*/
|
|
34
|
+
export const NICE_BAR_LENGTHS_M = [
|
|
35
|
+
0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000,
|
|
36
|
+
];
|
|
37
|
+
/**
|
|
38
|
+
* Whether `m` survives the scale bar's label formatting without lying:
|
|
39
|
+
* whole centimetres below 1 m, whole metres above.
|
|
40
|
+
*/
|
|
41
|
+
export function barLabelIsExact(m) {
|
|
42
|
+
// States the contract explicitly. The arithmetic below happens to reject all
|
|
43
|
+
// of these too — -5 fails the `>= 1` floor, NaN and Infinity fail because
|
|
44
|
+
// `Math.abs(NaN - NaN)` is not `< 1e-9` — so removing this line changes no
|
|
45
|
+
// test. It is kept as documentation a reader does not have to derive, not as
|
|
46
|
+
// a load-bearing check.
|
|
47
|
+
if (!(m > 0) || !Number.isFinite(m))
|
|
48
|
+
return false;
|
|
49
|
+
// Symmetric epsilon on both branches. `Number.isInteger` alone is too strict
|
|
50
|
+
// for the float artefacts the ladder itself carries (`0.02 * 100` is
|
|
51
|
+
// 2.0000000000000004), and `Math.round(m * 1000) / 10` was too loose in the
|
|
52
|
+
// other direction: it tolerates ±0.5 mm, so 0.0201 m passed and printed
|
|
53
|
+
// "2cm" over a bar 0.1 mm longer than that.
|
|
54
|
+
//
|
|
55
|
+
// The `>= 1` is what stops a sub-centimetre length passing. Without it,
|
|
56
|
+
// anything under 0.005 m rounds to zero centimetres, which is an integer, so
|
|
57
|
+
// it was "exact" — and drew a 0.4 mm bar labelled "0cm". A bar claiming to
|
|
58
|
+
// span no distance is less honest than no bar.
|
|
59
|
+
if (m < 1) {
|
|
60
|
+
const cm = m * 100;
|
|
61
|
+
return Math.abs(cm - Math.round(cm)) < 1e-9 && Math.round(cm) >= 1;
|
|
62
|
+
}
|
|
63
|
+
return Math.abs(m - Math.round(m)) < 1e-9;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Smallest SEGMENT worth drawing, in millimetres of paper.
|
|
67
|
+
*
|
|
68
|
+
* Segment widths are emitted through `toFixed(2)`, so anything under 0.005 mm
|
|
69
|
+
* prints `width="0.00"` — a label with no bar under it. That is reachable: at
|
|
70
|
+
* 1:1 on a 760 m model the transform gives 0.42 mm per metre, so a 5 cm bar is
|
|
71
|
+
* 0.02 mm wide and all five of its segments print zero. The old clamp-to-cell
|
|
72
|
+
* hid this by always stretching the bar to fill the cell; choosing the distance
|
|
73
|
+
* first removed that accident, so the floor has to be explicit.
|
|
74
|
+
*
|
|
75
|
+
* Deliberately a per-SEGMENT bound rather than a whole-bar one. A blanket bar
|
|
76
|
+
* minimum of 5 mm rejects a perfectly legible 4 mm bar at 1:500, which is a
|
|
77
|
+
* real configuration; what actually fails is a segment rounding to zero, and
|
|
78
|
+
* that is what this measures. 0.01 mm is twice the rounding threshold.
|
|
79
|
+
*
|
|
80
|
+
* A correction to an earlier version of this note, because it misattributed
|
|
81
|
+
* the mechanism: the old `Math.min(barLengthMm, maxBarWidth)` clamp could not
|
|
82
|
+
* ENLARGE anything. What filled the cell was the reciprocal-units bug — at
|
|
83
|
+
* 0.42 mm per metre the old formula computed `0.05 * 1000 / 0.42` = 119 mm and
|
|
84
|
+
* the clamp capped that at 50. So one bug was hiding another, which is why
|
|
85
|
+
* fixing the arithmetic is what exposed this.
|
|
86
|
+
*/
|
|
87
|
+
export const MIN_BAR_SEGMENT_MM = 0.01;
|
|
88
|
+
/** The end label the scale bar prints for a given distance. */
|
|
89
|
+
export function formatBarLabel(m) {
|
|
90
|
+
return m < 1 ? `${(m * 100).toFixed(0)}cm` : `${m.toFixed(0)}m`;
|
|
91
|
+
}
|
|
20
92
|
/**
|
|
21
93
|
* Calculate optimal scale bar length based on drawing scale
|
|
22
94
|
* Returns length in model units (meters)
|
|
@@ -29,11 +101,7 @@ export function calculateOptimalScaleBarLength(scaleFactor, maxLengthMm) {
|
|
|
29
101
|
const targetPaperLengthMm = Math.min(80, maxLengthMm * 0.8);
|
|
30
102
|
// Convert paper length to model units (meters)
|
|
31
103
|
const modelLength = (targetPaperLengthMm * scaleFactor) / 1000;
|
|
32
|
-
|
|
33
|
-
const niceNumbers = [
|
|
34
|
-
0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000,
|
|
35
|
-
];
|
|
36
|
-
for (const n of niceNumbers) {
|
|
104
|
+
for (const n of NICE_BAR_LENGTHS_M) {
|
|
37
105
|
if (n >= modelLength * 0.5 && n <= modelLength * 1.5) {
|
|
38
106
|
return n;
|
|
39
107
|
}
|
|
@@ -60,7 +128,6 @@ export function calculateOptimalDivisions(totalLengthM) {
|
|
|
60
128
|
export const DEFAULT_NORTH_ARROW = {
|
|
61
129
|
style: 'simple',
|
|
62
130
|
rotation: 0,
|
|
63
|
-
positionMm: { x: 30, y: 30 },
|
|
64
131
|
sizeMm: 15,
|
|
65
132
|
};
|
|
66
133
|
//# sourceMappingURL=scale-bar-types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scale-bar-types.js","sourceRoot":"","sources":["../../src/sheet/scale-bar-types.ts"],"names":[],"mappings":"AAAA;;+DAE+D;
|
|
1
|
+
{"version":3,"file":"scale-bar-types.js","sourceRoot":"","sources":["../../src/sheet/scale-bar-types.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AA8B/D,sCAAsC;AACtC,MAAM,CAAC,MAAM,iBAAiB,GAAmB;IAC/C,OAAO,EAAE,IAAI;IACb,YAAY,EAAE,CAAC,EAAE,yCAAyC;IAC1D,gBAAgB,EAAE,CAAC;IACnB,QAAQ,EAAE,CAAC;IACX,SAAS,EAAE,SAAS;IACpB,WAAW,EAAE,SAAS;IACtB,UAAU,EAAE,IAAI;CACjB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAsB;IACnD,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI;CACpE,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,CAAS;IACvC,6EAA6E;IAC7E,0EAA0E;IAC1E,2EAA2E;IAC3E,6EAA6E;IAC7E,wBAAwB;IACxB,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,6EAA6E;IAC7E,qEAAqE;IACrE,4EAA4E;IAC5E,wEAAwE;IACxE,4CAA4C;IAC5C,EAAE;IACF,wEAAwE;IACxE,6EAA6E;IAC7E,2EAA2E;IAC3E,+CAA+C;IAC/C,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACV,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;QACnB,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;AAC5C,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEvC,+DAA+D;AAC/D,MAAM,UAAU,cAAc,CAAC,CAAS;IACtC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;AAClE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,8BAA8B,CAC5C,WAAmB,EACnB,WAAmB;IAEnB,2CAA2C;IAC3C,MAAM,mBAAmB,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,WAAW,GAAG,GAAG,CAAC,CAAC;IAE5D,+CAA+C;IAC/C,MAAM,WAAW,GAAG,CAAC,mBAAmB,GAAG,WAAW,CAAC,GAAG,IAAI,CAAC;IAE/D,KAAK,MAAM,CAAC,IAAI,kBAAkB,EAAE,CAAC;QACnC,IAAI,CAAC,IAAI,WAAW,GAAG,GAAG,IAAI,CAAC,IAAI,WAAW,GAAG,GAAG,EAAE,CAAC;YACrD,OAAO,CAAC,CAAC;QACX,CAAC;IACH,CAAC;IAED,6BAA6B;IAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;AACjC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,YAAoB;IAC5D,IAAI,YAAY,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAChC,IAAI,YAAY,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAChC,IAAI,YAAY,IAAI,EAAE;QAAE,OAAO,CAAC,CAAC;IACjC,IAAI,YAAY,IAAI,EAAE;QAAE,OAAO,CAAC,CAAC;IACjC,OAAO,CAAC,CAAC,CAAC,yBAAyB;AACrC,CAAC;AAqBD,wCAAwC;AACxC,MAAM,CAAC,MAAM,mBAAmB,GAAqB;IACnD,KAAK,EAAE,QAAQ;IACf,QAAQ,EAAE,CAAC;IACX,MAAM,EAAE,EAAE;CACX,CAAC"}
|
|
@@ -81,4 +81,57 @@ export declare function calculateDrawingTransform(drawingBounds: {
|
|
|
81
81
|
translateY: number;
|
|
82
82
|
scaleFactor: number;
|
|
83
83
|
};
|
|
84
|
+
/**
|
|
85
|
+
* `calculateDrawingTransform` always derives `translateY` assuming the
|
|
86
|
+
* caller will map model Y to paper Y with a sign flip (`adjustedY = -y`,
|
|
87
|
+
* matching a plan view drawn with north/Y+ up on paper) — it centers using
|
|
88
|
+
* `drawingBounds.maxY` for exactly that reason (see the "Flip Y" comment
|
|
89
|
+
* above).
|
|
90
|
+
*
|
|
91
|
+
* Plan ('down') sections do NOT flip Y (see `Drawing2DCanvas.tsx`'s
|
|
92
|
+
* `flipY = sectionAxis !== 'down'`): they map `adjustedY = y` directly, so
|
|
93
|
+
* north stays up without a sign flip. Reusing the flipped `translateY`
|
|
94
|
+
* unmodified in that case still centers the WIDTH but shifts the centered Y
|
|
95
|
+
* off by `(minY + maxY) * scaleFactor` — the drawing lands off-center on the
|
|
96
|
+
* sheet, worse the further the section's bounds sit from being symmetric
|
|
97
|
+
* about Y=0. This offset was found diverging between the sheet PREVIEW
|
|
98
|
+
* (`Drawing2DCanvas.tsx`, which already carried this correction) and the
|
|
99
|
+
* sheet PRINT/EXPORT path (`useDrawingExport.ts`'s `generateSheetSVG`, which
|
|
100
|
+
* did not) — issue #2940.
|
|
101
|
+
*
|
|
102
|
+
* Call this instead of `calculateDrawingTransform` directly wherever the
|
|
103
|
+
* axis's flip behavior is known, so every sheet consumer (screen preview,
|
|
104
|
+
* print, SVG/PDF export) derives the flip correction from one place rather
|
|
105
|
+
* than each re-deriving it. Note this function is the geometry only: it
|
|
106
|
+
* takes no view state, so two callers still agree only insofar as they pass
|
|
107
|
+
* the same `flipY`/`flipX` AND resolve any cached/pinned placement the same
|
|
108
|
+
* way. The viewer routes both sheet consumers through one resolver
|
|
109
|
+
* (`apps/viewer/src/lib/drawing/sheet-transform.ts`) for exactly that
|
|
110
|
+
* reason.
|
|
111
|
+
*
|
|
112
|
+
* `translateX` needs the mirror-image correction, but gated the OPPOSITE
|
|
113
|
+
* way from `translateY`: `calculateDrawingTransform`'s `translateX` bakes
|
|
114
|
+
* in the assumption that the caller maps model X to paper X with NO sign
|
|
115
|
+
* flip (`adjustedX = x`) — unlike `translateY`, which bakes in a flip. So
|
|
116
|
+
* `translateX` is only wrong when `flipX` is actually true (`adjustedX =
|
|
117
|
+
* -x`, used for 'side' sections to view from the conventional direction,
|
|
118
|
+
* see `Drawing2DCanvas.tsx` / `useDrawingExport.ts`'s `flipX = sectionAxis
|
|
119
|
+
* === 'side'`): flipping negates and reverses the drawing's X extent to
|
|
120
|
+
* `[-maxX, -minX]`, so the base translate (keyed off `-minX`) shifts the
|
|
121
|
+
* centered box by `(minX + maxX) * scaleFactor` — the drawing lands off
|
|
122
|
+
* the left/right edge of the sheet, worse the further the section's bounds
|
|
123
|
+
* sit from being symmetric about X=0. Confirmed unfixed prior to this
|
|
124
|
+
* change (issue #2940's X-axis half): a 'side' section with bounds minX=2,
|
|
125
|
+
* maxX=12 on a 190mm-wide viewport landed 140mm off-center.
|
|
126
|
+
*/
|
|
127
|
+
export declare function calculateDrawingTransformForAxis(drawingBounds: {
|
|
128
|
+
minX: number;
|
|
129
|
+
minY: number;
|
|
130
|
+
maxX: number;
|
|
131
|
+
maxY: number;
|
|
132
|
+
}, viewportBounds: ViewportBounds, scale: DrawingScale, flipY: boolean, flipX?: boolean): {
|
|
133
|
+
translateX: number;
|
|
134
|
+
translateY: number;
|
|
135
|
+
scaleFactor: number;
|
|
136
|
+
};
|
|
84
137
|
//# sourceMappingURL=sheet-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sheet-types.d.ts","sourceRoot":"","sources":["../../src/sheet/sheet-types.ts"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC7B,8CAA8C;IAC9C,CAAC,EAAE,MAAM,CAAC;IACV,6CAA6C;IAC7C,CAAC,EAAE,MAAM,CAAC;IACV,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,2CAA2C;AAC3C,MAAM,WAAW,YAAY;IAC3B,8BAA8B;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,KAAK,EAAE,mBAAmB,CAAC;IAC3B,kCAAkC;IAClC,KAAK,EAAE,YAAY,CAAC;IACpB,gCAAgC;IAChC,UAAU,EAAE,gBAAgB,CAAC;IAC7B,8BAA8B;IAC9B,QAAQ,EAAE,cAAc,CAAC;IACzB,oBAAoB;IACpB,KAAK,EAAE,YAAY,CAAC;IACpB,gCAAgC;IAChC,UAAU,EAAE,gBAAgB,CAAC;IAC7B,8DAA8D;IAC9D,cAAc,EAAE,cAAc,CAAC;IAC/B,uBAAuB;IACvB,SAAS,EAAE,aAAa,EAAE,CAAC;CAC5B;AAED,6BAA6B;AAC7B,MAAM,WAAW,oBAAoB;IACnC,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kBAAkB;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yBAAyB;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oBAAoB;IACpB,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,mBAAmB,EAC1B,KAAK,EAAE,YAAY,EACnB,UAAU,EAAE,gBAAgB,GAC3B,cAAc,CA4ChB;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACvC,aAAa,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACzE,cAAc,EAAE,cAAc,EAC9B,KAAK,EAAE,YAAY,GAClB;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB,CAgCA"}
|
|
1
|
+
{"version":3,"file":"sheet-types.d.ts","sourceRoot":"","sources":["../../src/sheet/sheet-types.ts"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC7B,8CAA8C;IAC9C,CAAC,EAAE,MAAM,CAAC;IACV,6CAA6C;IAC7C,CAAC,EAAE,MAAM,CAAC;IACV,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,2CAA2C;AAC3C,MAAM,WAAW,YAAY;IAC3B,8BAA8B;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,KAAK,EAAE,mBAAmB,CAAC;IAC3B,kCAAkC;IAClC,KAAK,EAAE,YAAY,CAAC;IACpB,gCAAgC;IAChC,UAAU,EAAE,gBAAgB,CAAC;IAC7B,8BAA8B;IAC9B,QAAQ,EAAE,cAAc,CAAC;IACzB,oBAAoB;IACpB,KAAK,EAAE,YAAY,CAAC;IACpB,gCAAgC;IAChC,UAAU,EAAE,gBAAgB,CAAC;IAC7B,8DAA8D;IAC9D,cAAc,EAAE,cAAc,CAAC;IAC/B,uBAAuB;IACvB,SAAS,EAAE,aAAa,EAAE,CAAC;CAC5B;AAED,6BAA6B;AAC7B,MAAM,WAAW,oBAAoB;IACnC,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kBAAkB;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yBAAyB;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oBAAoB;IACpB,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,mBAAmB,EAC1B,KAAK,EAAE,YAAY,EACnB,UAAU,EAAE,gBAAgB,GAC3B,cAAc,CA4ChB;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACvC,aAAa,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACzE,cAAc,EAAE,cAAc,EAC9B,KAAK,EAAE,YAAY,GAClB;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB,CAgCA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,gCAAgC,CAC9C,aAAa,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACzE,cAAc,EAAE,cAAc,EAC9B,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,OAAO,EACd,KAAK,UAAQ,GACZ;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB,CASA"}
|
|
@@ -74,4 +74,57 @@ export function calculateDrawingTransform(drawingBounds, viewportBounds, scale)
|
|
|
74
74
|
drawingBounds.maxY * scaleFactor; // Flip Y
|
|
75
75
|
return { translateX, translateY, scaleFactor };
|
|
76
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* `calculateDrawingTransform` always derives `translateY` assuming the
|
|
79
|
+
* caller will map model Y to paper Y with a sign flip (`adjustedY = -y`,
|
|
80
|
+
* matching a plan view drawn with north/Y+ up on paper) — it centers using
|
|
81
|
+
* `drawingBounds.maxY` for exactly that reason (see the "Flip Y" comment
|
|
82
|
+
* above).
|
|
83
|
+
*
|
|
84
|
+
* Plan ('down') sections do NOT flip Y (see `Drawing2DCanvas.tsx`'s
|
|
85
|
+
* `flipY = sectionAxis !== 'down'`): they map `adjustedY = y` directly, so
|
|
86
|
+
* north stays up without a sign flip. Reusing the flipped `translateY`
|
|
87
|
+
* unmodified in that case still centers the WIDTH but shifts the centered Y
|
|
88
|
+
* off by `(minY + maxY) * scaleFactor` — the drawing lands off-center on the
|
|
89
|
+
* sheet, worse the further the section's bounds sit from being symmetric
|
|
90
|
+
* about Y=0. This offset was found diverging between the sheet PREVIEW
|
|
91
|
+
* (`Drawing2DCanvas.tsx`, which already carried this correction) and the
|
|
92
|
+
* sheet PRINT/EXPORT path (`useDrawingExport.ts`'s `generateSheetSVG`, which
|
|
93
|
+
* did not) — issue #2940.
|
|
94
|
+
*
|
|
95
|
+
* Call this instead of `calculateDrawingTransform` directly wherever the
|
|
96
|
+
* axis's flip behavior is known, so every sheet consumer (screen preview,
|
|
97
|
+
* print, SVG/PDF export) derives the flip correction from one place rather
|
|
98
|
+
* than each re-deriving it. Note this function is the geometry only: it
|
|
99
|
+
* takes no view state, so two callers still agree only insofar as they pass
|
|
100
|
+
* the same `flipY`/`flipX` AND resolve any cached/pinned placement the same
|
|
101
|
+
* way. The viewer routes both sheet consumers through one resolver
|
|
102
|
+
* (`apps/viewer/src/lib/drawing/sheet-transform.ts`) for exactly that
|
|
103
|
+
* reason.
|
|
104
|
+
*
|
|
105
|
+
* `translateX` needs the mirror-image correction, but gated the OPPOSITE
|
|
106
|
+
* way from `translateY`: `calculateDrawingTransform`'s `translateX` bakes
|
|
107
|
+
* in the assumption that the caller maps model X to paper X with NO sign
|
|
108
|
+
* flip (`adjustedX = x`) — unlike `translateY`, which bakes in a flip. So
|
|
109
|
+
* `translateX` is only wrong when `flipX` is actually true (`adjustedX =
|
|
110
|
+
* -x`, used for 'side' sections to view from the conventional direction,
|
|
111
|
+
* see `Drawing2DCanvas.tsx` / `useDrawingExport.ts`'s `flipX = sectionAxis
|
|
112
|
+
* === 'side'`): flipping negates and reverses the drawing's X extent to
|
|
113
|
+
* `[-maxX, -minX]`, so the base translate (keyed off `-minX`) shifts the
|
|
114
|
+
* centered box by `(minX + maxX) * scaleFactor` — the drawing lands off
|
|
115
|
+
* the left/right edge of the sheet, worse the further the section's bounds
|
|
116
|
+
* sit from being symmetric about X=0. Confirmed unfixed prior to this
|
|
117
|
+
* change (issue #2940's X-axis half): a 'side' section with bounds minX=2,
|
|
118
|
+
* maxX=12 on a 190mm-wide viewport landed 140mm off-center.
|
|
119
|
+
*/
|
|
120
|
+
export function calculateDrawingTransformForAxis(drawingBounds, viewportBounds, scale, flipY, flipX = false) {
|
|
121
|
+
const base = calculateDrawingTransform(drawingBounds, viewportBounds, scale);
|
|
122
|
+
const translateY = flipY
|
|
123
|
+
? base.translateY
|
|
124
|
+
: base.translateY - (drawingBounds.maxY + drawingBounds.minY) * base.scaleFactor;
|
|
125
|
+
const translateX = flipX
|
|
126
|
+
? base.translateX + (drawingBounds.minX + drawingBounds.maxX) * base.scaleFactor
|
|
127
|
+
: base.translateX;
|
|
128
|
+
return { ...base, translateX, translateY };
|
|
129
|
+
}
|
|
77
130
|
//# sourceMappingURL=sheet-types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sheet-types.js","sourceRoot":"","sources":["../../src/sheet/sheet-types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sheet-types.js","sourceRoot":"","sources":["../../src/sheet/sheet-types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"title-block-renderer.d.ts","sourceRoot":"","sources":["../../src/sheet/title-block-renderer.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAsB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"title-block-renderer.d.ts","sourceRoot":"","sources":["../../src/sheet/title-block-renderer.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAsB,MAAM,wBAAwB,CAAC;AAElG,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,kEAAkE;AAClE,MAAM,WAAW,gBAAgB;IAC/B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qEAAqE;AACrE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,oGAAoG;IACpG,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,sCAAsC;AACtC,MAAM,WAAW,sBAAsB;IACrC,uCAAuC;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACjE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,gBAAgB,EACxB,gBAAgB,EAAE,gBAAgB,EAClC,SAAS,GAAE,aAAa,EAAO,EAC/B,MAAM,CAAC,EAAE,gBAAgB,GACxB,sBAAsB,CAgFxB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
import { NICE_BAR_LENGTHS_M, barLabelIsExact, formatBarLabel, MIN_BAR_SEGMENT_MM } from './scale-bar-types.js';
|
|
4
5
|
/**
|
|
5
6
|
* Render a title block to SVG
|
|
6
7
|
*/
|
|
@@ -257,18 +258,116 @@ function renderRevisionHistory(revisions, x, y, w, h, config) {
|
|
|
257
258
|
*/
|
|
258
259
|
function renderScaleBarInTitleBlock(scaleBar, scale, x, y, w, h, _hasLogo, effectiveScaleFactor) {
|
|
259
260
|
let svg = ' <g id="title-block-scale-bar">\n';
|
|
260
|
-
//
|
|
261
|
-
|
|
261
|
+
// These two arguments are in RECIPROCAL units, and this function used to
|
|
262
|
+
// substitute one for the other in a single formula:
|
|
263
|
+
//
|
|
264
|
+
// effectiveScaleFactor millimetres per metre (10 at 1:100)
|
|
265
|
+
// scale.factor the N of 1:N (100 at 1:100)
|
|
266
|
+
//
|
|
267
|
+
// `calculateDrawingTransform` returns the former — `sheet-types.ts` uses it
|
|
268
|
+
// as `drawingWidth * scaleFactor` to get millimetres — its own doc says "mm
|
|
269
|
+
// per meter", and `useDrawingExport` passes it on EVERY export. So the branch
|
|
270
|
+
// the exporter always takes was the wrong one, and every exported bar was
|
|
271
|
+
// labelled wrong at every scale: a 5 m bar read 0.5 m at 1:100, and 0.1 m at
|
|
272
|
+
// 1:500 where 25 m is right. Normalise to one unit so there is one formula.
|
|
273
|
+
const mmPerMetre = effectiveScaleFactor ?? 1000 / scale.factor;
|
|
262
274
|
// Position: bottom left with small margin
|
|
263
275
|
const barX = x + 3;
|
|
264
276
|
const barY = y + h - 8; // 8mm from bottom (leaves room for label)
|
|
265
277
|
const maxBarWidth = Math.min(w * 0.3, 50);
|
|
266
|
-
const barLengthMm =
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
//
|
|
271
|
-
|
|
278
|
+
const barLengthMm = scaleBar.totalLengthM * mmPerMetre;
|
|
279
|
+
// Checked BEFORE the clamp, and the ordering is load-bearing: testing the
|
|
280
|
+
// clamped result loses the infinite case, because `Math.min(Infinity, 50)`
|
|
281
|
+
// is a perfectly finite 50.
|
|
282
|
+
//
|
|
283
|
+
// `mmPerMetre > 0` is checked SEPARATELY and is not redundant with
|
|
284
|
+
// `barLengthMm > 0`. An earlier version claimed one check on the product
|
|
285
|
+
// covered "any combination" of the two operands. It does not: two negatives
|
|
286
|
+
// cancel, so `totalLengthM: -5` with `effectiveScaleFactor: -10` gives
|
|
287
|
+
// `barLengthMm` 50 and passes every clause, after which the labels print
|
|
288
|
+
// NEGATIVE distances on a bar that draws normally. That is worse than the
|
|
289
|
+
// malformed rect this guard was written to stop, because nothing about the
|
|
290
|
+
// output looks wrong. Credit to CodeRabbit for the case and to
|
|
291
|
+
// @bimvoice-01 for relaying it.
|
|
292
|
+
//
|
|
293
|
+
// Deliberately not recording which of the sign checks in this file is
|
|
294
|
+
// load-bearing. Three versions of this paragraph did — "the snap masks
|
|
295
|
+
// this", then "this is load-bearing", then "three things reject it" — and
|
|
296
|
+
// each was a correct measurement made false by the next commit, twice by my
|
|
297
|
+
// own hand within one session and once by a revert. A mutation result is a
|
|
298
|
+
// fact about the whole program, so anything anywhere invalidates it, while
|
|
299
|
+
// it reads like a durable property. The commit message is where such a
|
|
300
|
+
// measurement belongs, pinned to a tree.
|
|
301
|
+
//
|
|
302
|
+
// Without this the bar emitted `<rect width="-10.00">`, invalid SVG dressed
|
|
303
|
+
// as a drawing. Drawing nothing is the honest degradation: a missing scale
|
|
304
|
+
// bar is visibly missing.
|
|
305
|
+
if (!(mmPerMetre > 0) ||
|
|
306
|
+
!(barLengthMm > 0) ||
|
|
307
|
+
!Number.isFinite(barLengthMm) ||
|
|
308
|
+
!(maxBarWidth > 0)) {
|
|
309
|
+
return '';
|
|
310
|
+
}
|
|
311
|
+
// Snap to a ROUND distance that fits, rather than clamping to the cell width
|
|
312
|
+
// and rounding the label afterwards. Folding the clamp into the label is not
|
|
313
|
+
// enough on its own: the label prints to 0 dp, so a 120 mm title block (a
|
|
314
|
+
// shipped preset) caps a 5 m bar at 36 mm = 3.6 m and prints "4m" — 11% off
|
|
315
|
+
// on a printed sheet, and up to ~33% for a bar landing near 1.5 m.
|
|
316
|
+
//
|
|
317
|
+
// Choosing the distance first makes the label exact by construction.
|
|
318
|
+
//
|
|
319
|
+
// It does NOT yet make the export agree with the on-screen preview, and an
|
|
320
|
+
// earlier version of this comment claimed it did. Measured over nine common
|
|
321
|
+
// (block width x scale) pairs with the default 5 m bar, six disagree:
|
|
322
|
+
// `Drawing2DCanvas` halves/doubles from the configured length, clamps to the
|
|
323
|
+
// cell, and rounds the label from the clamped value — the same clamp-then-
|
|
324
|
+
// round shape this function just stopped doing, so at a 120 mm block and
|
|
325
|
+
// 1:100 the preview still prints "3m" over a 2.5 m bar while the export
|
|
326
|
+
// prints an exact "2m". Unifying them means moving the preview onto
|
|
327
|
+
// `NICE_BAR_LENGTHS_M` too, which is a viewer change and not this diff.
|
|
328
|
+
// `divisions`, bounded at BOTH ends. The low end stops 0 dividing and 2.5 drawing three
|
|
329
|
+
// segments of width L/2.5 (the loop runs while `i < 2.5`), overflowing the
|
|
330
|
+
// bar by 20%. The high end matters too: 100000 divisions emits an 11.5 MB
|
|
331
|
+
// group of 0.0005 mm rects, and 1e7 is about a gigabyte — it hangs the export
|
|
332
|
+
// rather than drawing a bad bar. Nothing legible needs more than 20.
|
|
333
|
+
const divisions = Number.isFinite(scaleBar.primaryDivisions)
|
|
334
|
+
? Math.min(20, Math.max(1, Math.floor(scaleBar.primaryDivisions)))
|
|
335
|
+
: 1;
|
|
336
|
+
const requestedM = scaleBar.totalLengthM;
|
|
337
|
+
// Fits the cell AND draws segments that survive `toFixed(2)`. Without the
|
|
338
|
+
// lower bound a bar can be chosen whose segments all print `width="0.00"`
|
|
339
|
+
// under a confident label — a label with no bar, which is the dishonesty
|
|
340
|
+
// this function refuses everywhere else. Reachable at 1:1 on a large model,
|
|
341
|
+
// where the transform gives well under 1 mm per metre.
|
|
342
|
+
const fits = (m) => m * mmPerMetre <= maxBarWidth + 1e-9 &&
|
|
343
|
+
(m * mmPerMetre) / divisions >= MIN_BAR_SEGMENT_MM - 1e-12;
|
|
344
|
+
// Keep the requested length when it already fits AND already labels exactly.
|
|
345
|
+
// Snapping unconditionally shrank bars that were fine: a 4 m bar at 1:100 is
|
|
346
|
+
// 40 mm in a 50 mm cell and prints "4m" exactly, and snapping it to the
|
|
347
|
+
// nearest ladder value below halved it to 2 m for no reason.
|
|
348
|
+
let actualTotalLength = fits(requestedM) && barLabelIsExact(requestedM)
|
|
349
|
+
? requestedM
|
|
350
|
+
: undefined;
|
|
351
|
+
if (actualTotalLength === undefined) {
|
|
352
|
+
// Otherwise take the largest round distance that fits and does not claim
|
|
353
|
+
// more than was asked for.
|
|
354
|
+
const candidates = NICE_BAR_LENGTHS_M.filter((m) => m <= requestedM + 1e-9 && fits(m));
|
|
355
|
+
actualTotalLength = candidates[candidates.length - 1];
|
|
356
|
+
}
|
|
357
|
+
if (actualTotalLength === undefined) {
|
|
358
|
+
// Not even the smallest round distance fits the cell. A missing bar is
|
|
359
|
+
// visibly missing; a bar labelled with a distance it does not span is not.
|
|
360
|
+
return '';
|
|
361
|
+
}
|
|
362
|
+
const actualBarLength = actualTotalLength * mmPerMetre;
|
|
363
|
+
// `heightMm` reaches the emitted `height=` attribute, so it needs the same
|
|
364
|
+
// treatment as the length: NaN printed `height="NaN"` on every rect and
|
|
365
|
+
// `y="NaN"` on both labels, and a negative printed `height="-5.00"`. Guarding
|
|
366
|
+
// one of two attributes that reach the SVG is not guarding.
|
|
367
|
+
const barHeight = Number.isFinite(scaleBar.heightMm)
|
|
368
|
+
? Math.min(Math.max(scaleBar.heightMm, 0.1), 3)
|
|
369
|
+
: 3;
|
|
370
|
+
// Draw alternating bar segments; `divisions` was normalised above.
|
|
272
371
|
const divWidth = actualBarLength / divisions;
|
|
273
372
|
for (let i = 0; i < divisions; i++) {
|
|
274
373
|
const segX = barX + i * divWidth;
|
|
@@ -283,9 +382,10 @@ function renderScaleBarInTitleBlock(scaleBar, scale, x, y, w, h, _hasLogo, effec
|
|
|
283
382
|
svg += ` <text x="${barX.toFixed(2)}" y="${labelY.toFixed(2)}" `;
|
|
284
383
|
svg += `font-family="Arial, sans-serif" font-size="${fontSize}" `;
|
|
285
384
|
svg += `text-anchor="start" fill="#000000">0</text>\n`;
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
385
|
+
// Shared with `barLabelIsExact`, which decides whether a length CAN be
|
|
386
|
+
// printed without lying. Those two have to agree, and an inline copy here is
|
|
387
|
+
// exactly how they would stop agreeing.
|
|
388
|
+
const endLabel = formatBarLabel(actualTotalLength);
|
|
289
389
|
svg += ` <text x="${(barX + actualBarLength).toFixed(2)}" y="${labelY.toFixed(2)}" `;
|
|
290
390
|
svg += `font-family="Arial, sans-serif" font-size="${fontSize}" `;
|
|
291
391
|
svg += `text-anchor="end" fill="#000000">${endLabel}</text>\n`;
|