@design-core/core 0.0.5 → 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.
Files changed (145) hide show
  1. package/.eslintrc.json +4 -0
  2. package/TODO.md +49 -6
  3. package/core/core/core.js +0 -3
  4. package/core/dimensions/alignedDimension.js +5 -4
  5. package/core/dimensions/angularDimension.js +6 -5
  6. package/core/dimensions/baseCircularDimension.js +3 -2
  7. package/core/dimensions/baseDimension.js +59 -93
  8. package/core/dimensions/baseLinearDimension.js +6 -5
  9. package/core/dimensions/diametricDimension.js +4 -3
  10. package/core/dimensions/dimension.js +21 -14
  11. package/core/dimensions/radialDimension.js +4 -3
  12. package/core/dimensions/rotatedDimension.js +12 -11
  13. package/core/entities/arc.js +112 -145
  14. package/core/entities/arctext.js +212 -174
  15. package/core/entities/basePolyline.js +33 -284
  16. package/core/entities/circle.js +77 -99
  17. package/core/entities/entity.js +114 -93
  18. package/core/entities/hatch.js +322 -249
  19. package/core/entities/insert.js +66 -81
  20. package/core/entities/line.js +21 -142
  21. package/core/entities/lwpolyline.js +16 -11
  22. package/core/entities/polyline.js +4 -4
  23. package/core/entities/rectangle.js +3 -1
  24. package/core/entities/solid.js +6 -15
  25. package/core/entities/text.js +188 -199
  26. package/core/lib/auxiliary/rubberBand.js +32 -0
  27. package/core/lib/auxiliary/selectionWindow.js +63 -0
  28. package/core/lib/auxiliary/snapPoint.js +113 -0
  29. package/core/lib/auxiliary/trackingLine.js +76 -0
  30. package/core/lib/canvas.js +228 -166
  31. package/core/lib/clipboard.js +7 -1
  32. package/core/lib/colour.js +16 -0
  33. package/core/lib/commandLine.js +1 -6
  34. package/core/lib/commandManager.js +18 -8
  35. package/core/lib/constants.js +28 -0
  36. package/core/lib/dxf/dxf.js +4 -3
  37. package/core/lib/entityManager.js +18 -11
  38. package/core/lib/inputManager.js +36 -13
  39. package/core/lib/intersect.js +25 -0
  40. package/core/lib/patterns.js +8 -1
  41. package/core/lib/plotOptions.js +67 -0
  42. package/core/lib/polylineUtils.js +75 -0
  43. package/core/lib/renderers/cairoRenderer.js +371 -0
  44. package/core/lib/renderers/canvasRenderer.js +315 -0
  45. package/core/lib/renderers/pdfRenderer.js +366 -0
  46. package/core/lib/renderers/rendererBase.js +284 -0
  47. package/core/lib/renderers/svgRenderer.js +343 -0
  48. package/core/lib/scene.js +27 -5
  49. package/core/lib/selectionManager.js +2 -2
  50. package/core/lib/settings.js +4 -2
  51. package/core/lib/snapping.js +4 -191
  52. package/core/lib/stateManager.js +30 -1
  53. package/core/lib/utils.js +21 -2
  54. package/core/properties/entityProperties.js +104 -0
  55. package/core/properties/property.js +106 -2
  56. package/core/properties/propertyManager.js +29 -13
  57. package/core/tables/block.js +4 -37
  58. package/core/tables/endblk.js +1 -1
  59. package/core/tables/layer.js +8 -0
  60. package/core/tables/styleManagerBase.js +3 -2
  61. package/core/tables/tableManagerBase.js +8 -3
  62. package/core/tools/chamfer.js +111 -137
  63. package/core/tools/chamferFilletBase.js +135 -34
  64. package/core/tools/copy.js +3 -3
  65. package/core/tools/explode.js +2 -2
  66. package/core/tools/extend.js +132 -19
  67. package/core/tools/fillet.js +86 -183
  68. package/core/tools/matchProp.js +2 -11
  69. package/core/tools/mirror.js +7 -6
  70. package/core/tools/move.js +3 -3
  71. package/core/tools/offset.js +3 -5
  72. package/core/tools/pasteclip.js +1 -1
  73. package/core/tools/rotate.js +6 -3
  74. package/core/tools/scale.js +1 -1
  75. package/core/tools/trim.js +265 -23
  76. package/core/tools/zoom.js +1 -1
  77. package/jest.config.js +6 -0
  78. package/package.json +5 -3
  79. package/test/dimensions/alignedDimension.test.js +14 -7
  80. package/test/dimensions/angularDimension.test.js +6 -6
  81. package/test/dimensions/baseDimension.test.js +69 -8
  82. package/test/dimensions/baseLinearDimension.test.js +62 -0
  83. package/test/dimensions/diametricDimension.test.js +6 -6
  84. package/test/dimensions/radialDimension.test.js +6 -6
  85. package/test/dimensions/rotatedDimension.test.js +14 -7
  86. package/test/entities/arc.test.js +80 -83
  87. package/test/entities/arctext.test.js +180 -53
  88. package/test/entities/basePolyline.test.js +129 -2
  89. package/test/entities/circle.test.js +57 -85
  90. package/test/entities/entity.test.js +42 -8
  91. package/test/entities/hatch.test.js +543 -70
  92. package/test/entities/insert.test.js +96 -1
  93. package/test/entities/line.test.js +15 -41
  94. package/test/entities/lwpolyline.test.js +104 -0
  95. package/test/entities/polyline.test.js +11 -0
  96. package/test/entities/rectangle.test.js +11 -0
  97. package/test/entities/solid.test.js +1 -1
  98. package/test/entities/text.test.js +294 -69
  99. package/test/integration/exportIntegration.reference.dxf +9 -9
  100. package/test/integration/exportIntegration.test.js +2 -2
  101. package/test/integration/intersectionIntegration.test.js +4 -5
  102. package/test/integration/pdfRendererIntegration.reference.pdf +17709 -0
  103. package/test/integration/pdfRendererIntegration.test.js +61 -0
  104. package/test/integration/svgRendererIntegration.reference.svg +3343 -0
  105. package/test/integration/svgRendererIntegration.test.js +61 -0
  106. package/test/integration/userInputIntegration.reference.dxf +1165 -105
  107. package/test/integration/userInputIntegration.test.js +239 -132
  108. package/test/lib/canvas.test.js +742 -123
  109. package/test/lib/constants.test.js +27 -0
  110. package/test/lib/entityManager.test.js +4 -4
  111. package/test/lib/inputManager.test.js +80 -19
  112. package/test/lib/intersect.test.js +83 -0
  113. package/test/lib/plotOptions.test.js +179 -0
  114. package/test/lib/rubberBand.test.js +84 -0
  115. package/test/lib/settings.test.js +5 -6
  116. package/test/lib/snapping.test.js +71 -129
  117. package/test/lib/stateManager.test.js +7 -7
  118. package/test/lib/utils.test.js +36 -2
  119. package/test/properties/entityProperties.test.js +132 -0
  120. package/test/properties/property.test.js +53 -0
  121. package/test/properties/propertyManager.test.js +3 -3
  122. package/test/renderers/canvasRenderer.test.js +317 -0
  123. package/test/renderers/pdfRenderer.test.js +271 -0
  124. package/test/renderers/rendererBase.test.js +213 -0
  125. package/test/renderers/svgRenderer.test.js +326 -0
  126. package/test/tables/block.test.js +31 -1
  127. package/test/tables/endblk.test.js +2 -2
  128. package/test/tables/layer.test.js +30 -0
  129. package/test/test-helpers/hatchGridRenderer.js +180 -0
  130. package/test/test-helpers/test-helpers.js +152 -0
  131. package/test/tools/chamfer.test.js +227 -5
  132. package/test/tools/chamferFilletBase.test.js +98 -9
  133. package/test/tools/copy.test.js +2 -2
  134. package/test/tools/explode.test.js +3 -3
  135. package/test/tools/extend.test.js +331 -0
  136. package/test/tools/fillet.test.js +296 -0
  137. package/test/tools/matchProp.test.js +5 -5
  138. package/test/tools/mirror.test.js +13 -13
  139. package/test/tools/offset.test.js +170 -3
  140. package/test/tools/pasteclip.test.js +5 -5
  141. package/test/tools/rotate.test.js +32 -2
  142. package/test/tools/scale.test.js +5 -5
  143. package/test/tools/trim.test.js +526 -0
  144. package/test/tools/zoom.test.js +7 -7
  145. package/core/lib/selectionWindow.js +0 -83
@@ -96,7 +96,7 @@ ByLayer
96
96
  30
97
97
  0.0
98
98
  39
99
- 2
99
+ 1
100
100
  40
101
101
  100
102
102
  `;
@@ -105,7 +105,7 @@ ByLayer
105
105
 
106
106
 
107
107
  // create new entity from entity data to ensure all props are loaded
108
- const newCircle = new Circle(circle);
108
+ const newCircle = new Circle({ handle: circle.getProperty('handle'), points: circle.points });
109
109
  file = new File();
110
110
  newCircle.dxf(file);
111
111
  expect(file.contents).toEqual(dxfString);
@@ -127,73 +127,6 @@ test('Test Circle.toPolylinePoints', () => {
127
127
  expect(circlePoints[2].bulge).toBe(0);
128
128
  });
129
129
 
130
- test('Test Circle.trim returns empty array when provided with empty intersection list', () => {
131
- const circle = new Circle({ points: [new Point(0, 0), new Point(100, 0)] });
132
-
133
- expect(circle.trim([])).toEqual([]);
134
- expect(circle.trim()).toEqual([]);
135
- });
136
-
137
- test('Test Circle.trim does not modify circle', () => {
138
- const circle = new Circle({ points: [new Point(0, 0), new Point(10, 0)] });
139
-
140
- DesignCore.Mouse.pointOnScene = () => new Point(7.71, 7.71);
141
-
142
- // test a single intersection - not valid
143
- const intersections = [new Point(10, 0)];
144
- expect(circle.trim(intersections)).toEqual([]);
145
-
146
- // test two intersections - should return an arc
147
- intersections.push(new Point(-10, 0));
148
- let trimWithTwoPoints = circle.trim(intersections);
149
- let trimResult = trimWithTwoPoints[0].entity;
150
- expect(trimResult.type).toEqual('Arc');
151
- // Verify trimmed arc has a unique handle (not the circle's)
152
- expect(trimResult.handle).toBeUndefined();
153
- expect(trimResult.points[0].x).toEqual(0);
154
- expect(trimResult.points[0].y).toEqual(0);
155
-
156
- expect(trimResult.points[1].x).toEqual(10);
157
- expect(trimResult.points[1].y).toEqual(0);
158
-
159
- expect(trimResult.points[2].x).toEqual(-10);
160
- expect(trimResult.points[2].y).toEqual(0);
161
-
162
- expect(trimWithTwoPoints[1].entity).toEqual(circle);
163
-
164
- // test three intersections - should return an arc from first two points found
165
- intersections.push(new Point(0, 10));
166
- trimWithTwoPoints = circle.trim(intersections);
167
- trimResult = trimWithTwoPoints[0].entity;
168
- expect(trimResult.type).toEqual('Arc');
169
- expect(trimResult.points[0].x).toEqual(0);
170
- expect(trimResult.points[0].y).toEqual(0);
171
-
172
- expect(trimResult.points[1].x).toEqual(10);
173
- expect(trimResult.points[1].y).toEqual(0);
174
-
175
- expect(trimResult.points[2].x).toEqual(0);
176
- expect(trimResult.points[2].y).toEqual(10);
177
-
178
- expect(trimWithTwoPoints[1].entity).toEqual(circle);
179
-
180
- // test four intersections - should return an arc from first two points found
181
- intersections.push(new Point(0, -10));
182
- trimWithTwoPoints = circle.trim(intersections);
183
- trimResult = trimWithTwoPoints[0].entity;
184
- expect(trimResult.type).toEqual('Arc');
185
- expect(trimResult.points[0].x).toEqual(0);
186
- expect(trimResult.points[0].y).toEqual(0);
187
-
188
- expect(trimResult.points[1].x).toEqual(10);
189
- expect(trimResult.points[1].y).toEqual(0);
190
-
191
- expect(trimResult.points[2].x).toEqual(0);
192
- expect(trimResult.points[2].y).toEqual(10);
193
-
194
- expect(trimWithTwoPoints[1].entity).toEqual(circle);
195
- });
196
-
197
130
  test('Circle.execute creates circle from diameter input', async () => {
198
131
  const center = new Point(0, 0);
199
132
 
@@ -234,8 +167,8 @@ test('Circle.register returns command object', () => {
234
167
  expect(Circle.register()).toEqual({ command: 'Circle', shortcut: 'C', type: 'Entity' });
235
168
  });
236
169
 
237
- test('Circle constructor loads radius from DXF group code 40', () => {
238
- const circle = new Circle({ points: [new Point(0, 0)], 40: 50 });
170
+ test('Circle constructor loads radius from DXF group code 40 via fromDxf', () => {
171
+ const circle = new Circle(Circle.fromDxf({ points: [new Point(0, 0)], 40: 50 }));
239
172
  expect(circle.getRadius()).toBeCloseTo(50);
240
173
  expect(circle.points[1].x).toBeCloseTo(50);
241
174
  expect(circle.points[1].y).toBeCloseTo(0);
@@ -263,10 +196,10 @@ test('Circle.execute re-prompts on zero-distance point input', async () => {
263
196
  });
264
197
 
265
198
  test('Circle.preview does not throw with 0 or 1 points', () => {
266
- const origCreate = DesignCore.Scene.tempEntities.create;
199
+ const origCreate = DesignCore.Scene.previewEntities.create;
267
200
  const origMouse = DesignCore.Mouse.pointOnScene;
268
201
  const calls = [];
269
- DesignCore.Scene.tempEntities.create = (type, obj) => calls.push([type, obj]);
202
+ DesignCore.Scene.previewEntities.create = (type, obj) => calls.push([type, obj]);
270
203
  DesignCore.Mouse.pointOnScene = () => new Point(5, 5);
271
204
 
272
205
  const circle0 = new Circle({});
@@ -278,16 +211,20 @@ test('Circle.preview does not throw with 0 or 1 points', () => {
278
211
  expect(() => circle1.preview()).not.toThrow();
279
212
  expect(calls.some(([type]) => type === 'Circle')).toBe(true);
280
213
 
281
- DesignCore.Scene.tempEntities.create = origCreate;
214
+ DesignCore.Scene.previewEntities.create = origCreate;
282
215
  DesignCore.Mouse.pointOnScene = origMouse;
283
216
  });
284
217
 
285
- test('Circle.draw does not throw', () => {
218
+ test('Circle.draw calls renderer.drawShape with toPolylinePoints', () => {
286
219
  const circle = new Circle({ points: [new Point(0, 0), new Point(10, 0)] });
287
- const ctx = { arc: jest.fn(), stroke: jest.fn() };
288
- expect(() => circle.draw(ctx, 1)).not.toThrow();
289
- expect(ctx.arc).toHaveBeenCalledTimes(1);
290
- expect(ctx.stroke).toHaveBeenCalledTimes(1);
220
+ let capturedPoints;
221
+ const mockRenderer = {
222
+ drawShape(points) {
223
+ capturedPoints = points;
224
+ },
225
+ };
226
+ circle.draw(mockRenderer);
227
+ expect(capturedPoints).toEqual(circle.toPolylinePoints());
291
228
  });
292
229
 
293
230
  test('Circle.snaps returns centre snap', () => {
@@ -326,21 +263,21 @@ test('Circle.snaps nearest snap does not fire when too far', () => {
326
263
  test('Circle.fromPolylinePoints round-trip preserves centre and radius', () => {
327
264
  const circle = new Circle({ points: [new Point(5, 10), new Point(5, 10).project(0, 8)] });
328
265
  const polyPts = circle.toPolylinePoints();
329
- const rebuilt = new Circle({});
330
- rebuilt.fromPolylinePoints(polyPts);
266
+ const rebuilt = new Circle({}).fromPolylinePoints(polyPts);
267
+ expect(rebuilt.type).toBe('Circle');
331
268
  expect(rebuilt.points[0].x).toBeCloseTo(5, 10);
332
269
  expect(rebuilt.points[0].y).toBeCloseTo(10, 10);
333
- expect(rebuilt.radius).toBeCloseTo(8, 10);
270
+ expect(rebuilt.getProperty('radius')).toBeCloseTo(8, 10);
334
271
  });
335
272
 
336
273
  test('Circle.fromPolylinePoints precision drift is negligible', () => {
337
274
  const circle = new Circle({ points: [new Point(123.456, -789.012), new Point(123.456, -789.012).project(1.234, 56.789)] });
338
275
  const polyPts = circle.toPolylinePoints();
339
- const rebuilt = new Circle({});
340
- rebuilt.fromPolylinePoints(polyPts);
276
+ const rebuilt = new Circle({}).fromPolylinePoints(polyPts);
277
+ expect(rebuilt.type).toBe('Circle');
341
278
  const drift = Math.hypot(rebuilt.points[0].x - circle.points[0].x, rebuilt.points[0].y - circle.points[0].y);
342
279
  expect(drift).toBeLessThan(1e-10);
343
- expect(Math.abs(rebuilt.radius - circle.radius)).toBeLessThan(1e-10);
280
+ expect(Math.abs(rebuilt.getProperty('radius') - circle.getProperty('radius'))).toBeLessThan(1e-10);
344
281
  });
345
282
 
346
283
  test('Circle.snaps returns two tangent snaps from outside', () => {
@@ -357,6 +294,41 @@ test('Circle.snaps returns two tangent snaps from outside', () => {
357
294
  DesignCore.Scene.inputManager.inputPoint = savedInputPoint;
358
295
  });
359
296
 
297
+ describe('Circle.fromDxf', () => {
298
+ test('projects edge point from DXF radius code', () => {
299
+ const center = new Point(0, 0);
300
+ const result = Circle.fromDxf({ points: [center], 40: 10 });
301
+ expect(result.points.length).toBe(2);
302
+ expect(result.points[0]).toBe(center);
303
+ expect(result.points[1].x).toBeCloseTo(10);
304
+ expect(result.points[1].y).toBeCloseTo(0);
305
+ });
306
+
307
+ test('preserves all other data properties', () => {
308
+ const result = Circle.fromDxf({ points: [new Point(0, 0)], 40: 10, 8: 'MyLayer' });
309
+ expect(result[8]).toBe('MyLayer');
310
+ });
311
+
312
+ test('returns data unchanged when no center point', () => {
313
+ const data = { 40: 10 };
314
+ expect(Circle.fromDxf(data)).toBe(data);
315
+ });
316
+
317
+ test('returns data unchanged when no radius code', () => {
318
+ const data = { points: [new Point(0, 0)] };
319
+ expect(Circle.fromDxf(data)).toBe(data);
320
+ });
321
+
322
+ test('circle constructed via fromDxf matches circle constructed directly', () => {
323
+ const center = new Point(100, 100);
324
+ const fromDxfCircle = new Circle(Circle.fromDxf({ points: [center], 40: 50 }));
325
+ const directCircle = new Circle({ points: [center, new Point(150, 100)] });
326
+ expect(fromDxfCircle.getRadius()).toBeCloseTo(directCircle.getRadius());
327
+ expect(fromDxfCircle.points[0].x).toBeCloseTo(directCircle.points[0].x);
328
+ expect(fromDxfCircle.points[0].y).toBeCloseTo(directCircle.points[0].y);
329
+ });
330
+ });
331
+
360
332
  test('Circle.snaps returns no tangent when fromPoint is inside the circle', () => {
361
333
  const circle = new Circle({ points: [new Point(0, 0), new Point(10, 0)] });
362
334
  const savedInputPoint = DesignCore.Scene.inputManager.inputPoint;
@@ -10,7 +10,7 @@ new Core();
10
10
 
11
11
  test('Test Entity.colour', () => {
12
12
  const entity = new Entity();
13
- expect(entity.colour).toEqual({ 'r': 1, 'g': 1, 'b': 1 });
13
+ expect(entity.getProperty('colour')).toEqual({ 'r': 1, 'g': 1, 'b': 1 });
14
14
  });
15
15
 
16
16
  test('Test Entity.getLineType', () => {
@@ -36,12 +36,46 @@ test('Test Entity.touched', () => {
36
36
  expect(entity.touched(selectionExtremesTrue)).toBe(true);
37
37
  });
38
38
 
39
- test('Test Entity.trim', () => {
40
- const entity = new Entity();
41
- expect(entity.trim()).toEqual([]);
42
- });
39
+ describe('Entity.fromDxf', () => {
40
+ test('maps group code 62 to colourACI (absolute value)', () => {
41
+ const data = Entity.fromDxf({ 62: 3 });
42
+ expect(data.colourACI).toBe(3);
43
43
 
44
- test('Test Entity.extend', () => {
45
- const entity = new Entity();
46
- expect(entity.extend()).toEqual([]);
44
+ // negative ACI indicates layer is off; should be made positive
45
+ const negData = Entity.fromDxf({ 62: -3 });
46
+ expect(negData.colourACI).toBe(3);
47
+ });
48
+
49
+ test('maps group code 420 to trueColour', () => {
50
+ // Red=200, Green=100, Blue=50 → 0x00C86432 = 13132850
51
+ const data = Entity.fromDxf({ 420: 13132850 });
52
+ expect(data.trueColour).toBe(13132850);
53
+ });
54
+
55
+ test('preserves all other properties unchanged', () => {
56
+ const data = Entity.fromDxf({ layer: 'TEST', 8: 'TEST', 5: 'ABC' });
57
+ expect(data.layer).toBe('TEST');
58
+ expect(data[8]).toBe('TEST');
59
+ expect(data[5]).toBe('ABC');
60
+ });
61
+
62
+ test('ACI colour is applied to entity via colourACI', () => {
63
+ const e = new Entity(Entity.fromDxf({ 62: 1 })); // ACI 1 = red
64
+ expect(e.getProperty('colour')).toEqual({ r: 255, g: 0, b: 0 });
65
+ });
66
+
67
+ test('true colour overrides ACI when both are present', () => {
68
+ // ACI 1 = red; trueColour = green (0x0000FF00 >> 8 = 255... actually 0x00 00 FF 00)
69
+ // pure green = r:0, g:255, b:0 → 0x0000FF00 = 65280
70
+ const e = new Entity(Entity.fromDxf({ 62: 1, 420: 65280 }));
71
+ expect(e.getProperty('colour').g).toBe(255);
72
+ expect(e.getProperty('colour').r).toBe(0);
73
+ expect(e.getProperty('colour').b).toBe(0);
74
+ });
75
+
76
+ test('no colour codes leaves entity at default byLayer colour', () => {
77
+ const data = Entity.fromDxf({ layer: '0' });
78
+ expect(data.colourACI).toBeUndefined();
79
+ expect(data.trueColour).toBeUndefined();
80
+ });
47
81
  });