@design-core/core 0.0.7 → 51.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 (117) hide show
  1. package/.github/workflows/DesignCoreQA.yml +3 -0
  2. package/core/designCore.js +1 -1
  3. package/core/dimensions/alignedDimension.js +7 -7
  4. package/core/dimensions/angularDimension.js +32 -32
  5. package/core/dimensions/{baseCircularDimension.js → circularDimensionBase.js} +19 -19
  6. package/core/dimensions/diametricDimension.js +10 -10
  7. package/core/dimensions/dimension.js +42 -42
  8. package/core/dimensions/{baseDimension.js → dimensionBase.js} +17 -17
  9. package/core/dimensions/{baseLinearDimension.js → linearDimensionBase.js} +9 -9
  10. package/core/dimensions/radialDimension.js +8 -8
  11. package/core/dimensions/rotatedDimension.js +8 -8
  12. package/core/entities/arc.js +22 -22
  13. package/core/entities/arctext.js +13 -13
  14. package/core/entities/circle.js +7 -7
  15. package/core/entities/hatch.js +210 -40
  16. package/core/entities/insert.js +11 -6
  17. package/core/entities/point.js +19 -19
  18. package/core/entities/polyline.js +3 -3
  19. package/core/entities/{basePolyline.js → polylineBase.js} +18 -18
  20. package/core/entities/text.js +4 -4
  21. package/core/lib/boundingBox.js +11 -11
  22. package/core/lib/canvas.js +13 -6
  23. package/core/lib/clipboard.js +2 -2
  24. package/core/lib/dxf/dxf.js +11 -11
  25. package/core/lib/dxf/dxfIterator.js +2 -2
  26. package/core/lib/entityManager.js +19 -19
  27. package/core/lib/inputManager.js +1 -1
  28. package/core/lib/matrix.js +1 -1
  29. package/core/lib/patterns.js +1 -1
  30. package/core/lib/polylineUtils.js +9 -9
  31. package/core/lib/renderers/cairoRenderer.js +5 -5
  32. package/core/lib/renderers/canvasRenderer.js +4 -4
  33. package/core/lib/renderers/pdfRenderer.js +7 -7
  34. package/core/lib/renderers/rendererBase.js +1 -1
  35. package/core/lib/scene.js +1 -1
  36. package/core/lib/selectionManager.js +18 -18
  37. package/core/lib/utils.js +5 -5
  38. package/core/properties/entityProperties.js +2 -1
  39. package/core/properties/flags.js +12 -0
  40. package/core/properties/property.js +8 -3
  41. package/core/properties/propertyManager.js +52 -37
  42. package/core/tables/block.js +50 -46
  43. package/core/tables/dimStyle.js +5 -5
  44. package/core/tables/ltypeManager.js +1 -1
  45. package/core/tables/vport.js +2 -2
  46. package/core/tools/chamfer.js +6 -6
  47. package/core/tools/chamferFilletBase.js +4 -4
  48. package/core/tools/copy.js +8 -8
  49. package/core/tools/copybase.js +3 -3
  50. package/core/tools/copyclip.js +3 -3
  51. package/core/tools/cornerEntity.js +2 -2
  52. package/core/tools/cutclip.js +3 -3
  53. package/core/tools/explode.js +6 -6
  54. package/core/tools/extend.js +13 -13
  55. package/core/tools/fillet.js +6 -6
  56. package/core/tools/matchProp.js +1 -1
  57. package/core/tools/mirror.js +11 -11
  58. package/core/tools/move.js +5 -5
  59. package/core/tools/offset.js +29 -29
  60. package/core/tools/purge.js +10 -10
  61. package/core/tools/rotate.js +16 -16
  62. package/core/tools/scale.js +6 -6
  63. package/core/tools/trim.js +15 -15
  64. package/cspell.json +323 -0
  65. package/package.json +1 -1
  66. package/test/dimensions/alignedDimension.test.js +3 -3
  67. package/test/dimensions/angularDimension.test.js +7 -7
  68. package/test/dimensions/diametricDimension.test.js +9 -9
  69. package/test/dimensions/dimension.test.js +12 -12
  70. package/test/dimensions/{baseDimension.test.js → dimensionBase.test.js} +12 -12
  71. package/test/dimensions/{baseLinearDimension.test.js → linearDimensionBase.test.js} +7 -7
  72. package/test/dimensions/radialDimension.test.js +9 -9
  73. package/test/dimensions/rotatedDimension.test.js +4 -4
  74. package/test/entities/arc.test.js +10 -10
  75. package/test/entities/arctext.test.js +6 -6
  76. package/test/entities/circle.test.js +18 -18
  77. package/test/entities/hatch.test.js +362 -39
  78. package/test/entities/insert.test.js +9 -9
  79. package/test/entities/point.test.js +3 -3
  80. package/test/entities/{basePolyline.test.js → polylineBase.test.js} +131 -62
  81. package/test/entities/text.test.js +19 -19
  82. package/test/integration/exportIntegration.test.js +3 -3
  83. package/test/integration/userInputIntegration.test.js +5 -5
  84. package/test/lib/boundingBox.test.js +3 -3
  85. package/test/lib/canvas.test.js +9 -9
  86. package/test/lib/clipboard.test.js +73 -0
  87. package/test/lib/inputManager.test.js +4 -4
  88. package/test/lib/scene.test.js +9 -9
  89. package/test/lib/selectionManager.test.js +3 -3
  90. package/test/lib/snapping.test.js +1 -1
  91. package/test/properties/flags.test.js +24 -0
  92. package/test/properties/propertyManager.test.js +93 -22
  93. package/test/renderers/pdfRenderer.test.js +2 -2
  94. package/test/tables/block.test.js +63 -14
  95. package/test/tables/layerManager.test.js +2 -2
  96. package/test/test-helpers/hatchGridRenderer.js +3 -3
  97. package/test/test-helpers/test-helpers.js +6 -6
  98. package/test/tools/chamfer.test.js +60 -60
  99. package/test/tools/chamferFilletBase.test.js +36 -36
  100. package/test/tools/copy.test.js +6 -6
  101. package/test/tools/copybase.test.js +2 -2
  102. package/test/tools/copyclip.test.js +2 -2
  103. package/test/tools/cornerEntity.test.js +8 -8
  104. package/test/tools/cutclip.test.js +2 -2
  105. package/test/tools/erase.test.js +6 -6
  106. package/test/tools/explode.test.js +2 -2
  107. package/test/tools/extend.test.js +84 -84
  108. package/test/tools/fillet.test.js +62 -62
  109. package/test/tools/matchProp.test.js +6 -6
  110. package/test/tools/mirror.test.js +26 -26
  111. package/test/tools/move.test.js +7 -7
  112. package/test/tools/offset.test.js +65 -65
  113. package/test/tools/purge.test.js +4 -4
  114. package/test/tools/rotate.test.js +10 -10
  115. package/test/tools/scale.test.js +28 -28
  116. package/test/tools/trim.test.js +109 -109
  117. package/test/tools/zoom.test.js +15 -17
@@ -83,7 +83,7 @@ export class Text extends Entity {
83
83
  type: Property.Type.LIST,
84
84
  value: hAlign > 2 ? 1 : hAlign,
85
85
  dxfCode: 72,
86
- options: () => [{ display: 'Left', value: 0 }, { display: 'Center', value: 1 }, { display: 'Right', value: 2 }],
86
+ options: () => [{ display: 'Left', value: 0 }, { display: 'Centre', value: 1 }, { display: 'Right', value: 2 }],
87
87
  });
88
88
  // DXF Groupcode 73 - Vertical Alignment (0=Baseline; 1=Bottom; 2=Middle; 3=Top)
89
89
  this.properties.add(Property.Names.VERTICALALIGNMENT, {
@@ -354,13 +354,13 @@ export class Text extends Entity {
354
354
  case 0:
355
355
  return 'left';
356
356
  case 1:
357
- return 'center';
357
+ return 'centre';
358
358
  case 2:
359
359
  return 'right';
360
360
  case 3:
361
361
  return (this.getProperty(Property.Names.VERTICALALIGNMENT) === 0 ? 'aligned' : 'left');
362
362
  case 4:
363
- return (this.getProperty(Property.Names.VERTICALALIGNMENT) === 0 ? 'center' : 'left');
363
+ return (this.getProperty(Property.Names.VERTICALALIGNMENT) === 0 ? 'centre' : 'left');
364
364
  case 5:
365
365
  return (this.getProperty(Property.Names.VERTICALALIGNMENT) === 0 ? 'fit' : 'left');
366
366
  default:
@@ -539,7 +539,7 @@ export class Text extends Entity {
539
539
  case 0: // left
540
540
  offsetX = 0;
541
541
  break;
542
- case 1: // center
542
+ case 1: // centre
543
543
  offsetX = -rect.width / 2;
544
544
  break;
545
545
  case 2: // right
@@ -55,9 +55,9 @@ export class BoundingBox {
55
55
  }
56
56
 
57
57
  /**
58
- * return bounding box center point
58
+ * return bounding box centre point
59
59
  */
60
- get centerPoint() {
60
+ get centrePoint() {
61
61
  return new Point(this.xMin + this.xLength / 2, this.yMin + this.yLength / 2);
62
62
  }
63
63
 
@@ -136,16 +136,16 @@ export class BoundingBox {
136
136
 
137
137
  /**
138
138
  * Calculate the boundingbox for an arc
139
- * @param {Point} centerPoint
139
+ * @param {Point} centrePoint
140
140
  * @param {Point} startPoint
141
141
  * @param {Point} endPoint
142
142
  * @param {number} direction - ccw > 0, cw < 0
143
143
  * @return {BoundingBox}
144
144
  */
145
- static arcBoundingBox(centerPoint, startPoint, endPoint, direction = 1) {
146
- const startAngle = centerPoint.angle(startPoint);
147
- const endAngle = centerPoint.angle(endPoint);
148
- const radius = centerPoint.distance(startPoint);
145
+ static arcBoundingBox(centrePoint, startPoint, endPoint, direction = 1) {
146
+ const startAngle = centrePoint.angle(startPoint);
147
+ const endAngle = centrePoint.angle(endPoint);
148
+ const radius = centrePoint.distance(startPoint);
149
149
 
150
150
  const cross0 = this.crossesAxis(startAngle, endAngle, 0, direction);
151
151
  const cross90 = this.crossesAxis(startAngle, endAngle, Math.PI * 0.5, direction);
@@ -154,10 +154,10 @@ export class BoundingBox {
154
154
 
155
155
  // if the arc crosses the axis the min or max is where the arc intersects the axis
156
156
  // otherwise max/min is the arc endpoint
157
- const xmin = cross180 ? centerPoint.x - radius : Math.min(startPoint.x, endPoint.x);
158
- const xmax = cross0 ? centerPoint.x + radius : Math.max(startPoint.x, endPoint.x);
159
- const ymin = cross270 ? centerPoint.y - radius : Math.min(startPoint.y, endPoint.y);
160
- const ymax = cross90 ? centerPoint.y + radius : Math.max(startPoint.y, endPoint.y);
157
+ const xmin = cross180 ? centrePoint.x - radius : Math.min(startPoint.x, endPoint.x);
158
+ const xmax = cross0 ? centrePoint.x + radius : Math.max(startPoint.x, endPoint.x);
159
+ const ymin = cross270 ? centrePoint.y - radius : Math.min(startPoint.y, endPoint.y);
160
+ const ymax = cross90 ? centrePoint.y + radius : Math.max(startPoint.y, endPoint.y);
161
161
 
162
162
  const topLeft = new Point(xmin, ymax);
163
163
  const bottomRight = new Point(xmax, ymin);
@@ -189,7 +189,9 @@ export class Canvas {
189
189
  */
190
190
  wheel(delta) {
191
191
  if (DesignCore.Mouse.buttonTwoDown) return;
192
- const scale = Math.pow(1 + Math.abs(delta), delta > 0 ? 1 : -1);
192
+ // Clamp to [-1, 1] so that high-resolution mice and track pads don't produce runaway jumps.
193
+ const clampedDelta = Math.max(-1, Math.min(1, delta));
194
+ const scale = Math.pow(1.2, clampedDelta);
193
195
  if (scale < 1 && this.getScale() > this.minScaleFactor || scale > 1 && this.getScale() < this.maxScaleFactor) {
194
196
  this.zoom(scale);
195
197
  }
@@ -220,16 +222,21 @@ export class Canvas {
220
222
  const width = xMax - xMin;
221
223
  const height = yMax - yMin;
222
224
 
223
- if (width === 0 || height === 0) {
225
+ if (width === 0 && height === 0) {
224
226
  return;
225
227
  }
226
228
 
227
229
  const selectionCenter = new Point(xMin + width / 2, yMin + height / 2);
228
230
  const screenCenter = new Point(this.width / 2, this.height / 2);
229
231
  const translateDelta = DesignCore.Mouse.transformToScene(screenCenter).subtract(selectionCenter);
230
- const targetScale = Math.min((this.width / width), (this.height / height));
232
+ // fall back to the non-zero axis when the window is a single line
233
+ const targetScale = width === 0 ? this.height / height :
234
+ height === 0 ? this.width / width :
235
+ Math.min((this.width / width), (this.height / height));
231
236
  const scaleDelta = targetScale / this.getScale() * 0.9;
232
237
 
238
+ if (!isFinite(scaleDelta) || scaleDelta <= 0) return;
239
+
233
240
  this.matrix.scale(scaleDelta, scaleDelta);
234
241
  this.matrix.translate((selectionCenter.x / scaleDelta) - selectionCenter.x, (selectionCenter.y / scaleDelta) - selectionCenter.y);
235
242
  this.matrix.translate(translateDelta.x / scaleDelta, translateDelta.y / scaleDelta);
@@ -386,8 +393,8 @@ export class Canvas {
386
393
  this.#paintEntities(DesignCore.Scene.previewEntities, renderer);
387
394
 
388
395
  // Paint the selected scene items: single pass — renderer handles glow + entity internally.
389
- const selectedItems = DesignCore.Scene.selectionManager.selectedItems;
390
- this.#paintEntities(selectedItems, renderer, { colour: selectionHaloColour, lineWidthDelta: selectionLineWidthDelta });
396
+ const selectedEntities = DesignCore.Scene.selectionManager.selectedEntities;
397
+ this.#paintEntities(selectedEntities, renderer, { colour: selectionHaloColour, lineWidthDelta: selectionLineWidthDelta });
391
398
 
392
399
  // Paint the auxiliary scene items
393
400
  // auxiliary items include things like the selection window, snap points etc
@@ -403,7 +410,7 @@ export class Canvas {
403
410
 
404
411
  /**
405
412
  * Recursively paint a single entity and any children it returns.
406
- * Container entities (Insert, BaseDimension) return an array of child items from draw().
413
+ * Container entities (Insert, DimensionBase) return an array of child items from draw().
407
414
  * Leaf entities return undefined. Renderer state is saved/restored around each entity.
408
415
  * @param {Object} entity - entity to paint
409
416
  * @param {Object} renderer - renderer instance
@@ -69,7 +69,7 @@ export class Clipboard {
69
69
  return;
70
70
  }
71
71
 
72
- // copy the item to avoid mutation of originals
72
+ // copy the entity to avoid mutation of originals
73
73
  this.#Entities = entities.map((entity) => Utils.cloneObject(entity));
74
74
  // set basepoint to bottom left of selection bounding box
75
75
  const bbox = BoundingBox.fromEntities(entities);
@@ -152,7 +152,7 @@ export class Clipboard {
152
152
  const clipboardData = { basePoint: undefined, Entities: undefined };
153
153
  const jsonEntities = [];
154
154
 
155
- // convert each entity to a simple object - this allows non-enumberable properties (like points) to be copied
155
+ // convert each entity to a simple object - this allows non-enumerable properties (like points) to be copied
156
156
  for (const ent of this.#Entities) {
157
157
  const jsonEntity = this.#simplify(ent);
158
158
  jsonEntities.push(jsonEntity);
@@ -311,14 +311,14 @@ export class DXF {
311
311
  DesignCore.HandleManager.checkHandle(itemHandle);
312
312
  }
313
313
 
314
- if (block.hasOwnProperty('items') === false) {
315
- block.items = [];
314
+ if (block.hasOwnProperty('entities') === false) {
315
+ block.entities = [];
316
316
  }
317
317
 
318
318
  // add the child to the block items
319
319
  // TODO: create a block instance and add the items.
320
320
  // check block is valid before adding to scene
321
- block.items.push(item);
321
+ block.entities.push(item);
322
322
  }
323
323
  });
324
324
  }
@@ -347,7 +347,7 @@ export class DXF {
347
347
  entity.points = this.parsePoints(entity.points);
348
348
  }
349
349
 
350
- this.addItem(entity);
350
+ this.addEntity(entity);
351
351
  });
352
352
 
353
353
  // ensure all layers, ltypes, styles, dimstyles exist
@@ -378,18 +378,18 @@ export class DXF {
378
378
  }
379
379
 
380
380
  /**
381
- * Add Item
382
- * @param {Object} item
381
+ * Add Entity
382
+ * @param {Object} entity
383
383
  */
384
- addItem(item) {
385
- if (item.hasOwnProperty('0') === false) {
384
+ addEntity(entity) {
385
+ if (entity.hasOwnProperty('0') === false) {
386
386
  return;
387
387
  }
388
388
 
389
- const command = item[0];
389
+ const command = entity[0];
390
390
  if (DesignCore.CommandManager.isCommand(command)) {
391
- const entity = DesignCore.CommandManager.createNew(command, item);
392
- DesignCore.Scene.entities.add(entity);
391
+ const newEntity = DesignCore.CommandManager.createNew(command, entity);
392
+ DesignCore.Scene.entities.add(newEntity);
393
393
  } else {
394
394
  Logging.instance.warn(`${Strings.Message.UNKNOWNCOMMAND} ${command}`);
395
395
  this.unsupportedElements = true;
@@ -23,11 +23,11 @@ Example DXF Entity Pairs:
23
23
  value: 100.0 - y position
24
24
  */
25
25
 
26
- /** DXF Interator Class */
26
+ /** DXF Iterator Class */
27
27
  export class DxfIterator {
28
28
  static _instance;
29
29
 
30
- /** Create DXFInterator */
30
+ /** Create DXFIterator */
31
31
  constructor() {
32
32
  this.currentIndex = 0;
33
33
  this.lines = [];
@@ -92,26 +92,26 @@ export class EntityManager {
92
92
  }
93
93
 
94
94
  /**
95
- * Find items in scene
95
+ * Find entities in scene
96
96
  * @param {string} type - entity type or "ANY"
97
97
  * @param {string} prop - object of entity parameters
98
98
  * @param {any} value - value of the property
99
- * @return {number} - index of items
99
+ * @return {number} - index of entities
100
100
  */
101
101
  find(type, prop, value) {
102
- const filteredItems = [];
102
+ const filteredEntities = [];
103
103
 
104
- this.#entities.forEach((item, index) => {
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);
104
+ this.#entities.forEach((entity, index) => {
105
+ if (type.toUpperCase() === 'ANY' || entity.type.toUpperCase() === type.toUpperCase()) {
106
+ const inStore = entity.properties?.has(prop);
107
+ const propValue = inStore ? entity.getProperty(prop) : (Object.prototype.hasOwnProperty.call(entity, prop) ? entity[prop] : undefined);
108
108
  if (propValue !== undefined && propValue === value) {
109
- filteredItems.push(index);
109
+ filteredEntities.push(index);
110
110
  }
111
111
  }
112
112
  });
113
113
 
114
- return filteredItems;
114
+ return filteredEntities;
115
115
  }
116
116
 
117
117
  /** Get index of entity
@@ -124,16 +124,16 @@ export class EntityManager {
124
124
 
125
125
 
126
126
  /**
127
- * Find closest item to point
127
+ * Find closest entity to point
128
128
  * @param {Point} point
129
- * @return {number} - return index of closest item or undefined
129
+ * @return {number} - return index of closest entity or undefined
130
130
  */
131
131
  findClosest(point) {
132
132
  let delta = 1.65 / DesignCore.Core.canvas.getScale(); // find a more suitable starting value
133
- let closestItemIndex;
133
+ let closestEntityIndex;
134
134
 
135
135
  for (let i = 0; i < this.#entities.length; i++) {
136
- // check the items layer is selectable - i.e. on, thawed, etc...
136
+ // check the entities layer is selectable - i.e. on, thawed, etc...
137
137
  const layer = DesignCore.LayerManager.getItemByName(this.#entities[i].getProperty(Property.Names.LAYER));
138
138
 
139
139
  if (!layer?.isSelectable) {
@@ -144,11 +144,11 @@ export class EntityManager {
144
144
 
145
145
  if (distance < delta) {
146
146
  delta = distance;
147
- closestItemIndex = i;
147
+ closestEntityIndex = i;
148
148
  }
149
149
  }
150
150
 
151
- return closestItemIndex;
151
+ return closestEntityIndex;
152
152
  }
153
153
 
154
154
  /**
@@ -157,14 +157,14 @@ export class EntityManager {
157
157
  * @param {object} data
158
158
  */
159
159
  update(index, data) {
160
- const item = this.get(index);
160
+ const entity = this.get(index);
161
161
 
162
- if (!item) {
163
- throw Error('Item not found in scene');
162
+ if (!entity) {
163
+ throw Error('Entity not found in scene');
164
164
  }
165
165
 
166
166
  for (const property of Object.keys(data)) {
167
- item.setProperty(property, data[property]);
167
+ entity.setProperty(property, data[property]);
168
168
  }
169
169
  }
170
170
 
@@ -392,7 +392,7 @@ export class InputManager {
392
392
  }
393
393
 
394
394
  if (Input.getType(selection) === Input.Type.SINGLESELECTION) {
395
- DesignCore.Scene.selectionManager.addToSelectionSet(selection.selectedItemIndex);
395
+ DesignCore.Scene.selectionManager.addToSelectionSet(selection.selectedEntityIndex);
396
396
  }
397
397
  }
398
398
 
@@ -37,7 +37,7 @@ export class Matrix {
37
37
  };
38
38
 
39
39
  /**
40
- * Apply tranlation to matrix
40
+ * Apply translation to matrix
41
41
  * @param {number} x - translation in x
42
42
  * @param {number} y - translation in y
43
43
  */
@@ -462,7 +462,7 @@ export class Patterns {
462
462
  60, 0, 0, 4.7625, 2.749630645, -6.35, 3.175` },
463
463
 
464
464
  'HOUND': {
465
- description: 'Houndstooth check', pattern:
465
+ description: 'Hounds tooth check', pattern:
466
466
  `0, 0, 0, 6.35, 1.5875, 25.4, -12.7
467
467
  90, 0, 0, 6.35, 1.5875, 25.4, -12.7` },
468
468
 
@@ -16,12 +16,12 @@ export class PolylineUtils {
16
16
  */
17
17
  static isPointOnSegment(point, A, B) {
18
18
  if (A.bulge !== 0 && A.bulge !== undefined) {
19
- const center = A.bulgeCentrePoint(B);
20
- const radius = center.distance(A);
19
+ const centre = A.bulgeCentrePoint(B);
20
+ const radius = centre.distance(A);
21
21
  // Point must lie on the circle before checking the angular range.
22
- if (Utils.round(center.distance(point)) !== Utils.round(radius)) return false;
22
+ if (Utils.round(centre.distance(point)) !== Utils.round(radius)) return false;
23
23
  const direction = A.bulge > 0 ? 1 : -1;
24
- return point.isOnArc(A, B, center, direction);
24
+ return point.isOnArc(A, B, centre, direction);
25
25
  }
26
26
  return point.isOnLine(A, B);
27
27
  }
@@ -38,9 +38,9 @@ export class PolylineUtils {
38
38
  const B = points[segmentIndex % points.length];
39
39
 
40
40
  if (A.bulge !== 0 && A.bulge !== undefined) {
41
- const center = A.bulgeCentrePoint(B);
41
+ const centre = A.bulgeCentrePoint(B);
42
42
  const direction = A.bulge > 0 ? 1 : -1;
43
- const closest = point.closestPointOnArc(A, B, center, direction);
43
+ const closest = point.closestPointOnArc(A, B, centre, direction);
44
44
  if (closest) return closest;
45
45
  // Point not on arc — return the nearer endpoint
46
46
  return point.distance(A) < point.distance(B) ? A : B;
@@ -63,10 +63,10 @@ export class PolylineUtils {
63
63
  const B = points[segmentIndex % points.length];
64
64
 
65
65
  if (A.bulge !== 0 && A.bulge !== undefined) {
66
- const center = A.bulgeCentrePoint(B);
66
+ const centre = A.bulgeCentrePoint(B);
67
67
  const direction = A.bulge > 0 ? 1 : -1;
68
- const startAngle = center.angle(A);
69
- const pointAngle = center.angle(point);
68
+ const startAngle = centre.angle(A);
69
+ const pointAngle = centre.angle(point);
70
70
  return ((pointAngle - startAngle) * direction + 4 * Math.PI) % (2 * Math.PI);
71
71
  }
72
72
 
@@ -159,14 +159,14 @@ export class CairoRenderer extends RendererBase {
159
159
  if (!p.bulge) {
160
160
  this.#cr.lineTo(next.x, next.y);
161
161
  } else {
162
- const center = p.bulgeCentrePoint(next);
162
+ const centre = p.bulgeCentrePoint(next);
163
163
  const radius = p.bulgeRadius(next);
164
- const startAngle = center.angle(p);
165
- const endAngle = center.angle(next);
164
+ const startAngle = centre.angle(p);
165
+ const endAngle = centre.angle(next);
166
166
  if (p.bulge > 0) {
167
- this.#cr.arc(center.x, center.y, radius, startAngle, endAngle);
167
+ this.#cr.arc(centre.x, centre.y, radius, startAngle, endAngle);
168
168
  } else {
169
- this.#cr.arcNegative(center.x, center.y, radius, startAngle, endAngle);
169
+ this.#cr.arcNegative(centre.x, centre.y, radius, startAngle, endAngle);
170
170
  }
171
171
  }
172
172
  }
@@ -141,12 +141,12 @@ export class CanvasRenderer extends RendererBase {
141
141
  if (!p.bulge) {
142
142
  this.#ctx.lineTo(next.x, next.y);
143
143
  } else {
144
- const center = p.bulgeCentrePoint(next);
144
+ const centre = p.bulgeCentrePoint(next);
145
145
  const radius = p.bulgeRadius(next);
146
- const startAngle = center.angle(p);
147
- const endAngle = center.angle(next);
146
+ const startAngle = centre.angle(p);
147
+ const endAngle = centre.angle(next);
148
148
  // Canvas arc: CCW = false (default), CW = true (anticlockwise flag inverted)
149
- this.#ctx.arc(center.x, center.y, radius, startAngle, endAngle, p.bulge < 0);
149
+ this.#ctx.arc(centre.x, centre.y, radius, startAngle, endAngle, p.bulge < 0);
150
150
  }
151
151
  }
152
152
  }
@@ -102,11 +102,11 @@ export class PdfRenderer extends RendererBase {
102
102
  if (!p.bulge) {
103
103
  this.#emit(`${this.#fmt(next.x)} ${this.#fmt(next.y)} l`);
104
104
  } else {
105
- const center = p.bulgeCentrePoint(next);
105
+ const centre = p.bulgeCentrePoint(next);
106
106
  const radius = p.bulgeRadius(next);
107
- const startAngle = center.angle(p);
107
+ const startAngle = centre.angle(p);
108
108
  const sweepAngle = p.bulgeAngle();
109
- for (const curve of this.#arcToBezier(center, radius, startAngle, sweepAngle)) {
109
+ for (const curve of this.#arcToBezier(centre, radius, startAngle, sweepAngle)) {
110
110
  this.#emit(`${this.#fmt(curve.cp1x)} ${this.#fmt(curve.cp1y)} ${this.#fmt(curve.cp2x)} ${this.#fmt(curve.cp2y)} ${this.#fmt(curve.x)} ${this.#fmt(curve.y)} c`);
111
111
  }
112
112
  }
@@ -321,13 +321,13 @@ export class PdfRenderer extends RendererBase {
321
321
  /**
322
322
  * Approximate a circular arc as one or more PDF cubic Bezier curves.
323
323
  * Arcs larger than 90° are split into ≤90° segments for accuracy.
324
- * @param {Point} center - arc centre
324
+ * @param {Point} centre - arc centre
325
325
  * @param {number} radius - arc radius
326
326
  * @param {number} startAngle - angle from positive x-axis to arc start (radians)
327
327
  * @param {number} sweepAngle - signed sweep: positive = CCW, negative = CW (radians)
328
328
  * @return {Array<{cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number}>}
329
329
  */
330
- #arcToBezier(center, radius, startAngle, sweepAngle) {
330
+ #arcToBezier(centre, radius, startAngle, sweepAngle) {
331
331
  // Number of ≤90° segments needed to keep Bezier error below ~0.03%
332
332
  const steps = Math.max(1, Math.ceil(Math.abs(sweepAngle) / (Math.PI / 2)));
333
333
  // Signed angle of each segment (negative for CW arcs)
@@ -340,8 +340,8 @@ export class PdfRenderer extends RendererBase {
340
340
  const a1 = startAngle + i * stepAngle;
341
341
  const a2 = a1 + stepAngle;
342
342
  // Points on the circle at the segment start and end angles
343
- const p1 = center.project(a1, radius);
344
- const p2 = center.project(a2, radius);
343
+ const p1 = centre.project(a1, radius);
344
+ const p2 = centre.project(a2, radius);
345
345
  // Control points are offset from p1/p2 along the tangent direction by radius*tangentScale.
346
346
  // Tangent at p1 (CCW) is a1 + π/2; tangent at p2 (CW, pointing back) is a2 - π/2.
347
347
  // tangentScale is signed, so CW arcs (negative sweepAngle) automatically reverse the offset.
@@ -11,7 +11,7 @@
11
11
  * correction.
12
12
  *
13
13
  * Transform helpers (applyTransform, save, restore) are called by canvas.js
14
- * when processing container entities (Insert, BaseDimension) that return a
14
+ * when processing container entities (Insert, DimensionBase) that return a
15
15
  * child list from draw().
16
16
  */
17
17
  export class RendererBase {
package/core/lib/scene.js CHANGED
@@ -75,7 +75,7 @@ export class Scene {
75
75
  * @param {number} index - integer of item to replace
76
76
  * @return {number} - index of created item
77
77
  */
78
- addItem(type, data, index) {
78
+ addEntity(type, data, index) {
79
79
  // TODO: validate data is valid for type
80
80
  if (!data) {
81
81
  throw Error('Input data missing');
@@ -13,7 +13,7 @@ export class SingleSelection {
13
13
  * @param {Point} point
14
14
  */
15
15
  constructor(index, point) {
16
- this.selectedItemIndex = index;
16
+ this.selectedEntityIndex = index;
17
17
  this.selectedPoint = point;
18
18
  }
19
19
  }
@@ -41,24 +41,24 @@ export class SelectionSet {
41
41
  export class SelectionManager {
42
42
  /** Create a SelectionManager */
43
43
  constructor() {
44
- this.selectionSet = new SelectionSet(); // store a list of selected items indices
45
- this.selectedItems = []; // store a copy of selected items
44
+ this.selectionSet = new SelectionSet(); // store a list of selected entities indices
45
+ this.selectedEntities = []; // store a copy of selected entities
46
46
  }
47
47
 
48
48
  /**
49
49
  * Reset the selection state
50
50
  */
51
51
  reset() {
52
- this.selectedItems = [];
52
+ this.selectedEntities = [];
53
53
  this.selectionSet = new SelectionSet();
54
54
  this.selectionSetChanged();
55
55
  }
56
56
 
57
57
  /**
58
- * Remove the last selection from the selected items
58
+ * Remove the last selection from the selected entities
59
59
  */
60
60
  removeLastSelection() {
61
- this.selectedItems.pop();
61
+ this.selectedEntities.pop();
62
62
  this.selectionSet.removeLastSelection();
63
63
  this.selectionSetChanged();
64
64
  }
@@ -157,19 +157,19 @@ export class SelectionManager {
157
157
  }
158
158
 
159
159
  /**
160
- * Remove the item at index from the selectionSet and selectedItems
160
+ * Remove the item at index from the selectionSet and selectedEntities
161
161
  * @param {number} index - index of the item
162
162
  */
163
163
  removeFromSelectionSet(index) {
164
164
  const itemIndex = this.selectionSet.selectionSet.indexOf(index);
165
165
  if (itemIndex !== -1) {
166
166
  this.selectionSet.selectionSet.splice(itemIndex, 1);
167
- this.selectedItems.splice(itemIndex, 1);
167
+ this.selectedEntities.splice(itemIndex, 1);
168
168
  }
169
169
  }
170
170
 
171
171
  /**
172
- * Add the item at index to the selectionSet and selectedItems
172
+ * Add the item at index to the selectionSet and selectedEntities
173
173
  * @param {number} index
174
174
  */
175
175
  addToSelectionSet(index) {
@@ -179,28 +179,28 @@ export class SelectionManager {
179
179
  // only store selections once
180
180
  if (this.selectionSet.selectionSet.indexOf(index) === -1) {
181
181
  this.selectionSet.selectionSet.push(index);
182
- this.addToSelectedItems(index);
182
+ this.addToSelectedEntities(index);
183
183
  }
184
184
  }
185
185
 
186
186
  /**
187
- * Duplicate the item at index and add to selectedItems
187
+ * Duplicate the item at index and add to selectedEntities
188
188
  * @param {number} index
189
189
  */
190
- addToSelectedItems(index) {
191
- const copyofitem = Utils.cloneObject(DesignCore.Scene.entities.get(index));
192
- this.selectedItems.push(copyofitem);
190
+ addToSelectedEntities(index) {
191
+ const copyOfItem = Utils.cloneObject(DesignCore.Scene.entities.get(index));
192
+ this.selectedEntities.push(copyOfItem);
193
193
  }
194
194
 
195
195
  /**
196
- * Reload the selectedItems
196
+ * Reload the selectedEntities
197
197
  * This is required following changes to selected item properties
198
198
  */
199
- reloadSelectedItems() {
200
- this.selectedItems = [];
199
+ reloadSelectedEntities() {
200
+ this.selectedEntities = [];
201
201
 
202
202
  for (let i = 0; i < this.selectionSet.selectionSet.length; i++) {
203
- this.addToSelectedItems(this.selectionSet.selectionSet[i]);
203
+ this.addToSelectedEntities(this.selectionSet.selectionSet[i]);
204
204
  }
205
205
 
206
206
  DesignCore.Core.canvas.requestPaint();
package/core/lib/utils.js CHANGED
@@ -47,26 +47,26 @@ export class Utils {
47
47
  }
48
48
 
49
49
  /**
50
- * Sort points around an arc center by angular position.
50
+ * Sort points around an arc centre by angular position.
51
51
  * Mutates the points array.
52
52
  *
53
53
  * @param {Array} points - array of points
54
54
  * @param {Point} startPoint - arc start point
55
55
  * @param {Point} endPoint - arc end point
56
- * @param {Point} centerPoint - arc center point
56
+ * @param {Point} centrePoint - arc centre point
57
57
  * @param {number} direction - arc direction (ccw >0, cw <=0)
58
58
  *
59
59
  */
60
- static sortPointsOnArc(points, startPoint, endPoint, centerPoint, direction = 1) {
60
+ static sortPointsOnArc(points, startPoint, endPoint, centrePoint, direction = 1) {
61
61
  if (!Array.isArray(points)) return;
62
62
 
63
- const refAngle = direction > 0 ? centerPoint.angle(startPoint) : centerPoint.angle(endPoint);
63
+ const refAngle = direction > 0 ? centrePoint.angle(startPoint) : centrePoint.angle(endPoint);
64
64
  const twoPi = Math.PI * 2;
65
65
  const normalize = (ang) => ((ang % twoPi) + twoPi) % twoPi;
66
66
 
67
67
  // map points to angles
68
68
  const mapped = points.map((point) => {
69
- const angle = Math.atan2(point.y - centerPoint.y, point.x - centerPoint.x);
69
+ const angle = Math.atan2(point.y - centrePoint.y, point.x - centrePoint.x);
70
70
  const normalizedAngle = normalize(angle - refAngle);
71
71
  return { point, normalizedAngle };
72
72
  });
@@ -58,7 +58,8 @@ export class EntityProperties {
58
58
  set(name, value, entity) {
59
59
  const prop = this._store[name];
60
60
  if (!prop) return;
61
- if (prop.readOnly) return;
61
+ const isReadOnly = typeof prop.readOnly === 'function' ? prop.readOnly(entity) : prop.readOnly;
62
+ if (isReadOnly) return;
62
63
  if (prop.set) {
63
64
  prop.set(entity, value);
64
65
  } else {
@@ -28,6 +28,18 @@ export class Flags {
28
28
  return this.flags;
29
29
  }
30
30
 
31
+ /**
32
+ * Serialise as a plain number so JSON.stringify produces e.g. `"flags":1`
33
+ * instead of `"flags":{"flags":1}`. Without this, the round-trip through
34
+ * clipboard stringify/parse loses the flag value (e.g. the closed flag on a
35
+ * polyline), because the reconstructed plain object is not a Flags instance
36
+ * and Property.loadValue cannot extract the numeric value from it.
37
+ * @return {number}
38
+ */
39
+ toJSON() {
40
+ return this.flags;
41
+ }
42
+
31
43
  /**
32
44
  * Get the backwards value
33
45
  * @param {number} flagValue