@design-core/core 0.0.1 → 0.0.3

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 (254) hide show
  1. package/.eslintrc.json +14 -7
  2. package/.github/workflows/DesignCoreQA.yml +3 -1
  3. package/README.md +11 -4
  4. package/TODO.md +19 -0
  5. package/core/core/core.js +49 -42
  6. package/core/designCore.js +61 -2
  7. package/core/dimensions/alignedDimension.js +142 -0
  8. package/core/dimensions/angularDimension.js +550 -0
  9. package/core/dimensions/baseCircularDimension.js +244 -0
  10. package/core/dimensions/baseDimension.js +558 -0
  11. package/core/dimensions/baseLinearDimension.js +371 -0
  12. package/core/dimensions/diametricDimension.js +82 -0
  13. package/core/dimensions/dimension.js +298 -0
  14. package/core/dimensions/linearDimension.js +29 -0
  15. package/core/dimensions/radialDimension.js +78 -0
  16. package/core/dimensions/rotatedDimension.js +167 -0
  17. package/core/entities/arc.js +314 -60
  18. package/core/entities/arctext.js +642 -0
  19. package/core/entities/basePolyline.js +553 -94
  20. package/core/entities/circle.js +181 -54
  21. package/core/entities/entity.js +154 -74
  22. package/core/entities/hatch.js +807 -0
  23. package/core/entities/insert.js +200 -39
  24. package/core/entities/line.js +196 -134
  25. package/core/entities/lwpolyline.js +21 -44
  26. package/core/entities/point.js +194 -29
  27. package/core/entities/polyline.js +54 -7
  28. package/core/entities/rectangle.js +43 -13
  29. package/core/entities/solid.js +43 -6
  30. package/core/entities/text.js +324 -181
  31. package/core/lib/boundingBox.js +73 -13
  32. package/core/lib/canvas.js +256 -65
  33. package/core/lib/clipboard.js +198 -0
  34. package/core/lib/colour.js +144 -0
  35. package/core/lib/colours.js +367 -301
  36. package/core/lib/commandLine.js +16 -28
  37. package/core/lib/commandManager.js +115 -58
  38. package/core/lib/constants.js +23 -0
  39. package/core/lib/dxf/dxf.js +249 -34
  40. package/core/lib/dxf/dxfFile.js +23 -38
  41. package/core/lib/dxf/dxfIterator.js +17 -5
  42. package/core/lib/dxf/dxfRead.js +23 -7
  43. package/core/lib/dxf/dxfWrite.js +39 -129
  44. package/core/lib/dxf/handleManager.js +97 -0
  45. package/core/lib/dxf/sections/blocks.js +16 -2
  46. package/core/lib/dxf/sections/entities.js +17 -3
  47. package/core/lib/dxf/sections/header.js +10 -1
  48. package/core/lib/dxf/sections/objects.js +36 -0
  49. package/core/lib/dxf/sections/section.js +43 -5
  50. package/core/lib/dxf/sections/tables.js +17 -3
  51. package/core/lib/entityManager.js +178 -0
  52. package/core/lib/fileio.js +14 -4
  53. package/core/lib/headers.js +229 -0
  54. package/core/lib/input.js +46 -0
  55. package/core/lib/inputManager.js +199 -194
  56. package/core/lib/intersect.js +341 -468
  57. package/core/lib/logging.js +25 -1
  58. package/core/lib/matrix.js +6 -8
  59. package/core/lib/mouse.js +24 -79
  60. package/core/lib/patterns.js +690 -0
  61. package/core/lib/promptOptions.js +133 -0
  62. package/core/lib/scene.js +71 -174
  63. package/core/lib/selectionManager.js +74 -48
  64. package/core/lib/selectionWindow.js +24 -10
  65. package/core/lib/settings.js +18 -11
  66. package/core/lib/snapping.js +129 -21
  67. package/core/lib/stateManager.js +288 -0
  68. package/core/lib/strings.js +57 -5
  69. package/core/lib/utils.js +95 -11
  70. package/core/objects/dictionary.js +43 -0
  71. package/core/objects/dictionaryManager.js +80 -0
  72. package/core/properties/dimType.js +117 -0
  73. package/core/properties/flags.js +66 -0
  74. package/core/properties/property.js +26 -0
  75. package/core/properties/propertyManager.js +65 -19
  76. package/core/tables/appId.js +31 -0
  77. package/core/tables/appIdManager.js +44 -0
  78. package/core/tables/block.js +323 -0
  79. package/core/tables/blockManager.js +48 -0
  80. package/core/tables/blockRecord.js +29 -0
  81. package/core/tables/blockRecordManager.js +37 -0
  82. package/core/tables/dimStyle.js +627 -0
  83. package/core/tables/dimStyleManager.js +187 -0
  84. package/core/tables/endblk.js +28 -0
  85. package/core/tables/layer.js +219 -0
  86. package/core/tables/layerManager.js +47 -0
  87. package/core/tables/ltype.js +85 -0
  88. package/core/tables/ltypeManager.js +65 -0
  89. package/core/tables/style.js +145 -0
  90. package/core/tables/styleManager.js +55 -0
  91. package/core/tables/styleManagerBase.js +110 -0
  92. package/core/tables/tableManagerBase.js +354 -0
  93. package/core/tables/ucs.js +28 -0
  94. package/core/tables/ucsManager.js +44 -0
  95. package/core/tables/view.js +28 -0
  96. package/core/tables/viewManager.js +44 -0
  97. package/core/tables/vport.js +70 -0
  98. package/core/tables/vportManager.js +65 -0
  99. package/core/tools/chamfer.js +343 -0
  100. package/core/tools/chamferFilletBase.js +162 -0
  101. package/core/tools/copy.js +68 -40
  102. package/core/tools/copybase.js +65 -0
  103. package/core/tools/copyclip.js +59 -0
  104. package/core/tools/cornerEntity.js +138 -0
  105. package/core/tools/cutclip.js +72 -0
  106. package/core/tools/distance.js +37 -9
  107. package/core/tools/erase.js +43 -13
  108. package/core/tools/explode.js +117 -0
  109. package/core/tools/extend.js +71 -32
  110. package/core/tools/fillet.js +336 -0
  111. package/core/tools/identify.js +33 -6
  112. package/core/tools/matchProp.js +106 -0
  113. package/core/tools/mirror.js +141 -0
  114. package/core/tools/move.js +65 -32
  115. package/core/tools/offset.js +321 -0
  116. package/core/tools/pan.js +54 -0
  117. package/core/tools/pasteclip.js +93 -0
  118. package/core/tools/purge.js +90 -0
  119. package/core/tools/rotate.js +105 -70
  120. package/core/tools/scale.js +166 -0
  121. package/core/tools/tool.js +14 -4
  122. package/core/tools/trim.js +71 -32
  123. package/core/tools/zoom.js +152 -0
  124. package/package.json +3 -305
  125. package/test/dimensions/alignedDimension.test.js +211 -0
  126. package/test/dimensions/angularDimension.test.js +243 -0
  127. package/test/dimensions/baseDimension.test.js +239 -0
  128. package/test/dimensions/diametricDimension.test.js +203 -0
  129. package/test/dimensions/dimension.test.js +102 -0
  130. package/test/dimensions/radialDimension.test.js +203 -0
  131. package/test/dimensions/rotatedDimension.test.js +224 -0
  132. package/test/entities/arc.test.js +463 -41
  133. package/test/entities/arctext.test.js +329 -0
  134. package/test/entities/basePolyline.test.js +459 -10
  135. package/test/entities/circle.test.js +312 -7
  136. package/test/entities/entity.test.js +28 -9
  137. package/test/entities/hatch.test.js +606 -0
  138. package/test/entities/insert.test.js +90 -0
  139. package/test/entities/line.test.js +182 -6
  140. package/test/entities/point.test.js +375 -37
  141. package/test/entities/solid.test.js +156 -0
  142. package/test/entities/text.test.js +533 -7
  143. package/test/integration/exportIntegration.reference.dxf +1706 -0
  144. package/test/integration/exportIntegration.test.js +186 -0
  145. package/test/integration/importExportIntegration.test.js +126 -0
  146. package/test/integration/intersectionIntegration.test.js +880 -0
  147. package/test/integration/userInputIntegration.reference.dxf +1410 -0
  148. package/test/integration/userInputIntegration.test.js +269 -0
  149. package/test/lib/boundingBox.test.js +26 -2
  150. package/test/lib/canvas.test.js +613 -5
  151. package/test/lib/clipboard.test.js +70 -0
  152. package/test/lib/colour.test.js +114 -0
  153. package/test/lib/colours.test.js +63 -18
  154. package/test/lib/commandLine.test.js +159 -22
  155. package/test/lib/commandManager.test.js +4 -4
  156. package/test/lib/constants.test.js +9 -0
  157. package/test/lib/dxf/dxfFile.test.js +6 -2
  158. package/test/lib/dxf/dxfIterator.test.js +1 -1
  159. package/test/lib/dxf/dxfWrite.test.js +173 -0
  160. package/test/lib/dxf/handleManager.test.js +137 -0
  161. package/test/lib/dxf/sections/blocks.test.js +5 -5
  162. package/test/lib/dxf/sections/entities.test.js +4 -4
  163. package/test/lib/dxf/sections/header.test.js +18 -4
  164. package/test/lib/dxf/sections/objects.test.js +132 -0
  165. package/test/lib/dxf/sections/section.test.js +29 -29
  166. package/test/lib/dxf/sections/tables.test.js +3 -3
  167. package/test/lib/entityManager.test.js +163 -0
  168. package/test/lib/headers.test.js +233 -0
  169. package/test/lib/inputManager.test.js +1144 -5
  170. package/test/lib/intersect.test.js +454 -331
  171. package/test/lib/logging.test.js +1 -1
  172. package/test/lib/matrix.test.js +1 -1
  173. package/test/lib/mouse.test.js +327 -4
  174. package/test/lib/patterns.test.js +105 -0
  175. package/test/lib/promptOptions.test.js +179 -0
  176. package/test/lib/scene.test.js +16 -52
  177. package/test/lib/selectionManager.test.js +17 -5
  178. package/test/lib/settings.test.js +111 -5
  179. package/test/lib/snapping.test.js +142 -4
  180. package/test/lib/stateManager.test.js +155 -0
  181. package/test/lib/utils.test.js +81 -1
  182. package/test/objects/dictionary.test.js +81 -0
  183. package/test/objects/dictionaryManager.test.js +34 -0
  184. package/test/properties/dimType.test.js +62 -0
  185. package/test/properties/flags.test.js +164 -0
  186. package/test/properties/property.test.js +13 -0
  187. package/test/properties/propertyManager.test.js +10 -11
  188. package/test/tables/appId.test.js +46 -0
  189. package/test/tables/appIdManager.test.js +72 -0
  190. package/test/tables/block.test.js +157 -0
  191. package/test/tables/blockManager.test.js +52 -0
  192. package/test/tables/blockRecord.test.js +39 -0
  193. package/test/tables/blockRecordManager.test.js +60 -0
  194. package/test/tables/dimstyle.test.js +72 -0
  195. package/test/tables/endblk.test.js +30 -0
  196. package/test/tables/layer.test.js +95 -0
  197. package/test/tables/layerManager.test.js +172 -0
  198. package/test/{styles → tables}/ltype.test.js +7 -7
  199. package/test/{styles → tables}/ltypeManager.test.js +2 -2
  200. package/test/tables/style.test.js +44 -0
  201. package/test/tables/stylemanagerbase.test.js +220 -0
  202. package/test/tables/tableManagerBase.test.js +194 -0
  203. package/test/tables/ucs.test.js +37 -0
  204. package/test/tables/ucsManager.test.js +48 -0
  205. package/test/tables/view.test.js +37 -0
  206. package/test/tables/viewManager.test.js +48 -0
  207. package/test/tables/vport.test.js +117 -0
  208. package/test/tables/vportManager.test.js +54 -0
  209. package/test/test-helpers/test-helpers.js +66 -0
  210. package/test/tools/chamfer.test.js +970 -0
  211. package/test/tools/chamferFilletBase.test.js +446 -0
  212. package/test/tools/copy.test.js +22 -14
  213. package/test/tools/copybase.test.js +93 -0
  214. package/test/tools/copyclip.test.js +92 -0
  215. package/test/tools/cornerEntity.test.js +201 -0
  216. package/test/tools/cutclip.test.js +94 -0
  217. package/test/tools/distance.test.js +140 -18
  218. package/test/tools/erase.test.js +12 -12
  219. package/test/tools/explode.test.js +45 -0
  220. package/test/tools/extend.test.js +326 -44
  221. package/test/tools/fillet.test.js +831 -0
  222. package/test/tools/identify.test.js +115 -16
  223. package/test/tools/matchProp.test.js +84 -0
  224. package/test/tools/mirror.test.js +475 -0
  225. package/test/tools/move.test.js +75 -19
  226. package/test/tools/offset.test.js +748 -0
  227. package/test/tools/pan.test.js +72 -0
  228. package/test/tools/pasteclip.test.js +100 -0
  229. package/test/tools/purge.test.js +89 -0
  230. package/test/tools/rotate.test.js +89 -23
  231. package/test/tools/scale.test.js +529 -0
  232. package/test/tools/trim.test.js +419 -84
  233. package/test/tools/zoom.test.js +504 -0
  234. package/core/entities/alignedDimension.js +0 -279
  235. package/core/entities/angularDimension.js +0 -194
  236. package/core/entities/baseDimension.js +0 -227
  237. package/core/entities/block.js +0 -247
  238. package/core/entities/diametricDimension.js +0 -133
  239. package/core/entities/dimension.js +0 -202
  240. package/core/entities/radialDimension.js +0 -126
  241. package/core/styles/dimStyle.js +0 -719
  242. package/core/styles/dimStyleManager.js +0 -47
  243. package/core/styles/layer.js +0 -133
  244. package/core/styles/layerManager.js +0 -40
  245. package/core/styles/ltype.js +0 -86
  246. package/core/styles/ltypeManager.js +0 -50
  247. package/core/styles/style.js +0 -158
  248. package/core/styles/styleManager.js +0 -43
  249. package/core/styles/styleManagerBase.js +0 -299
  250. package/test/entities/block.test.js +0 -22
  251. package/test/styles/dimstyle.test.js +0 -19
  252. package/test/styles/layerManager.test.js +0 -150
  253. package/test/styles/style.test.js +0 -33
  254. package/test/styles/stylemanagerbase.test.js +0 -219
@@ -1,11 +1,134 @@
1
- import {BasePolyline} from '../../core/entities/basePolyline';
2
- import {Point} from '../../core/entities/point';
1
+ import { BasePolyline } from '../../core/entities/basePolyline.js';
2
+ import { Point } from '../../core/entities/point';
3
+ import { Polyline } from '../../core/entities/polyline.js';
4
+ import { Line } from '../../core/entities/line.js';
5
+ import { Arc } from '../../core/entities/arc.js';
6
+ import { Core } from '../../core/core/core.js';
7
+ import { DesignCore } from '../../core/designCore.js';
8
+ import { DXFFile } from '../../core/lib/dxf/dxfFile.js';
9
+ import { Flags } from '../../core/properties/flags.js';
10
+
11
+ import { File, withMockInput } from '../test-helpers/test-helpers.js';
12
+
13
+ // initialise core
14
+ new Core();
15
+
16
+ const inputScenarios = [
17
+ {
18
+ desc: 'two points and an angle',
19
+ inputs: [new Point(0, 0), new Point(10, 0)],
20
+ expectedPoints: 2,
21
+ },
22
+ {
23
+ desc: 'three points with arc segment',
24
+ inputs: [new Point(0, 0), new Point(10, 0), 'Arc', new Point(10, 10)],
25
+ expectedPoints: 3,
26
+ },
27
+ ];
28
+
29
+ test.each(inputScenarios)('Polyline.execute handles $desc', async (scenario) => {
30
+ const { inputs, expectedPoints } = scenario;
31
+
32
+ await withMockInput(DesignCore.Scene, inputs, async () => {
33
+ const polyline = new BasePolyline({});
34
+ await polyline.execute();
35
+
36
+ expect(polyline.points.length).toBe(expectedPoints);
37
+ expect(polyline.points[0]).toBe(inputs[0]);
38
+ expect(polyline.points[0].bulge).toBe(0);
39
+ expect(polyline.points[1]).toBe(inputs[1]);
40
+
41
+ // validate arc segment bulge and end point
42
+ if (inputs.length > 2) {
43
+ expect(polyline.points[1].bulge).toBeCloseTo(1);
44
+ expect(polyline.points[2]).toBe(inputs[3]);
45
+ }
46
+ }, { extraMethods: { actionCommand: () => {} } });
47
+ });
48
+
49
+ test('Test BasePolyline.getClosestSegment', () => {
50
+ // Polyline with a line and an arc segment
51
+ const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10)];
52
+ points[1].bulge = 1; // Arc from (10,0) to (10,10)
53
+ const polyline = new BasePolyline({ points });
54
+
55
+ // Closest to the line segment
56
+ const testPoint1 = new Point(5, 2);
57
+ const segment1 = polyline.getClosestSegment(testPoint1);
58
+ expect(segment1).toBeInstanceOf(Line);
59
+
60
+ // Closest to the arc segment
61
+ const testPoint2 = new Point(12, 5);
62
+ const segment2 = polyline.getClosestSegment(testPoint2);
63
+ expect(segment2).toBeInstanceOf(Arc);
64
+ });
65
+
66
+ // ─── closing segment (closed polylines) ──────────────────────────────────────
67
+
68
+ test('BasePolyline.getClosestSegmentIndex returns closing-segment index for closed polyline', () => {
69
+ // Square: P0=(0,0) P1=(10,0) P2=(10,10) P3=(0,10), closed flag set.
70
+ // Closing segment is P3→P0. A point near the left edge (0,5) is closest to that segment.
71
+ const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)];
72
+ const poly = new BasePolyline({ points });
73
+ poly.flags.setFlagValue(1);
74
+
75
+ const closestIdx = poly.getClosestSegmentIndex(new Point(0, 5));
76
+ // Closing segment index = points.length = 4
77
+ expect(closestIdx).toBe(4);
78
+ });
79
+
80
+ test('BasePolyline.getClosestSegment returns closing segment as a Line for closed polyline', () => {
81
+ const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)];
82
+ const poly = new BasePolyline({ points });
83
+ poly.flags.setFlagValue(1);
84
+
85
+ const seg = poly.getClosestSegment(new Point(0, 5));
86
+ expect(seg).toBeInstanceOf(Line);
87
+ // The segment spans P3=(0,10) → P0=(0,0)
88
+ expect(seg.points[0].x).toBeCloseTo(0);
89
+ expect(seg.points[0].y).toBeCloseTo(10);
90
+ expect(seg.points[1].x).toBeCloseTo(0);
91
+ expect(seg.points[1].y).toBeCloseTo(0);
92
+ });
93
+
94
+ test('BasePolyline.areConsecutiveSegments returns false for non-adjacent segments on a closed polyline', () => {
95
+ // 4-point closed poly: segments 1 and 3 are NOT adjacent (bug: old code returned true).
96
+ const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)];
97
+ const poly = new BasePolyline({ points });
98
+ poly.flags.setFlagValue(1);
99
+
100
+ expect(poly.areConsecutiveSegments(1, 3)).toBe(false);
101
+ });
102
+
103
+ test('BasePolyline.areConsecutiveSegments returns true for closing segment and its neighbours', () => {
104
+ // 4-point closed poly, N=4 (closing segment index).
105
+ // Seg N-1=3 ends at P3, closing seg starts at P3 → consecutive (diff=1).
106
+ // Closing seg ends at P0, seg 1 starts at P0 → consecutive (wrap case).
107
+ const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)];
108
+ const poly = new BasePolyline({ points });
109
+ poly.flags.setFlagValue(1);
110
+
111
+ expect(poly.areConsecutiveSegments(3, 4)).toBe(true); // diff = 1
112
+ expect(poly.areConsecutiveSegments(4, 1)).toBe(true); // wrap
113
+ expect(poly.areConsecutiveSegments(1, 4)).toBe(true); // wrap (order reversed)
114
+ });
115
+
116
+ test('BasePolyline.closestPointOnSegment handles closing-segment index correctly', () => {
117
+ // 4-point square, closing segment = P3=(0,10)→P0=(0,0). Index = 4.
118
+ // Closest point on that segment to (0, 5) should be (0, 5).
119
+ const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)];
120
+ const poly = new BasePolyline({ points });
121
+ poly.flags.setFlagValue(1);
122
+
123
+ const closest = poly.closestPointOnSegment(new Point(0, 5), 4);
124
+ expect(closest.x).toBeCloseTo(0);
125
+ expect(closest.y).toBeCloseTo(5);
126
+ });
3
127
 
4
128
 
5
129
  test('Test BasePolyline.closestPoint', () => {
6
- const points = [new Point(100, 100), new Point(200, 100), new Point(200, 50)];
7
- points[1].bulge = -1;
8
- const polyline = new BasePolyline({points: points});
130
+ const points = [new Point(100, 100), new Point(200, 100, -1), new Point(200, 50)];
131
+ const polyline = new BasePolyline({ points: points });
9
132
  // line segment
10
133
  const point1 = new Point(150, 85);
11
134
  const closest1 = polyline.closestPoint(point1);
@@ -22,13 +145,13 @@ test('Test BasePolyline.closestPoint', () => {
22
145
  });
23
146
 
24
147
  test('Test BasePolyline.boundingBox', () => {
25
- let polyline = new BasePolyline({points: [new Point(101, 102), new Point(201, 202)]});
148
+ let polyline = new BasePolyline({ points: [new Point(101, 102), new Point(201, 202)] });
26
149
  expect(polyline.boundingBox().xMin).toBeCloseTo(101);
27
150
  expect(polyline.boundingBox().xMax).toBeCloseTo(201);
28
151
  expect(polyline.boundingBox().yMin).toBeCloseTo(102);
29
152
  expect(polyline.boundingBox().yMax).toBeCloseTo(202);
30
153
 
31
- polyline = new BasePolyline({points: [new Point(101, 102), new Point(-201, 202)]});
154
+ polyline = new BasePolyline({ points: [new Point(101, 102), new Point(-201, 202)] });
32
155
  expect(polyline.boundingBox().xMin).toBeCloseTo(-201);
33
156
  expect(polyline.boundingBox().xMax).toBeCloseTo(101);
34
157
  expect(polyline.boundingBox().yMin).toBeCloseTo(102);
@@ -38,7 +161,7 @@ test('Test BasePolyline.boundingBox', () => {
38
161
  let points = [new Point(101, 102), new Point(200, 102), new Point(200, 0)];
39
162
  // set the bulge
40
163
  points[1].bulge = -1;
41
- polyline = new BasePolyline({points: points});
164
+ polyline = new BasePolyline({ points: points });
42
165
  expect(polyline.boundingBox().xMin).toBeCloseTo(101);
43
166
  expect(polyline.boundingBox().xMax).toBeCloseTo(251);
44
167
  expect(polyline.boundingBox().yMin).toBeCloseTo(0);
@@ -47,7 +170,7 @@ test('Test BasePolyline.boundingBox', () => {
47
170
  points = [new Point(101, 102), new Point(200, 102), new Point(200, 0)];
48
171
  // set the bulge
49
172
  points[1].bulge = -1;
50
- polyline = new BasePolyline({points: points});
173
+ polyline = new BasePolyline({ points: points });
51
174
  expect(polyline.boundingBox().xMin).toBeCloseTo(101);
52
175
  expect(polyline.boundingBox().xMax).toBeCloseTo(251);
53
176
  expect(polyline.boundingBox().yMin).toBeCloseTo(0);
@@ -59,7 +182,7 @@ test('Test BasePolyline.getBulgeFromSegment', () => {
59
182
  // center: 100,50
60
183
  // radius: 50
61
184
  const points = [new Point(), new Point(100, 0)];
62
- const polyline = new BasePolyline({points: points});
185
+ const polyline = new BasePolyline({ points: points });
63
186
 
64
187
  // zero delta angle:
65
188
  // bulge: 0
@@ -177,3 +300,329 @@ test('Test BasePolyline.getBulgeFromSegment', () => {
177
300
  bulge = Math.tan(angle / 4);
178
301
  expect(polyline.getBulgeFromSegment(new Point(64.6447, -14.6447))).toBeCloseTo(bulge);
179
302
  });
303
+
304
+ test('Test Polyline.dxf', () => {
305
+ const points = [new Point(100, 100), new Point(200, 100), new Point(200, 50)];
306
+ points[1].bulge = -1;
307
+ const polyline = new Polyline({ handle: '1', points: points });
308
+ let file = new File();
309
+ polyline.dxf(file);
310
+ // console.log(file.contents);
311
+
312
+ const dxfString = `0
313
+ LWPOLYLINE
314
+ 5
315
+ 1
316
+ 100
317
+ AcDbEntity
318
+ 100
319
+ AcDbPolyline
320
+ 8
321
+ 0
322
+ 6
323
+ ByLayer
324
+ 39
325
+ 2
326
+ 90
327
+ 3
328
+ 70
329
+ 0
330
+ 10
331
+ 100
332
+ 20
333
+ 100
334
+ 42
335
+ 0
336
+ 10
337
+ 200
338
+ 20
339
+ 100
340
+ 42
341
+ -1
342
+ 10
343
+ 200
344
+ 20
345
+ 50
346
+ 42
347
+ 0
348
+ `;
349
+
350
+ expect(file.contents).toEqual(dxfString);
351
+
352
+ // create new entity from entity data to ensure all props are loaded
353
+ const newPolyline = new Polyline(polyline);
354
+ file = new File();
355
+ newPolyline.dxf(file);
356
+
357
+ expect(file.contents).toEqual(dxfString);
358
+ });
359
+
360
+ test('Test Polyline.dxf R12', () => {
361
+ const points = [new Point(100, 100), new Point(200, 100), new Point(200, 50)];
362
+ points[1].bulge = -1;
363
+ const polyline = new Polyline({ handle: '1', points: points });
364
+ const file = new DXFFile('R12');
365
+ polyline.dxf(file);
366
+
367
+ const dxfString = `0
368
+ POLYLINE
369
+ 8
370
+ 0
371
+ 6
372
+ ByLayer
373
+ 66
374
+ 1
375
+ 70
376
+ 0
377
+ 0
378
+ VERTEX
379
+ 8
380
+ 0
381
+ 10
382
+ 100
383
+ 20
384
+ 100
385
+ 30
386
+ 0.0
387
+ 42
388
+ 0
389
+ 0
390
+ VERTEX
391
+ 8
392
+ 0
393
+ 10
394
+ 200
395
+ 20
396
+ 100
397
+ 30
398
+ 0.0
399
+ 42
400
+ -1
401
+ 0
402
+ VERTEX
403
+ 8
404
+ 0
405
+ 10
406
+ 200
407
+ 20
408
+ 50
409
+ 30
410
+ 0.0
411
+ 42
412
+ 0
413
+ 0
414
+ SEQEND
415
+ 8
416
+ 0
417
+ `;
418
+
419
+ expect(file.contents).toEqual(dxfString);
420
+ });
421
+
422
+ test('Test BasePolyline.toPolylinePoints', () => {
423
+ const points = [new Point(100, 100), new Point(200, 100), new Point(200, 50)];
424
+ points[1].bulge = -1;
425
+ const polyline = new BasePolyline({ points: points });
426
+
427
+ const polylinePoints = polyline.toPolylinePoints();
428
+ expect(polylinePoints[0].x).toBe(100);
429
+ expect(polylinePoints[0].y).toBe(100);
430
+ expect(polylinePoints[0].bulge).toBe(0);
431
+
432
+ expect(polylinePoints[1].x).toBe(200);
433
+ expect(polylinePoints[1].y).toBe(100);
434
+ expect(polylinePoints[1].bulge).toBe(-1);
435
+
436
+ expect(polylinePoints[2].x).toBe(200);
437
+ expect(polylinePoints[2].y).toBe(50);
438
+ expect(polylinePoints[2].bulge).toBe(0);
439
+ });
440
+
441
+ test('Test BasePolyline.toPolylinePoints returns defensive copy for open polyline', () => {
442
+ const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10)];
443
+ points[1].bulge = -1;
444
+ const polyline = new BasePolyline({ points: points });
445
+
446
+ const result = polyline.toPolylinePoints();
447
+ // Mutate the returned array and one of the returned points
448
+ result.reverse();
449
+ result[0].bulge = 99;
450
+
451
+ // Original this.points must be unaffected
452
+ expect(polyline.points.length).toBe(3);
453
+ expect(polyline.points[0].x).toBe(0);
454
+ expect(polyline.points[1].bulge).toBe(-1);
455
+ });
456
+
457
+ test('Test BasePolyline.toPolylinePoints closed polyline appends closure point', () => {
458
+ // A triangle closed via the Close command (flag bit 1 set, no duplicate
459
+ // point stored in this.points).
460
+ const points = [new Point(0, 0), new Point(10, 0), new Point(5, 10)];
461
+ const flags = new Flags();
462
+ flags.addValue(1); // closed flag
463
+ const polyline = new BasePolyline({ points: points, flags: flags });
464
+
465
+ const polylinePoints = polyline.toPolylinePoints();
466
+ // Should have 4 points: the 3 vertices plus the closure point
467
+ expect(polylinePoints.length).toBe(4);
468
+ // Closure point matches the first point
469
+ expect(polylinePoints[3].x).toBe(polylinePoints[0].x);
470
+ expect(polylinePoints[3].y).toBe(polylinePoints[0].y);
471
+ // The original this.points array is not mutated
472
+ expect(polyline.points.length).toBe(3);
473
+ });
474
+
475
+ test('Polyline.execute handles Close option', async () => {
476
+ const pt1 = new Point(0, 0);
477
+ const pt2 = new Point(10, 0);
478
+ const pt3 = new Point(10, 10);
479
+
480
+ await withMockInput(DesignCore.Scene, [pt1, pt2, pt3, 'Close'], async () => {
481
+ const polyline = new Polyline({});
482
+ await polyline.execute();
483
+
484
+ expect(polyline.points.length).toBe(3);
485
+ expect(polyline.points[0]).toBe(pt1);
486
+ expect(polyline.points[1]).toBe(pt2);
487
+ expect(polyline.points[2]).toBe(pt3);
488
+ expect(polyline.flags.hasFlag(1)).toBe(true);
489
+ }, { extraMethods: { actionCommand: () => 0 } });
490
+ });
491
+
492
+ // ─── Polyline interactive options: Undo, Close, Arc, Line ───────────────
493
+
494
+ test('Polyline.execute supports Undo in line mode', async () => {
495
+ const inputs = [new Point(0, 0), new Point(10, 0), new Point(20, 0), 'Undo', new Point(10, 10), 'Close'];
496
+ await withMockInput(DesignCore.Scene, inputs, async () => {
497
+ const polyline = new BasePolyline({});
498
+ await polyline.execute();
499
+ expect(polyline.points.length).toBe(3);
500
+ expect(polyline.points[0]).toEqual(inputs[0]);
501
+ expect(polyline.points[1]).toEqual(inputs[1]);
502
+ expect(polyline.points[2]).toEqual(inputs[4]);
503
+ expect(polyline.flags.hasFlag(1)).toBe(true); // closed
504
+ }, { extraMethods: { actionCommand: () => {} } });
505
+ });
506
+
507
+ test('Polyline.execute supports Undo in arc mode', async () => {
508
+ const inputs = [new Point(0, 0), new Point(10, 0), 'Arc', new Point(10, 10), new Point(20, 20), 'Undo', 'Line', new Point(10, 20), 'Close'];
509
+ await withMockInput(DesignCore.Scene, inputs, async () => {
510
+ const polyline = new BasePolyline({});
511
+ await polyline.execute();
512
+ expect(polyline.points.length).toBe(4);
513
+ expect(polyline.points[0]).toEqual(inputs[0]);
514
+ expect(polyline.points[1]).toEqual(inputs[1]);
515
+ expect(polyline.points[2]).toEqual(inputs[3]);
516
+ expect(polyline.points[3]).toEqual(inputs[7]);
517
+ expect(polyline.flags.hasFlag(1)).toBe(true); // closed
518
+ // After undo in arc mode
519
+ expect(polyline.points[2].bulge).toBeCloseTo(0);
520
+ expect(polyline.points[3].bulge).toBeCloseTo(0);
521
+ // console.log(polyline.points);
522
+ }, { extraMethods: { actionCommand: () => {} } });
523
+ });
524
+
525
+ test('Polyline.execute supports Arc/Line mode switching', async () => {
526
+ const inputs = [new Point(0, 0), new Point(10, 0), 'Arc', new Point(10, 10), 'Line', new Point(20, 10), 'Close'];
527
+ await withMockInput(DesignCore.Scene, inputs, async () => {
528
+ const polyline = new BasePolyline({});
529
+ await polyline.execute();
530
+ expect(polyline.points.length).toBe(4);
531
+ expect(polyline.points[0]).toEqual(inputs[0]);
532
+ expect(polyline.points[1]).toEqual(inputs[1]);
533
+ expect(polyline.points[2]).toEqual(inputs[3]);
534
+ expect(polyline.points[3]).toEqual(inputs[5]);
535
+ expect(polyline.flags.hasFlag(1)).toBe(true); // closed
536
+ // Arc segment bulge should be set, line segment bulge should be 0
537
+ expect(polyline.points[1].bulge).not.toBe(0);
538
+ expect(polyline.points[2].bulge).toBe(0);
539
+ }, { extraMethods: { actionCommand: () => {} } });
540
+ });
541
+
542
+ // ─── Auto-close when first and last point are the same ───────────────────────
543
+
544
+ test('BasePolyline constructor auto-closes when last point matches first', () => {
545
+ // Simulate a polyline loaded with a redundant closing point (e.g. from DXF data)
546
+ const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 0)];
547
+ const polyline = new BasePolyline({ points });
548
+
549
+ // Duplicate point should be removed and the closed flag set
550
+ expect(polyline.points.length).toBe(3);
551
+ expect(polyline.flags.hasFlag(1)).toBe(true);
552
+ });
553
+
554
+ test('BasePolyline constructor does not auto-close when fewer than 3 unique points remain after pop', () => {
555
+ // [A, B, A] — only 2 unique points after removing the duplicate; must not auto-close
556
+ const points = [new Point(0, 0), new Point(10, 0), new Point(0, 0)];
557
+ const polyline = new BasePolyline({ points });
558
+
559
+ expect(polyline.points.length).toBe(3);
560
+ expect(polyline.flags.hasFlag(1)).toBe(false);
561
+ });
562
+
563
+ test('BasePolyline constructor does not auto-close with only 2 identical points', () => {
564
+ // Only 2 unique points — not enough to form a closed polygon
565
+ const points = [new Point(0, 0), new Point(0, 0)];
566
+ const polyline = new BasePolyline({ points });
567
+
568
+ expect(polyline.points.length).toBe(2);
569
+ expect(polyline.flags.hasFlag(1)).toBe(false);
570
+ });
571
+
572
+ test('Polyline.execute auto-closes when start point is re-entered', async () => {
573
+ const pt1 = new Point(0, 0);
574
+ const pt2 = new Point(10, 0);
575
+ const pt3 = new Point(10, 10);
576
+ const pt4 = new Point(0, 0); // same as pt1
577
+
578
+ // Track how many times actionCommand is called with data that has the closed flag set
579
+ let autoClosedCount = 0;
580
+ const mockActionCommand = (entity) => {
581
+ // The constructor will have auto-closed the entity when pt4 == pt1
582
+ if (entity.points.length === 4 && entity.points.at(-1).isSame(pt1)) {
583
+ // simulate what the constructor does: a new entity created from this data would auto-close
584
+ const rebuilt = new BasePolyline({ points: [...entity.points] });
585
+ if (rebuilt.flags.hasFlag(1)) autoClosedCount++;
586
+ }
587
+ return 0;
588
+ };
589
+
590
+ await withMockInput(DesignCore.Scene, [pt1, pt2, pt3, pt4], async () => {
591
+ const polyline = new BasePolyline({});
592
+ await polyline.execute();
593
+ // execute does not exit early — the duplicate point is on the instance
594
+ expect(polyline.points.length).toBe(4);
595
+ // The scene entity (rebuilt in actionCommand) would be auto-closed
596
+ expect(autoClosedCount).toBe(1);
597
+ }, { extraMethods: { actionCommand: mockActionCommand } });
598
+ });
599
+
600
+ test('BasePolyline.fromPolylinePoints round-trip preserves open polyline', () => {
601
+ const poly = new BasePolyline({ points: [new Point(0, 0, 0.5), new Point(10, 0), new Point(10, 10)] });
602
+ const polyPts = poly.toPolylinePoints();
603
+ const rebuilt = new BasePolyline({});
604
+ rebuilt.fromPolylinePoints(polyPts);
605
+ expect(rebuilt.points.length).toBe(3);
606
+ expect(rebuilt.points[0].x).toBe(0);
607
+ expect(rebuilt.points[0].y).toBe(0);
608
+ expect(rebuilt.points[0].bulge).toBe(0.5);
609
+ expect(rebuilt.points[2].x).toBe(10);
610
+ expect(rebuilt.points[2].y).toBe(10);
611
+ });
612
+
613
+ test('BasePolyline.fromPolylinePoints round-trip strips closure point for closed polyline', () => {
614
+ const flags = new Flags();
615
+ flags.addValue(1);
616
+ const poly = new BasePolyline({ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)], flags: flags });
617
+ const polyPts = poly.toPolylinePoints();
618
+ // toPolylinePoints appends a closure copy of the first point
619
+ expect(polyPts.length).toBe(5);
620
+ const flags2 = new Flags();
621
+ flags2.addValue(1);
622
+ const rebuilt = new BasePolyline({ flags: flags2 });
623
+ rebuilt.fromPolylinePoints(polyPts);
624
+ expect(rebuilt.points.length).toBe(4);
625
+ expect(rebuilt.points[0].x).toBe(0);
626
+ expect(rebuilt.points[3].x).toBe(0);
627
+ expect(rebuilt.points[3].y).toBe(10);
628
+ });