@design-core/core 0.0.6 → 0.0.7
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/.eslintrc.json +4 -0
- package/TODO.md +49 -6
- package/core/core/core.js +0 -3
- package/core/dimensions/alignedDimension.js +5 -4
- package/core/dimensions/angularDimension.js +6 -5
- package/core/dimensions/baseCircularDimension.js +3 -2
- package/core/dimensions/baseDimension.js +59 -93
- package/core/dimensions/baseLinearDimension.js +6 -5
- package/core/dimensions/diametricDimension.js +4 -3
- package/core/dimensions/dimension.js +21 -14
- package/core/dimensions/radialDimension.js +4 -3
- package/core/dimensions/rotatedDimension.js +12 -11
- package/core/entities/arc.js +112 -145
- package/core/entities/arctext.js +212 -174
- package/core/entities/basePolyline.js +30 -282
- package/core/entities/circle.js +77 -99
- package/core/entities/entity.js +114 -93
- package/core/entities/hatch.js +322 -249
- package/core/entities/insert.js +66 -81
- package/core/entities/line.js +21 -142
- package/core/entities/lwpolyline.js +16 -11
- package/core/entities/polyline.js +4 -4
- package/core/entities/rectangle.js +3 -1
- package/core/entities/solid.js +6 -15
- package/core/entities/text.js +188 -199
- package/core/lib/auxiliary/rubberBand.js +32 -0
- package/core/lib/auxiliary/selectionWindow.js +63 -0
- package/core/lib/auxiliary/snapPoint.js +113 -0
- package/core/lib/auxiliary/trackingLine.js +76 -0
- package/core/lib/canvas.js +228 -166
- package/core/lib/clipboard.js +7 -1
- package/core/lib/colour.js +16 -0
- package/core/lib/commandLine.js +1 -6
- package/core/lib/commandManager.js +18 -8
- package/core/lib/constants.js +28 -0
- package/core/lib/dxf/dxf.js +4 -3
- package/core/lib/entityManager.js +18 -11
- package/core/lib/inputManager.js +35 -11
- package/core/lib/intersect.js +25 -0
- package/core/lib/patterns.js +8 -1
- package/core/lib/plotOptions.js +67 -0
- package/core/lib/polylineUtils.js +75 -0
- package/core/lib/renderers/cairoRenderer.js +371 -0
- package/core/lib/renderers/canvasRenderer.js +315 -0
- package/core/lib/renderers/pdfRenderer.js +366 -0
- package/core/lib/renderers/rendererBase.js +284 -0
- package/core/lib/renderers/svgRenderer.js +343 -0
- package/core/lib/scene.js +27 -5
- package/core/lib/selectionManager.js +2 -2
- package/core/lib/settings.js +4 -2
- package/core/lib/snapping.js +4 -191
- package/core/lib/stateManager.js +30 -1
- package/core/lib/utils.js +21 -2
- package/core/properties/entityProperties.js +104 -0
- package/core/properties/property.js +106 -2
- package/core/properties/propertyManager.js +29 -13
- package/core/tables/block.js +4 -37
- package/core/tables/endblk.js +1 -1
- package/core/tables/layer.js +8 -0
- package/core/tables/styleManagerBase.js +3 -2
- package/core/tables/tableManagerBase.js +8 -3
- package/core/tools/chamfer.js +111 -137
- package/core/tools/chamferFilletBase.js +135 -34
- package/core/tools/copy.js +3 -3
- package/core/tools/explode.js +2 -2
- package/core/tools/extend.js +132 -19
- package/core/tools/fillet.js +86 -183
- package/core/tools/matchProp.js +2 -11
- package/core/tools/mirror.js +7 -6
- package/core/tools/move.js +3 -3
- package/core/tools/offset.js +3 -5
- package/core/tools/pasteclip.js +1 -1
- package/core/tools/rotate.js +6 -3
- package/core/tools/scale.js +1 -1
- package/core/tools/trim.js +265 -23
- package/core/tools/zoom.js +1 -1
- package/jest.config.js +6 -0
- package/package.json +5 -3
- package/test/dimensions/alignedDimension.test.js +14 -7
- package/test/dimensions/angularDimension.test.js +6 -6
- package/test/dimensions/baseDimension.test.js +69 -8
- package/test/dimensions/baseLinearDimension.test.js +62 -0
- package/test/dimensions/diametricDimension.test.js +6 -6
- package/test/dimensions/radialDimension.test.js +6 -6
- package/test/dimensions/rotatedDimension.test.js +14 -7
- package/test/entities/arc.test.js +80 -83
- package/test/entities/arctext.test.js +180 -53
- package/test/entities/basePolyline.test.js +68 -2
- package/test/entities/circle.test.js +57 -85
- package/test/entities/entity.test.js +42 -8
- package/test/entities/hatch.test.js +543 -70
- package/test/entities/insert.test.js +96 -1
- package/test/entities/line.test.js +15 -41
- package/test/entities/lwpolyline.test.js +104 -0
- package/test/entities/polyline.test.js +11 -0
- package/test/entities/rectangle.test.js +11 -0
- package/test/entities/solid.test.js +1 -1
- package/test/entities/text.test.js +294 -69
- package/test/integration/exportIntegration.reference.dxf +9 -9
- package/test/integration/exportIntegration.test.js +2 -2
- package/test/integration/intersectionIntegration.test.js +4 -5
- package/test/integration/pdfRendererIntegration.reference.pdf +17709 -0
- package/test/integration/pdfRendererIntegration.test.js +61 -0
- package/test/integration/svgRendererIntegration.reference.svg +3343 -0
- package/test/integration/svgRendererIntegration.test.js +61 -0
- package/test/integration/userInputIntegration.reference.dxf +1165 -105
- package/test/integration/userInputIntegration.test.js +239 -132
- package/test/lib/canvas.test.js +742 -123
- package/test/lib/constants.test.js +27 -0
- package/test/lib/entityManager.test.js +4 -4
- package/test/lib/inputManager.test.js +80 -19
- package/test/lib/intersect.test.js +83 -0
- package/test/lib/plotOptions.test.js +179 -0
- package/test/lib/rubberBand.test.js +84 -0
- package/test/lib/settings.test.js +5 -6
- package/test/lib/snapping.test.js +71 -129
- package/test/lib/stateManager.test.js +7 -7
- package/test/lib/utils.test.js +36 -2
- package/test/properties/entityProperties.test.js +132 -0
- package/test/properties/property.test.js +53 -0
- package/test/properties/propertyManager.test.js +3 -3
- package/test/renderers/canvasRenderer.test.js +317 -0
- package/test/renderers/pdfRenderer.test.js +271 -0
- package/test/renderers/rendererBase.test.js +213 -0
- package/test/renderers/svgRenderer.test.js +326 -0
- package/test/tables/block.test.js +31 -1
- package/test/tables/endblk.test.js +2 -2
- package/test/tables/layer.test.js +30 -0
- package/test/test-helpers/hatchGridRenderer.js +180 -0
- package/test/test-helpers/test-helpers.js +152 -0
- package/test/tools/chamfer.test.js +227 -5
- package/test/tools/chamferFilletBase.test.js +98 -9
- package/test/tools/copy.test.js +2 -2
- package/test/tools/explode.test.js +3 -3
- package/test/tools/extend.test.js +331 -0
- package/test/tools/fillet.test.js +181 -0
- package/test/tools/matchProp.test.js +5 -5
- package/test/tools/mirror.test.js +13 -13
- package/test/tools/offset.test.js +170 -3
- package/test/tools/pasteclip.test.js +5 -5
- package/test/tools/rotate.test.js +32 -2
- package/test/tools/scale.test.js +5 -5
- package/test/tools/trim.test.js +526 -0
- package/test/tools/zoom.test.js +7 -7
- package/core/lib/selectionWindow.js +0 -83
package/core/lib/constants.js
CHANGED
|
@@ -11,6 +11,10 @@ export class Constants {
|
|
|
11
11
|
static Tolerance = {
|
|
12
12
|
/** Near-zero threshold for floating-point geometric comparisons (1e-10). */
|
|
13
13
|
EPSILON: 1e-10,
|
|
14
|
+
/** Small relative offset for geometric offsets (1e-9).
|
|
15
|
+
* Suitable where a nudge must be negligible compared to the scale of
|
|
16
|
+
* the geometry but larger than floating-point noise. */
|
|
17
|
+
DELTA: 1e-9,
|
|
14
18
|
};
|
|
15
19
|
|
|
16
20
|
/**
|
|
@@ -20,4 +24,28 @@ export class Constants {
|
|
|
20
24
|
/** Number of decimal places used by Utils.round(). */
|
|
21
25
|
DECIMALPLACES: 5,
|
|
22
26
|
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Standard paper sizes in PDF points (portrait orientation).
|
|
30
|
+
* 1 PDF point = 1/72 inch = 25.4/72 mm.
|
|
31
|
+
* Swap width and height for landscape.
|
|
32
|
+
*
|
|
33
|
+
* ISO sizes are defined by ISO 216: A4 = 210×297mm, A3 = 297×420mm, etc.
|
|
34
|
+
* ANSI sizes follow ANSI/ASME Y14.1.
|
|
35
|
+
*/
|
|
36
|
+
static PageSizes = {
|
|
37
|
+
// ISO A-series (portrait)
|
|
38
|
+
'A4': { width: 595, height: 842 },
|
|
39
|
+
'A3': { width: 842, height: 1191 },
|
|
40
|
+
'A2': { width: 1191, height: 1684 },
|
|
41
|
+
'A1': { width: 1684, height: 2384 },
|
|
42
|
+
'A0': { width: 2384, height: 3370 },
|
|
43
|
+
// ANSI sizes (portrait)
|
|
44
|
+
'Letter': { width: 612, height: 792 },
|
|
45
|
+
'Legal': { width: 612, height: 1008 },
|
|
46
|
+
'Tabloid': { width: 792, height: 1224 },
|
|
47
|
+
'ANSI C': { width: 1224, height: 1584 },
|
|
48
|
+
'ANSI D': { width: 1584, height: 2448 },
|
|
49
|
+
'ANSI E': { width: 2448, height: 3168 },
|
|
50
|
+
};
|
|
23
51
|
}
|
package/core/lib/dxf/dxf.js
CHANGED
|
@@ -30,6 +30,7 @@ export class DXF {
|
|
|
30
30
|
this.reader.read(data);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
|
|
33
34
|
/**
|
|
34
35
|
* Write
|
|
35
36
|
* @param {string} version
|
|
@@ -302,12 +303,12 @@ export class DXF {
|
|
|
302
303
|
const command = child[0];
|
|
303
304
|
// check if the child is a valid entity
|
|
304
305
|
if (DesignCore.CommandManager.isCommand(command)) {
|
|
305
|
-
// create an instance of the child entity
|
|
306
306
|
const item = DesignCore.CommandManager.createNew(command, child);
|
|
307
307
|
|
|
308
308
|
// Register the child entity handle
|
|
309
|
-
|
|
310
|
-
|
|
309
|
+
const itemHandle = item?.getProperty?.('handle') ?? item?.handle;
|
|
310
|
+
if (itemHandle) {
|
|
311
|
+
DesignCore.HandleManager.checkHandle(itemHandle);
|
|
311
312
|
}
|
|
312
313
|
|
|
313
314
|
if (block.hasOwnProperty('items') === false) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { DesignCore } from '../designCore.js';
|
|
3
|
+
import { Property } from '../properties/property.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Entity Manager Class
|
|
@@ -36,10 +37,13 @@ export class EntityManager {
|
|
|
36
37
|
if (!entity) return;
|
|
37
38
|
|
|
38
39
|
if (this.#trackHandles) {
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
const existingHandle = entity.getProperty?.(Property.Names.HANDLE) ?? entity.handle;
|
|
41
|
+
if (existingHandle === undefined) {
|
|
42
|
+
if (entity.setProperty) {
|
|
43
|
+
entity.setProperty(Property.Names.HANDLE, DesignCore.HandleManager.next());
|
|
44
|
+
}
|
|
41
45
|
} else {
|
|
42
|
-
DesignCore.HandleManager.checkHandle(
|
|
46
|
+
DesignCore.HandleManager.checkHandle(existingHandle);
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
49
|
|
|
@@ -53,8 +57,9 @@ export class EntityManager {
|
|
|
53
57
|
remove(index) {
|
|
54
58
|
if (this.#trackHandles) {
|
|
55
59
|
const entity = this.#entities[index];
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
const handle = entity?.getProperty?.(Property.Names.HANDLE) ?? entity?.handle;
|
|
61
|
+
if (handle !== undefined) {
|
|
62
|
+
DesignCore.HandleManager.releaseHandle(handle);
|
|
58
63
|
}
|
|
59
64
|
}
|
|
60
65
|
this.#entities.splice(index, 1);
|
|
@@ -97,8 +102,12 @@ export class EntityManager {
|
|
|
97
102
|
const filteredItems = [];
|
|
98
103
|
|
|
99
104
|
this.#entities.forEach((item, index) => {
|
|
100
|
-
if (
|
|
101
|
-
|
|
105
|
+
if (type.toUpperCase() === 'ANY' || item.type.toUpperCase() === type.toUpperCase()) {
|
|
106
|
+
const inStore = item.properties?.has(prop);
|
|
107
|
+
const propValue = inStore ? item.getProperty(prop) : (Object.prototype.hasOwnProperty.call(item, prop) ? item[prop] : undefined);
|
|
108
|
+
if (propValue !== undefined && propValue === value) {
|
|
109
|
+
filteredItems.push(index);
|
|
110
|
+
}
|
|
102
111
|
}
|
|
103
112
|
});
|
|
104
113
|
|
|
@@ -125,7 +134,7 @@ export class EntityManager {
|
|
|
125
134
|
|
|
126
135
|
for (let i = 0; i < this.#entities.length; i++) {
|
|
127
136
|
// check the items layer is selectable - i.e. on, thawed, etc...
|
|
128
|
-
const layer = DesignCore.LayerManager.getItemByName(this.#entities[i].
|
|
137
|
+
const layer = DesignCore.LayerManager.getItemByName(this.#entities[i].getProperty(Property.Names.LAYER));
|
|
129
138
|
|
|
130
139
|
if (!layer?.isSelectable) {
|
|
131
140
|
continue;
|
|
@@ -155,9 +164,7 @@ export class EntityManager {
|
|
|
155
164
|
}
|
|
156
165
|
|
|
157
166
|
for (const property of Object.keys(data)) {
|
|
158
|
-
|
|
159
|
-
item.setProperty(property, data[property]);
|
|
160
|
-
}
|
|
167
|
+
item.setProperty(property, data[property]);
|
|
161
168
|
}
|
|
162
169
|
}
|
|
163
170
|
|
package/core/lib/inputManager.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Tool } from '../tools/tool.js';
|
|
2
2
|
import { Snapping } from './snapping.js';
|
|
3
|
-
import { Utils } from './utils.js';
|
|
4
3
|
|
|
5
4
|
import { DesignCore } from '../designCore.js';
|
|
5
|
+
import { Property } from '../properties/property.js';
|
|
6
6
|
import { Point } from '../entities/point.js';
|
|
7
7
|
import { Input, MouseDown, MouseUp } from './input.js';
|
|
8
8
|
|
|
@@ -137,6 +137,14 @@ export class InputManager {
|
|
|
137
137
|
const resolvedCommand = DesignCore.CommandManager.getCommand(command);
|
|
138
138
|
DesignCore.CommandLine.addToCommandHistory(resolvedCommand);
|
|
139
139
|
this.activeCommand = DesignCore.CommandManager.createNew(resolvedCommand);
|
|
140
|
+
|
|
141
|
+
// Clear selection for Entity commands
|
|
142
|
+
// Tools (Erase, Offset, etc.) consume preselection in execute().
|
|
143
|
+
if (!(this.activeCommand instanceof Tool)) {
|
|
144
|
+
DesignCore.Scene.selectionManager.reset();
|
|
145
|
+
DesignCore.Canvas.requestPaint();
|
|
146
|
+
}
|
|
147
|
+
|
|
140
148
|
this.activeCommand.execute();
|
|
141
149
|
}
|
|
142
150
|
|
|
@@ -217,11 +225,24 @@ export class InputManager {
|
|
|
217
225
|
}
|
|
218
226
|
}
|
|
219
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Clears preview entities and re-runs the active command preview
|
|
230
|
+
*/
|
|
231
|
+
refreshPreview() {
|
|
232
|
+
if (this.activeCommand === undefined) return;
|
|
233
|
+
DesignCore.Scene.previewEntities.clear();
|
|
234
|
+
DesignCore.Scene.auxiliaryEntities.clear();
|
|
235
|
+
DesignCore.Scene.hoverEntities.clear();
|
|
236
|
+
this.activeCommand.preview();
|
|
237
|
+
DesignCore.Canvas.requestPaint();
|
|
238
|
+
}
|
|
239
|
+
|
|
220
240
|
/**
|
|
221
241
|
* Handle mouse position changes
|
|
222
242
|
*/
|
|
223
243
|
mouseMoved() {
|
|
224
|
-
DesignCore.Scene.
|
|
244
|
+
DesignCore.Scene.previewEntities.clear();
|
|
245
|
+
DesignCore.Scene.hoverEntities.clear();
|
|
225
246
|
DesignCore.Scene.auxiliaryEntities.clear();
|
|
226
247
|
|
|
227
248
|
if (DesignCore.Mouse.buttonOneDown) {
|
|
@@ -255,13 +276,16 @@ export class InputManager {
|
|
|
255
276
|
}
|
|
256
277
|
}
|
|
257
278
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
// preview active commands when items are not being selected
|
|
261
|
-
if (this.activeCommand !== undefined && !selecting) {
|
|
279
|
+
// preview active commands
|
|
280
|
+
if (this.activeCommand !== undefined) {
|
|
262
281
|
this.activeCommand.preview();
|
|
263
282
|
}
|
|
264
283
|
|
|
284
|
+
// Only show hover glow when no command preview is occupying previewEntities
|
|
285
|
+
if (!snapped && DesignCore.Scene.previewEntities.count() === 0) {
|
|
286
|
+
this.highlightEntityUnderMouse();
|
|
287
|
+
}
|
|
288
|
+
|
|
265
289
|
DesignCore.Canvas.requestPaint();
|
|
266
290
|
}
|
|
267
291
|
|
|
@@ -282,7 +306,7 @@ export class InputManager {
|
|
|
282
306
|
|
|
283
307
|
const index = DesignCore.Scene.selectionManager.findClosestItem(DesignCore.Mouse.pointOnScene());
|
|
284
308
|
if (index !== undefined) {
|
|
285
|
-
DesignCore.Scene.
|
|
309
|
+
DesignCore.Scene.hoverEntities.add(DesignCore.Scene.entities.get(index));
|
|
286
310
|
return true;
|
|
287
311
|
}
|
|
288
312
|
|
|
@@ -406,10 +430,10 @@ export class InputManager {
|
|
|
406
430
|
this.activeCommand.action();
|
|
407
431
|
} else {
|
|
408
432
|
if (item !== undefined) {
|
|
409
|
-
|
|
410
|
-
item.
|
|
411
|
-
//
|
|
412
|
-
return DesignCore.Scene.
|
|
433
|
+
// set the items layer to the current layer
|
|
434
|
+
item.setProperty(Property.Names.LAYER, DesignCore.LayerManager.getCstyle());
|
|
435
|
+
// commit the already-constructed entity directly, bypassing createNew
|
|
436
|
+
return DesignCore.Scene.commitEntity(item, index);
|
|
413
437
|
}
|
|
414
438
|
}
|
|
415
439
|
}
|
package/core/lib/intersect.js
CHANGED
|
@@ -448,5 +448,30 @@ export class Intersection {
|
|
|
448
448
|
|
|
449
449
|
return result;
|
|
450
450
|
};
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Even-odd point-in-boundary test using a horizontal ray to the right.
|
|
454
|
+
* A tiny y-offset prevents the ray from passing exactly through the
|
|
455
|
+
* top or bottom of a curved boundary (e.g. a circle), which would produce a
|
|
456
|
+
* wrong crossing count. The offset scales with the y-coordinate of the test
|
|
457
|
+
* point so it stays negligible regardless of drawing scale or position.
|
|
458
|
+
* @param {number} px - x coordinate of the test point
|
|
459
|
+
* @param {number} py - y coordinate of the test point
|
|
460
|
+
* @param {Array} boundaries - array of closed point arrays (each a polyline loop)
|
|
461
|
+
* @param {number} boundaryExtentX - x coordinate of the ray endpoint, must be rightward of all geometry
|
|
462
|
+
* @return {boolean} true if the point is inside the boundaries under the even-odd rule
|
|
463
|
+
*/
|
|
464
|
+
static isInsidePolyline(px, py, boundaries, boundaryExtentX) {
|
|
465
|
+
// Scale epsilon by the y-coordinate magnitude of the test point rather than
|
|
466
|
+
// boundaryExtentX: a y-offset should be negligible relative to the y-scale of
|
|
467
|
+
// the geometry, not the (unrelated) x-extent of the ray. The +1 handles py=0.
|
|
468
|
+
const rayEps = Constants.Tolerance.DELTA * (Math.abs(py) + 1);
|
|
469
|
+
const testLine = [new Point(px, py + rayEps), new Point(boundaryExtentX, py + rayEps)];
|
|
470
|
+
let count = 0;
|
|
471
|
+
for (const b of boundaries) {
|
|
472
|
+
count += Intersection.intersectPolylinePolyline(b, testLine).points.length;
|
|
473
|
+
}
|
|
474
|
+
return (count % 2) === 1;
|
|
475
|
+
}
|
|
451
476
|
}
|
|
452
477
|
|
package/core/lib/patterns.js
CHANGED
|
@@ -116,8 +116,15 @@ export class Patterns {
|
|
|
116
116
|
|
|
117
117
|
static hatch_patterns = {
|
|
118
118
|
'SOLID': {
|
|
119
|
+
// Commercial CAD defines SOLID as two dense cross-hatch line families
|
|
120
|
+
// (45° and -45°) with a near-zero spacing of 0.0001 inches. Design does
|
|
121
|
+
// not use this pattern for rendering — solid hatches are drawn with a
|
|
122
|
+
// direct fill() call for quality and performance reasons. The definition
|
|
123
|
+
// is retained here so that patternExists('SOLID') returns true and the
|
|
124
|
+
// name is available for pattern selection and DXF round-trip validation.
|
|
119
125
|
description: 'Solid Fill', pattern:
|
|
120
|
-
`45, 0,0, 0,
|
|
126
|
+
`45, 0,0, 0,0.00254
|
|
127
|
+
-45, 0,0, 0,0.00254`,
|
|
121
128
|
},
|
|
122
129
|
|
|
123
130
|
'ANGLE': {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { RendererBase } from './renderers/rendererBase.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PlotOptions Class
|
|
5
|
+
* Encapsulates all parameters for a plot/export operation.
|
|
6
|
+
*/
|
|
7
|
+
export class PlotOptions {
|
|
8
|
+
/** Valid plot area modes. */
|
|
9
|
+
static Area = {
|
|
10
|
+
EXTENTS: 'Extents',
|
|
11
|
+
DISPLAY: 'Display',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
static #fileTypes = ['pdf', 'svg'];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {number} pageWidth - required, positive number (PDF points)
|
|
18
|
+
* @param {number} pageHeight - required, positive number (PDF points)
|
|
19
|
+
*/
|
|
20
|
+
constructor(pageWidth, pageHeight) {
|
|
21
|
+
this.setOption('pageWidth', pageWidth);
|
|
22
|
+
this.setOption('pageHeight', pageHeight);
|
|
23
|
+
this.plotScale = null;
|
|
24
|
+
this.plotArea = PlotOptions.Area.EXTENTS;
|
|
25
|
+
this.style = RendererBase.Styles.NONE;
|
|
26
|
+
this.fileType = 'pdf';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validate and set a single option.
|
|
31
|
+
* @param {string} key
|
|
32
|
+
* @param {*} value
|
|
33
|
+
*/
|
|
34
|
+
setOption(key, value) {
|
|
35
|
+
switch (key) {
|
|
36
|
+
case 'pageWidth':
|
|
37
|
+
case 'pageHeight':
|
|
38
|
+
if (typeof value !== 'number' || value <= 0) {
|
|
39
|
+
throw new Error(`PlotOptions: ${key} must be a positive number`);
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
case 'plotScale':
|
|
43
|
+
if (value !== null && (typeof value !== 'number' || value <= 0)) {
|
|
44
|
+
throw new Error('PlotOptions: plotScale must be a positive number or null');
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
case 'plotArea':
|
|
48
|
+
if (!Object.values(PlotOptions.Area).includes(value)) {
|
|
49
|
+
throw new Error(`PlotOptions: plotArea must be one of: ${Object.values(PlotOptions.Area).join(', ')}`);
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
case 'style':
|
|
53
|
+
if (typeof value !== 'function') {
|
|
54
|
+
throw new Error('PlotOptions: style must be a function');
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
case 'fileType':
|
|
58
|
+
if (!PlotOptions.#fileTypes.includes(value)) {
|
|
59
|
+
throw new Error(`PlotOptions: fileType must be one of: ${PlotOptions.#fileTypes.join(', ')}`);
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
default:
|
|
63
|
+
throw new Error(`PlotOptions: unknown option '${key}'`);
|
|
64
|
+
}
|
|
65
|
+
this[key] = value;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Utils } from './utils.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared polyline-segment geometry helpers used by the Trim and Extend tools.
|
|
5
|
+
* All methods accept an explicit `points` array rather than operating on
|
|
6
|
+
* `this.points`, so they are entity-independent and can be reused by any
|
|
7
|
+
* tool that works in polyline-point space.
|
|
8
|
+
*/
|
|
9
|
+
export class PolylineUtils {
|
|
10
|
+
/**
|
|
11
|
+
* Test whether a point lies on the segment from A to B (line or arc).
|
|
12
|
+
* @param {Point} point
|
|
13
|
+
* @param {Point} A - segment start; carries bulge for arc segments
|
|
14
|
+
* @param {Point} B - segment end
|
|
15
|
+
* @return {boolean}
|
|
16
|
+
*/
|
|
17
|
+
static isPointOnSegment(point, A, B) {
|
|
18
|
+
if (A.bulge !== 0 && A.bulge !== undefined) {
|
|
19
|
+
const center = A.bulgeCentrePoint(B);
|
|
20
|
+
const radius = center.distance(A);
|
|
21
|
+
// Point must lie on the circle before checking the angular range.
|
|
22
|
+
if (Utils.round(center.distance(point)) !== Utils.round(radius)) return false;
|
|
23
|
+
const direction = A.bulge > 0 ? 1 : -1;
|
|
24
|
+
return point.isOnArc(A, B, center, direction);
|
|
25
|
+
}
|
|
26
|
+
return point.isOnLine(A, B);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get the closest point on a segment to a reference point.
|
|
31
|
+
* @param {Array} points - polyline points array
|
|
32
|
+
* @param {Point} point - reference point
|
|
33
|
+
* @param {number} segmentIndex - 1-based segment index
|
|
34
|
+
* @return {Point|null}
|
|
35
|
+
*/
|
|
36
|
+
static closestPointOnSegment(points, point, segmentIndex) {
|
|
37
|
+
const A = points[segmentIndex - 1];
|
|
38
|
+
const B = points[segmentIndex % points.length];
|
|
39
|
+
|
|
40
|
+
if (A.bulge !== 0 && A.bulge !== undefined) {
|
|
41
|
+
const center = A.bulgeCentrePoint(B);
|
|
42
|
+
const direction = A.bulge > 0 ? 1 : -1;
|
|
43
|
+
const closest = point.closestPointOnArc(A, B, center, direction);
|
|
44
|
+
if (closest) return closest;
|
|
45
|
+
// Point not on arc — return the nearer endpoint
|
|
46
|
+
return point.distance(A) < point.distance(B) ? A : B;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return point.closestPointOnLine(A, B);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get the normalised position of a point along a segment (used for ordering).
|
|
54
|
+
* For line segments returns chord distance from A; for arc segments returns
|
|
55
|
+
* the angular sweep from A, normalised to [0, 2π).
|
|
56
|
+
* @param {Array} points - polyline points array
|
|
57
|
+
* @param {Point} point - a point known to lie on the segment
|
|
58
|
+
* @param {number} segmentIndex - 1-based segment index
|
|
59
|
+
* @return {number}
|
|
60
|
+
*/
|
|
61
|
+
static positionOnSegment(points, point, segmentIndex) {
|
|
62
|
+
const A = points[segmentIndex - 1];
|
|
63
|
+
const B = points[segmentIndex % points.length];
|
|
64
|
+
|
|
65
|
+
if (A.bulge !== 0 && A.bulge !== undefined) {
|
|
66
|
+
const center = A.bulgeCentrePoint(B);
|
|
67
|
+
const direction = A.bulge > 0 ? 1 : -1;
|
|
68
|
+
const startAngle = center.angle(A);
|
|
69
|
+
const pointAngle = center.angle(point);
|
|
70
|
+
return ((pointAngle - startAngle) * direction + 4 * Math.PI) % (2 * Math.PI);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return A.distance(point);
|
|
74
|
+
}
|
|
75
|
+
}
|