@design-core/core 0.0.6 → 50.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.
Files changed (173) hide show
  1. package/.eslintrc.json +4 -0
  2. package/.github/workflows/DesignCoreQA.yml +3 -0
  3. package/TODO.md +49 -6
  4. package/core/core/core.js +0 -3
  5. package/core/designCore.js +1 -1
  6. package/core/dimensions/alignedDimension.js +12 -11
  7. package/core/dimensions/angularDimension.js +38 -37
  8. package/core/dimensions/{baseCircularDimension.js → circularDimensionBase.js} +22 -21
  9. package/core/dimensions/diametricDimension.js +14 -13
  10. package/core/dimensions/dimension.js +62 -55
  11. package/core/dimensions/{baseDimension.js → dimensionBase.js} +73 -107
  12. package/core/dimensions/{baseLinearDimension.js → linearDimensionBase.js} +15 -14
  13. package/core/dimensions/radialDimension.js +12 -11
  14. package/core/dimensions/rotatedDimension.js +20 -19
  15. package/core/entities/arc.js +123 -156
  16. package/core/entities/arctext.js +222 -184
  17. package/core/entities/circle.js +77 -99
  18. package/core/entities/entity.js +114 -93
  19. package/core/entities/hatch.js +346 -273
  20. package/core/entities/insert.js +68 -83
  21. package/core/entities/line.js +21 -142
  22. package/core/entities/lwpolyline.js +16 -11
  23. package/core/entities/point.js +19 -19
  24. package/core/entities/polyline.js +7 -7
  25. package/core/entities/{basePolyline.js → polylineBase.js} +47 -299
  26. package/core/entities/rectangle.js +3 -1
  27. package/core/entities/solid.js +6 -15
  28. package/core/entities/text.js +190 -201
  29. package/core/lib/auxiliary/rubberBand.js +32 -0
  30. package/core/lib/auxiliary/selectionWindow.js +63 -0
  31. package/core/lib/auxiliary/snapPoint.js +113 -0
  32. package/core/lib/auxiliary/trackingLine.js +76 -0
  33. package/core/lib/boundingBox.js +11 -11
  34. package/core/lib/canvas.js +232 -168
  35. package/core/lib/clipboard.js +9 -3
  36. package/core/lib/colour.js +16 -0
  37. package/core/lib/commandLine.js +1 -6
  38. package/core/lib/commandManager.js +18 -8
  39. package/core/lib/constants.js +28 -0
  40. package/core/lib/dxf/dxf.js +15 -14
  41. package/core/lib/dxf/dxfIterator.js +2 -2
  42. package/core/lib/entityManager.js +33 -26
  43. package/core/lib/inputManager.js +36 -12
  44. package/core/lib/intersect.js +25 -0
  45. package/core/lib/matrix.js +1 -1
  46. package/core/lib/patterns.js +9 -2
  47. package/core/lib/plotOptions.js +67 -0
  48. package/core/lib/polylineUtils.js +75 -0
  49. package/core/lib/renderers/cairoRenderer.js +371 -0
  50. package/core/lib/renderers/canvasRenderer.js +315 -0
  51. package/core/lib/renderers/pdfRenderer.js +366 -0
  52. package/core/lib/renderers/rendererBase.js +284 -0
  53. package/core/lib/renderers/svgRenderer.js +343 -0
  54. package/core/lib/scene.js +28 -6
  55. package/core/lib/selectionManager.js +20 -20
  56. package/core/lib/settings.js +4 -2
  57. package/core/lib/snapping.js +4 -191
  58. package/core/lib/stateManager.js +30 -1
  59. package/core/lib/utils.js +26 -7
  60. package/core/properties/entityProperties.js +104 -0
  61. package/core/properties/property.js +106 -2
  62. package/core/properties/propertyManager.js +60 -44
  63. package/core/tables/block.js +50 -81
  64. package/core/tables/dimStyle.js +5 -5
  65. package/core/tables/endblk.js +1 -1
  66. package/core/tables/layer.js +8 -0
  67. package/core/tables/ltypeManager.js +1 -1
  68. package/core/tables/styleManagerBase.js +3 -2
  69. package/core/tables/tableManagerBase.js +8 -3
  70. package/core/tables/vport.js +2 -2
  71. package/core/tools/chamfer.js +117 -143
  72. package/core/tools/chamferFilletBase.js +138 -37
  73. package/core/tools/copy.js +11 -11
  74. package/core/tools/copybase.js +3 -3
  75. package/core/tools/copyclip.js +3 -3
  76. package/core/tools/cornerEntity.js +2 -2
  77. package/core/tools/cutclip.js +3 -3
  78. package/core/tools/explode.js +7 -7
  79. package/core/tools/extend.js +138 -25
  80. package/core/tools/fillet.js +92 -189
  81. package/core/tools/matchProp.js +3 -12
  82. package/core/tools/mirror.js +15 -14
  83. package/core/tools/move.js +8 -8
  84. package/core/tools/offset.js +31 -33
  85. package/core/tools/pasteclip.js +1 -1
  86. package/core/tools/purge.js +10 -10
  87. package/core/tools/rotate.js +19 -16
  88. package/core/tools/scale.js +7 -7
  89. package/core/tools/trim.js +271 -29
  90. package/core/tools/zoom.js +1 -1
  91. package/cspell.json +321 -0
  92. package/jest.config.js +6 -0
  93. package/package.json +5 -3
  94. package/test/dimensions/alignedDimension.test.js +17 -10
  95. package/test/dimensions/angularDimension.test.js +13 -13
  96. package/test/dimensions/diametricDimension.test.js +15 -15
  97. package/test/dimensions/dimension.test.js +12 -12
  98. package/test/dimensions/{baseDimension.test.js → dimensionBase.test.js} +73 -12
  99. package/test/dimensions/linearDimensionBase.test.js +62 -0
  100. package/test/dimensions/radialDimension.test.js +15 -15
  101. package/test/dimensions/rotatedDimension.test.js +18 -11
  102. package/test/entities/arc.test.js +83 -86
  103. package/test/entities/arctext.test.js +184 -57
  104. package/test/entities/circle.test.js +68 -96
  105. package/test/entities/entity.test.js +42 -8
  106. package/test/entities/hatch.test.js +569 -96
  107. package/test/entities/insert.test.js +99 -4
  108. package/test/entities/line.test.js +15 -41
  109. package/test/entities/lwpolyline.test.js +104 -0
  110. package/test/entities/point.test.js +3 -3
  111. package/test/entities/polyline.test.js +11 -0
  112. package/test/entities/{basePolyline.test.js → polylineBase.test.js} +192 -57
  113. package/test/entities/rectangle.test.js +11 -0
  114. package/test/entities/solid.test.js +1 -1
  115. package/test/entities/text.test.js +302 -77
  116. package/test/integration/exportIntegration.reference.dxf +9 -9
  117. package/test/integration/exportIntegration.test.js +5 -5
  118. package/test/integration/intersectionIntegration.test.js +4 -5
  119. package/test/integration/pdfRendererIntegration.reference.pdf +17709 -0
  120. package/test/integration/pdfRendererIntegration.test.js +61 -0
  121. package/test/integration/svgRendererIntegration.reference.svg +3343 -0
  122. package/test/integration/svgRendererIntegration.test.js +61 -0
  123. package/test/integration/userInputIntegration.reference.dxf +1165 -105
  124. package/test/integration/userInputIntegration.test.js +239 -132
  125. package/test/lib/boundingBox.test.js +3 -3
  126. package/test/lib/canvas.test.js +744 -125
  127. package/test/lib/constants.test.js +27 -0
  128. package/test/lib/entityManager.test.js +4 -4
  129. package/test/lib/inputManager.test.js +81 -20
  130. package/test/lib/intersect.test.js +83 -0
  131. package/test/lib/plotOptions.test.js +179 -0
  132. package/test/lib/rubberBand.test.js +84 -0
  133. package/test/lib/scene.test.js +9 -9
  134. package/test/lib/selectionManager.test.js +3 -3
  135. package/test/lib/settings.test.js +5 -6
  136. package/test/lib/snapping.test.js +72 -130
  137. package/test/lib/stateManager.test.js +7 -7
  138. package/test/lib/utils.test.js +36 -2
  139. package/test/properties/entityProperties.test.js +132 -0
  140. package/test/properties/property.test.js +53 -0
  141. package/test/properties/propertyManager.test.js +25 -25
  142. package/test/renderers/canvasRenderer.test.js +317 -0
  143. package/test/renderers/pdfRenderer.test.js +271 -0
  144. package/test/renderers/rendererBase.test.js +213 -0
  145. package/test/renderers/svgRenderer.test.js +326 -0
  146. package/test/tables/block.test.js +92 -13
  147. package/test/tables/endblk.test.js +2 -2
  148. package/test/tables/layer.test.js +30 -0
  149. package/test/tables/layerManager.test.js +2 -2
  150. package/test/test-helpers/hatchGridRenderer.js +180 -0
  151. package/test/test-helpers/test-helpers.js +158 -6
  152. package/test/tools/chamfer.test.js +273 -51
  153. package/test/tools/chamferFilletBase.test.js +125 -36
  154. package/test/tools/copy.test.js +8 -8
  155. package/test/tools/copybase.test.js +2 -2
  156. package/test/tools/copyclip.test.js +2 -2
  157. package/test/tools/cornerEntity.test.js +8 -8
  158. package/test/tools/cutclip.test.js +2 -2
  159. package/test/tools/erase.test.js +6 -6
  160. package/test/tools/explode.test.js +5 -5
  161. package/test/tools/extend.test.js +373 -42
  162. package/test/tools/fillet.test.js +230 -49
  163. package/test/tools/matchProp.test.js +11 -11
  164. package/test/tools/mirror.test.js +39 -39
  165. package/test/tools/move.test.js +7 -7
  166. package/test/tools/offset.test.js +222 -55
  167. package/test/tools/pasteclip.test.js +5 -5
  168. package/test/tools/purge.test.js +4 -4
  169. package/test/tools/rotate.test.js +39 -9
  170. package/test/tools/scale.test.js +33 -33
  171. package/test/tools/trim.test.js +579 -53
  172. package/test/tools/zoom.test.js +12 -12
  173. package/core/lib/selectionWindow.js +0 -83
@@ -3,6 +3,10 @@ import { Strings } from '../lib/strings.js';
3
3
  import { Tool } from './tool.js';
4
4
  import { Input, PromptOptions } from '../lib/inputManager.js';
5
5
  import { Logging } from '../lib/logging.js';
6
+ import { AddState, RemoveState } from '../lib/stateManager.js';
7
+ import { Utils } from '../lib/utils.js';
8
+ import { Colour } from '../lib/colour.js';
9
+ import { PolylineUtils } from '../lib/polylineUtils.js';
6
10
 
7
11
  import { DesignCore } from '../designCore.js';
8
12
 
@@ -16,7 +20,7 @@ export class Trim extends Tool {
16
20
  /** Create a Trim command */
17
21
  constructor() {
18
22
  super();
19
- this.selectedItem = null;
23
+ this.selectedEntity = null;
20
24
  this.selectedBoundaryItems = [];
21
25
  }
22
26
 
@@ -38,24 +42,24 @@ export class Trim extends Tool {
38
42
  */
39
43
  async execute() {
40
44
  try {
41
- const op = new PromptOptions(Strings.Input.BOUNDARY, [Input.Type.SELECTIONSET]);
45
+ const boundaryOp = new PromptOptions(Strings.Input.BOUNDARY, [Input.Type.SELECTIONSET]);
42
46
 
43
47
  if (!DesignCore.Scene.selectionManager.selectionSet.selectionSet.length) {
44
- const boundary = await DesignCore.Scene.inputManager.requestInput(op);
48
+ const boundary = await DesignCore.Scene.inputManager.requestInput(boundaryOp);
45
49
  if (boundary === undefined) return;
46
50
  }
47
51
 
48
- // add all selected items to boundary items
52
+ // add all selected entities to boundary items
49
53
  for (let i = 0; i < DesignCore.Scene.selectionManager.selectionSet.selectionSet.length; i++) {
50
54
  const boundaryItem = DesignCore.Scene.entities.get(DesignCore.Scene.selectionManager.selectionSet.selectionSet[i]);
51
55
  this.selectedBoundaryItems.push(boundaryItem);
52
56
  }
53
57
 
54
- const op2 = new PromptOptions(Strings.Input.SELECT, [Input.Type.SINGLESELECTION]);
58
+ const selectOp = new PromptOptions(Strings.Input.SELECT, [Input.Type.SINGLESELECTION]);
55
59
  while (true) {
56
- const selection = await DesignCore.Scene.inputManager.requestInput(op2);
60
+ const selection = await DesignCore.Scene.inputManager.requestInput(selectOp);
57
61
  if (selection === undefined) break;
58
- this.selectedItem = DesignCore.Scene.entities.get(selection.selectedItemIndex);
62
+ this.selectedEntity = DesignCore.Scene.entities.get(selection.selectedEntityIndex);
59
63
  DesignCore.Scene.selectionManager.removeLastSelection();
60
64
  DesignCore.Scene.inputManager.actionCommand();
61
65
  }
@@ -70,44 +74,282 @@ export class Trim extends Tool {
70
74
  * Preview the command during execution
71
75
  */
72
76
  preview() {
73
- // No Preview
77
+ if (!this.selectedBoundaryItems.length) return;
78
+
79
+ const index = DesignCore.Scene.selectionManager.findClosestItem(DesignCore.Mouse.pointOnScene());
80
+ if (index === undefined) return;
81
+
82
+ const entity = DesignCore.Scene.entities.get(index);
83
+ if (typeof entity.toPolylinePoints !== 'function' || typeof entity.fromPolylinePoints !== 'function') return;
84
+ const intersectPoints = this.#collectIntersectPoints(entity);
85
+ if (!intersectPoints.length) return;
86
+
87
+ const stateChanges = this.#trimEntity(entity, intersectPoints);
88
+ if (!stateChanges?.length) return;
89
+
90
+ // Draw the full entity in a dulled colour, then draw survivors on top.
91
+ const dulledEntity = Utils.cloneObject(entity);
92
+ dulledEntity.setProperty('colour', Colour.blend(entity.getDrawColour(), DesignCore.Settings.canvasbackgroundcolour, DesignCore.Settings.previewBlendFactor));
93
+ DesignCore.Scene.previewEntities.add(dulledEntity);
94
+
95
+ for (const change of stateChanges) {
96
+ if (change instanceof AddState) {
97
+ DesignCore.Scene.previewEntities.add(change.entity);
98
+ }
99
+ }
74
100
  }
75
101
 
76
102
  /**
77
103
  * Perform the command
78
104
  */
79
105
  action() {
80
- if (this.selectedItem && this.selectedBoundaryItems.length) {
81
- const intersectPoints = [];
82
-
83
- for (const boundaryItem of this.selectedBoundaryItems) {
84
- if (boundaryItem !== this.selectedItem) {
85
- try {
86
- const intersect = Intersection.intersectPolylinePolyline(boundaryItem.toPolylinePoints(), this.selectedItem.toPolylinePoints());
87
- if (intersect.points.length) {
88
- for (let point = 0; point < intersect.points.length; point++) {
89
- intersectPoints.push(intersect.points[point]);
90
- }
91
- }
92
- } catch {
93
- Logging.instance.warn(`${this.constructor.name}: Error intersecting between ${boundaryItem.type} and ${this.selectedItem.type}`);
94
- continue;
95
- }
96
- }
106
+ if (this.selectedEntity && this.selectedBoundaryItems.length) {
107
+ if (typeof this.selectedEntity.fromPolylinePoints !== 'function') {
108
+ DesignCore.Core.notify(`${this.type} - ${this.selectedEntity.type} ${Strings.Message.NOTRIM}`);
109
+ this.selectedEntity = null;
110
+ return;
97
111
  }
98
112
 
113
+ const intersectPoints = this.#collectIntersectPoints(this.selectedEntity);
114
+
99
115
  if (intersectPoints.length) {
100
- const stateChanges = this.selectedItem.trim(intersectPoints);
116
+ const stateChanges = this.#trimEntity(this.selectedEntity, intersectPoints);
101
117
  if (stateChanges?.length) {
102
118
  DesignCore.Scene.commit(stateChanges);
103
119
  }
104
120
  } else {
105
- DesignCore.Core.notify(`${this.type} - ${this.selectedItem.type} ${Strings.Message.NOTRIM}`);
121
+ DesignCore.Core.notify(`${this.type} - ${this.selectedEntity.type} ${Strings.Message.NOTRIM}`);
122
+ }
123
+ }
124
+
125
+ this.selectedEntity = null;
126
+ }
127
+
128
+ /**
129
+ * Collect all intersection points between the boundary items and the given entity.
130
+ * Unsupported entity combinations are silently skipped.
131
+ * @param {Object} entity
132
+ * @return {Array}
133
+ */
134
+ #collectIntersectPoints(entity) {
135
+ const intersectPoints = [];
136
+ for (const boundaryItem of this.selectedBoundaryItems) {
137
+ if (boundaryItem === entity) continue;
138
+ try {
139
+ const intersect = Intersection.intersectPolylinePolyline(boundaryItem.toPolylinePoints(), entity.toPolylinePoints());
140
+ intersectPoints.push(...intersect.points);
141
+ } catch {
142
+ // skip unsupported entity combinations
143
+ }
144
+ }
145
+ return intersectPoints;
146
+ }
147
+
148
+ /**
149
+ * Compute state changes to trim entity at the pair of intersection points
150
+ * surrounding the mouse position. Operates entirely in polyline-point space
151
+ * via entity.toPolylinePoints() / entity.fromPolylinePoints(), so it works
152
+ * for any entity that implements that protocol (Line, Arc, Circle,
153
+ * PolylineBase, and future entities such as Spline or Ellipse).
154
+ * @param {Object} entity
155
+ * @param {Array} intersectPoints
156
+ * @return {Array}
157
+ */
158
+ #trimEntity(entity, intersectPoints) {
159
+ const polyPoints = entity.toPolylinePoints();
160
+ const mousePosition = DesignCore.Mouse.pointOnScene();
161
+ const isClosed = polyPoints[0].isSame(polyPoints.at(-1));
162
+
163
+ // Drop any intersection point that coincides with an existing vertex
164
+ const filtered = intersectPoints.filter((p) => !polyPoints.some((v) => v.isSame(p)));
165
+ if (!filtered.length) return [];
166
+
167
+ // Locate each intersection point within the polyline segments
168
+ const locatedIntersections = [];
169
+ for (const point of filtered) {
170
+ for (let i = 1; i < polyPoints.length; i++) {
171
+ if (PolylineUtils.isPointOnSegment(point, polyPoints[i - 1], polyPoints[i])) {
172
+ locatedIntersections.push({
173
+ segmentIndex: i,
174
+ point,
175
+ positionAlongSegment: PolylineUtils.positionOnSegment(polyPoints, point, i),
176
+ });
177
+ break;
178
+ }
179
+ }
180
+ }
181
+
182
+ if (!locatedIntersections.length) return [];
183
+
184
+ locatedIntersections.sort((a, b) => {
185
+ if (a.segmentIndex !== b.segmentIndex) return a.segmentIndex - b.segmentIndex;
186
+ return a.positionAlongSegment - b.positionAlongSegment;
187
+ });
188
+
189
+ // Find which segment the mouse is closest to
190
+ let mouseSegmentIndex = 0;
191
+ let mouseClosest = null;
192
+ let minDistance = Infinity;
193
+ for (let i = 1; i < polyPoints.length; i++) {
194
+ const closest = PolylineUtils.closestPointOnSegment(polyPoints, mousePosition, i);
195
+ if (closest) {
196
+ const dist = mousePosition.distance(closest);
197
+ if (dist < minDistance) {
198
+ minDistance = dist;
199
+ mouseSegmentIndex = i;
200
+ mouseClosest = closest;
201
+ }
202
+ }
203
+ }
204
+
205
+ const mousePos = mouseClosest ?
206
+ PolylineUtils.positionOnSegment(polyPoints, mouseClosest, mouseSegmentIndex) :
207
+ 0;
208
+
209
+ // Find the nearest intersections before and after the mouse position
210
+ let trimBefore = null;
211
+ let trimAfter = null;
212
+ for (const loc of locatedIntersections) {
213
+ if (loc.segmentIndex < mouseSegmentIndex) {
214
+ trimBefore = loc;
215
+ } else if (loc.segmentIndex === mouseSegmentIndex) {
216
+ if (loc.positionAlongSegment <= mousePos) {
217
+ trimBefore = loc;
218
+ } else if (!trimAfter) {
219
+ trimAfter = loc;
220
+ }
221
+ } else if (!trimAfter) {
222
+ trimAfter = loc;
106
223
  }
107
224
  }
108
225
 
109
- // reset selected item
110
- this.selectedItem = null;
226
+ const stateChanges = [];
227
+
228
+ if (isClosed) {
229
+ // Wrap cyclically: if trimBefore is null use the last intersection,
230
+ // if trimAfter is null use the first.
231
+ if (!trimBefore) trimBefore = locatedIntersections.at(-1);
232
+ if (!trimAfter) trimAfter = locatedIntersections[0];
233
+
234
+ // A single intersection point cannot divide a closed shape.
235
+ if (trimBefore === trimAfter) return [];
236
+
237
+ // Build the single kept open portion from trimAfter to trimBefore,
238
+ // going away from the mouse (i.e. not through the mouse segment).
239
+ let outputPoints;
240
+ if (trimAfter.segmentIndex < trimBefore.segmentIndex ||
241
+ (trimAfter.segmentIndex === trimBefore.segmentIndex &&
242
+ trimAfter.positionAlongSegment < trimBefore.positionAlongSegment)) {
243
+ // Forward walk: trimAfter → trimBefore without crossing the seam
244
+ outputPoints = this.#buildPortion(polyPoints, trimAfter, trimBefore);
245
+ } else {
246
+ // Wrapping walk: trimAfter → end → seam → start → trimBefore
247
+ outputPoints = this.#buildWrappingPortion(polyPoints, trimAfter, trimBefore);
248
+ }
249
+
250
+ if (outputPoints && outputPoints.length >= 2) {
251
+ const output = Utils.cloneObject(entity).fromPolylinePoints(outputPoints);
252
+ output.flags?.removeValue(1);
253
+ stateChanges.push(new AddState(output));
254
+ stateChanges.push(new RemoveState(entity));
255
+ }
256
+ return stateChanges;
257
+ }
258
+
259
+ // Open entity: build portionOne (start → trimBefore) and portionTwo (trimAfter → end)
260
+ const startLoc = { segmentIndex: 1, point: polyPoints[0] };
261
+ const endLoc = { segmentIndex: polyPoints.length - 1, point: polyPoints.at(-1) };
262
+ const portionOnePoints = trimBefore ? this.#buildPortion(polyPoints, startLoc, trimBefore) : null;
263
+ const portionTwoPoints = trimAfter ? this.#buildPortion(polyPoints, trimAfter, endLoc) : null;
264
+
265
+ if (portionOnePoints) {
266
+ const output = Utils.cloneObject(entity).fromPolylinePoints(portionOnePoints);
267
+ output.flags?.removeValue(1);
268
+ stateChanges.push(new AddState(output));
269
+ }
270
+ if (portionTwoPoints) {
271
+ const output = Utils.cloneObject(entity).fromPolylinePoints(portionTwoPoints);
272
+ output.flags?.removeValue(1);
273
+ stateChanges.push(new AddState(output));
274
+ }
275
+
276
+ if (trimBefore || trimAfter) {
277
+ stateChanges.push(new RemoveState(entity));
278
+ }
279
+
280
+ return stateChanges;
281
+ }
282
+
283
+ /**
284
+ * Build a sub-polyline from fromLoc to toLoc going forward (no seam crossing).
285
+ * Both locations must appear in forward order along the polyline.
286
+ * @param {Array} polyPoints
287
+ * @param {Object} fromLoc - { segmentIndex, point, positionAlongSegment }
288
+ * @param {Object} toLoc - { segmentIndex, point, positionAlongSegment }
289
+ * @return {Array|null}
290
+ */
291
+ #buildPortion(polyPoints, fromLoc, toLoc) {
292
+ const fromSegStart = polyPoints[fromLoc.segmentIndex - 1];
293
+ const fromSegEnd = polyPoints[fromLoc.segmentIndex];
294
+
295
+ // Same-segment case: compute the sub-arc bulge directly from the arc geometry
296
+ if (fromLoc.segmentIndex === toLoc.segmentIndex) {
297
+ const startPoint = fromLoc.point.clone();
298
+ if (fromSegStart.bulge !== 0 && fromSegStart.bulge !== undefined) {
299
+ const centre = fromSegStart.bulgeCentrePoint(fromSegEnd);
300
+ const direction = fromSegStart.bulge > 0 ? 1 : -1;
301
+ const startAngle = centre.angle(fromLoc.point);
302
+ const endAngle = centre.angle(toLoc.point);
303
+ const includedAngle = ((endAngle - startAngle) * direction + 4 * Math.PI) % (2 * Math.PI);
304
+ startPoint.bulge = Math.tan(includedAngle / 4) * direction;
305
+ }
306
+ return [startPoint, toLoc.point.clone()];
307
+ }
308
+
309
+ const points = [];
310
+
311
+ // Start point: fromLoc.point with partial bulge covering the rest of its segment
312
+ const startPoint = fromLoc.point.clone();
313
+ if (fromSegStart.bulge !== 0 && fromSegStart.bulge !== undefined) {
314
+ startPoint.bulge = fromSegStart.partialBulge(fromSegEnd, fromLoc.point, true);
315
+ }
316
+ points.push(startPoint);
317
+
318
+ // Intermediate vertices between the two segments
319
+ for (let i = fromLoc.segmentIndex; i <= toLoc.segmentIndex - 2; i++) {
320
+ points.push(polyPoints[i].clone());
321
+ }
322
+
323
+ // Start of toLoc's segment with partial bulge trimmed to toLoc.point
324
+ const toSegStart = polyPoints[toLoc.segmentIndex - 1];
325
+ const toSegEnd = polyPoints[toLoc.segmentIndex];
326
+ const lastSegStart = toSegStart.clone();
327
+ if (toSegStart.bulge !== 0 && toSegStart.bulge !== undefined) {
328
+ lastSegStart.bulge = toSegStart.partialBulge(toSegEnd, toLoc.point);
329
+ }
330
+ if (!points.at(-1).isSame(lastSegStart)) points.push(lastSegStart);
331
+
332
+ const endPoint = toLoc.point.clone();
333
+ if (!points.at(-1).isSame(endPoint)) points.push(endPoint);
334
+
335
+ return points.length >= 2 ? points : null;
336
+ }
337
+
338
+ /**
339
+ * Build a sub-polyline from fromLoc to toLoc crossing the seam (wrapping).
340
+ * Used for closed entities when the kept portion wraps through the closure point.
341
+ * Implemented as two forward #buildPortion calls joined at the seam.
342
+ * @param {Array} polyPoints
343
+ * @param {Object} fromLoc - { segmentIndex, point, positionAlongSegment }
344
+ * @param {Object} toLoc - { segmentIndex, point, positionAlongSegment }
345
+ * @return {Array|null}
346
+ */
347
+ #buildWrappingPortion(polyPoints, fromLoc, toLoc) {
348
+ const firstHalf = this.#buildPortion(polyPoints, fromLoc, { segmentIndex: polyPoints.length - 1, point: polyPoints.at(-1) });
349
+ const secondHalf = this.#buildPortion(polyPoints, { segmentIndex: 1, point: polyPoints[0] }, toLoc);
350
+ if (!firstHalf || !secondHalf) return null;
351
+ firstHalf.pop(); // discard closure point before joining
352
+ return firstHalf.concat(secondHalf);
111
353
  }
112
354
  }
113
355
 
@@ -99,7 +99,7 @@ export class Zoom extends Tool {
99
99
  new Point(p1.x, p1.y),
100
100
  ];
101
101
 
102
- DesignCore.Scene.tempEntities.create('Polyline', { points: points });
102
+ DesignCore.Scene.previewEntities.create('Polyline', { points: points });
103
103
  }
104
104
  }
105
105
 
package/cspell.json ADDED
@@ -0,0 +1,321 @@
1
+ {
2
+ "version": "0.2",
3
+ "language": "en-GB",
4
+ "ignorePaths": [
5
+ "node_modules",
6
+ "_build",
7
+ "coverage",
8
+ "*.lock",
9
+ "package-lock.json",
10
+ "test/**"
11
+ ],
12
+ "ignoreRegExpList": [
13
+ "[0-9a-fA-F]{6,}"
14
+ ],
15
+ "words": [
16
+ "acad",
17
+ "ACADVER",
18
+ "autocad",
19
+ "ARCALIGNEDTEXT",
20
+ "ARCHTICK",
21
+ "arcpoints",
22
+ "ARCSIDE",
23
+ "arctext",
24
+ "ASME",
25
+ "AABB",
26
+ "basepoint",
27
+ "bbox",
28
+ "bboxes",
29
+ "bitmasked",
30
+ "byblock",
31
+ "bylayer",
32
+ "CANNOSCALE",
33
+ "CHARACTERSPACING",
34
+ "CHILDENTITIES",
35
+ "clayer",
36
+ "copybase",
37
+ "copyclip",
38
+ "cutclip",
39
+ "pasteclip",
40
+ "defpoints",
41
+ "dedup",
42
+ "DECIMALPLACES",
43
+ "DESTINATIONSET",
44
+ "dimstyle",
45
+ "dimstyles",
46
+ "dimline",
47
+ "DIMENSIONSTYLE",
48
+ "DIMADEC",
49
+ "DIMALI",
50
+ "DIMALT",
51
+ "DIMALTD",
52
+ "DIMALTF",
53
+ "DIMALTMZF",
54
+ "DIMALTMZS",
55
+ "DIMALTRND",
56
+ "DIMALTTD",
57
+ "DIMALTTZ",
58
+ "DIMALTU",
59
+ "DIMALTZ",
60
+ "DIMANG",
61
+ "DIMANNO",
62
+ "DIMAPOST",
63
+ "DIMARCSYM",
64
+ "DIMASO",
65
+ "DIMASSOC",
66
+ "DIMASZ",
67
+ "DIMATFIT",
68
+ "DIMAUNIT",
69
+ "DIMAZIN",
70
+ "DIMBLK",
71
+ "DIMCEN",
72
+ "DIMCENSTYL",
73
+ "DIMCENVALUE",
74
+ "DIMCLRD",
75
+ "DIMCLRE",
76
+ "DIMCLRT",
77
+ "DIMCONSTRAINTICON",
78
+ "DIMDEC",
79
+ "DIMDIA",
80
+ "DIMDLE",
81
+ "DIMDLI",
82
+ "DIMDSEP",
83
+ "DIMEXE",
84
+ "DIMEXO",
85
+ "DIMFIT",
86
+ "DIMFRAC",
87
+ "DIMFXL",
88
+ "DIMFXLON",
89
+ "DIMGAP",
90
+ "DIMJOGANG",
91
+ "DIMJUST",
92
+ "DIMLDRBLK",
93
+ "DIMLFAC",
94
+ "DIMLIM",
95
+ "DIMLIN",
96
+ "DIMLTEX",
97
+ "DIMLTYPE",
98
+ "DIMLUNIT",
99
+ "DIMLWD",
100
+ "DIMLWE",
101
+ "DIMMZF",
102
+ "DIMMZS",
103
+ "DIMPOST",
104
+ "DIMRAD",
105
+ "DIMRND",
106
+ "DIMROT",
107
+ "DIMSAH",
108
+ "DIMSCALE",
109
+ "DIMSD",
110
+ "DIMSE",
111
+ "DIMSHO",
112
+ "DIMSOXD",
113
+ "DIMTAD",
114
+ "DIMTDEC",
115
+ "DIMTFAC",
116
+ "DIMTFILL",
117
+ "DIMTFILLCLR",
118
+ "DIMTIH",
119
+ "DIMTIX",
120
+ "DIMTM",
121
+ "DIMTMOVE",
122
+ "DIMTOFL",
123
+ "DIMTOH",
124
+ "DIMTOL",
125
+ "DIMTOLJ",
126
+ "DIMTP",
127
+ "DIMTSZ",
128
+ "DIMTVP",
129
+ "DIMTXSTY",
130
+ "DIMTXT",
131
+ "DIMTXTDIRECTION",
132
+ "DIMTZIN",
133
+ "DIMUNIT",
134
+ "DIMUPT",
135
+ "DIMZIN",
136
+ "drawgrid",
137
+ "endblk",
138
+ "endsec",
139
+ "endsnap",
140
+ "EXTMAX",
141
+ "EXTMIN",
142
+ "FILLETRAD",
143
+ "FONTNAME",
144
+ "groupcode",
145
+ "groupcodes",
146
+ "groupvalue",
147
+ "handseed",
148
+ "HORIZONTALALIGNMENT",
149
+ "INSBASE",
150
+ "inputprompt",
151
+ "isopair",
152
+ "itemname",
153
+ "keypresses",
154
+ "lerp",
155
+ "LINEARDIMANGLE",
156
+ "linetype",
157
+ "linetypes",
158
+ "lineweight",
159
+ "ltscale",
160
+ "ltype",
161
+ "ltypes",
162
+ "lwpoly",
163
+ "lwpolyline",
164
+ "MLEADER",
165
+ "MLEADERSCALE",
166
+ "multileader",
167
+ "nearestsnap",
168
+ "midsnap",
169
+ "nodesnap",
170
+ "tangentsnap",
171
+ "quadrantsnap",
172
+ "perpendicularsnap",
173
+ "centresnap",
174
+ "NOCHAMFER",
175
+ "NOFILLET",
176
+ "NONCONSECUTIVESEGMENTS",
177
+ "NOREDO",
178
+ "NOTRIM",
179
+ "NOUNDO",
180
+ "OFFSETFROMARC",
181
+ "OFFSETFROMLEFT",
182
+ "OFFSETFROMRIGHT",
183
+ "Paperspace",
184
+ "PATTERNNAME",
185
+ "PLOTSTYLENAME",
186
+ "plottable",
187
+ "polarangle",
188
+ "polyface",
189
+ "postsuffix",
190
+ "preselection",
191
+ "RADIUSTOOLARGE",
192
+ "REFERENCELENGTH",
193
+ "RRGGBB",
194
+ "rubberbandcolour",
195
+ "SCALEFACTOR",
196
+ "selectedchild",
197
+ "SELECTIONSET",
198
+ "SEQEND",
199
+ "SINGLESELECTION",
200
+ "solidfill",
201
+ "standardflags",
202
+ "startxref",
203
+ "STYLENAME",
204
+ "TEXTALIGNMENT",
205
+ "TEXTORIENTATION",
206
+ "TEXTOVERRIDE",
207
+ "TEXTREVERSED",
208
+ "TEXTSIZE",
209
+ "TEXTSTYLE",
210
+ "THROUGHPOINT",
211
+ "truecolour",
212
+ "TRIMMODE",
213
+ "UCSICON",
214
+ "uncloseable",
215
+ "undrawn",
216
+ "UNKNOWNCOMMAND",
217
+ "unrecognised",
218
+ "VERTICALALIGNMENT",
219
+ "vport",
220
+ "WIDTHFACTOR",
221
+ "xgridmax",
222
+ "xgridmin",
223
+ "ygridmax",
224
+ "ygridmin",
225
+ "ZEROLENGTH",
226
+ "accentcolour",
227
+ "Alignedtext",
228
+ "BADINPUT",
229
+ "BLOCKNAME",
230
+ "boundingbox",
231
+ "BOXBLANK",
232
+ "BOXFILLED",
233
+ "BRELM",
234
+ "BRSTD",
235
+ "BRSTONE",
236
+ "canvasbackgroundcolour",
237
+ "CANNOTBEACTIONED",
238
+ "CANNOTBEDELETED",
239
+ "CANNOTBERENAMED",
240
+ "CHAMFERA",
241
+ "CHAMFERB",
242
+ "CHAMFERC",
243
+ "CHAMFERD",
244
+ "CHAMMODE",
245
+ "checkitems",
246
+ "CLOSEDBLANK",
247
+ "CONC",
248
+ "counterclockwise",
249
+ "cstyle",
250
+ "CSTYLEDELETE",
251
+ "currentstyle",
252
+ "DASHDOT",
253
+ "DATUMBLANK",
254
+ "DATUMFILLED",
255
+ "Degen",
256
+ "deleteblock",
257
+ "DOLMIT",
258
+ "DOTBLANK",
259
+ "DOTSMALL",
260
+ "Elec",
261
+ "ENDTAB",
262
+ "ERASESOURCE",
263
+ "Extline",
264
+ "FILEOPEN",
265
+ "fontbackwards",
266
+ "fontupsidedown",
267
+ "Gradians",
268
+ "gridcolour",
269
+ "gridlines",
270
+ "HBONE",
271
+ "INSUL",
272
+ "INVALIDBOUNDARY",
273
+ "INVALIDCLIPBOARD",
274
+ "INVALIDDXFFORMAT",
275
+ "INVALIDFILE",
276
+ "INVALIDGROUPCODE",
277
+ "INVALIDINTERSECTTYPE",
278
+ "INVALIDNUMBER",
279
+ "INVALIDPOINT",
280
+ "INVALIDTYPE",
281
+ "LAYERNAME",
282
+ "LEADERLENGTH",
283
+ "LWDEFAULT",
284
+ "MAXVALUE",
285
+ "midp",
286
+ "MINVALUE",
287
+ "MIRRORFIRST",
288
+ "MIRRORSECOND",
289
+ "MUDST",
290
+ "nendobj",
291
+ "NEWLENGTH",
292
+ "NEXTPOINT",
293
+ "NOEXTEND",
294
+ "nonexistent",
295
+ "NOTACOMMAND",
296
+ "NOTAPATTERN",
297
+ "NOTIMPLEMENTED",
298
+ "Offscreen",
299
+ "OFFSETDIST",
300
+ "PARALLELLINES",
301
+ "PARQ",
302
+ "PLAST",
303
+ "PLASTI",
304
+ "readblocks",
305
+ "renameitem",
306
+ "roundtrip",
307
+ "RROOF",
308
+ "RSHKE",
309
+ "SACNCR",
310
+ "Segs",
311
+ "TRIANG",
312
+ "underflowed",
313
+ "unminified",
314
+ "UNSUPPORTEDENTITIES",
315
+ "UPSIDEDOWN",
316
+ "Viewmode",
317
+ "xref",
318
+ "GOST",
319
+ "quot"
320
+ ]
321
+ }
package/jest.config.js ADDED
@@ -0,0 +1,6 @@
1
+ export default {
2
+ coveragePathIgnorePatterns: [
3
+ '/node_modules/',
4
+ '/test/',
5
+ ],
6
+ };