@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
@@ -0,0 +1,44 @@
1
+ import { DXFFile } from '../lib/dxf/dxfFile.js';
2
+ import { TableManagerBase } from './tableManagerBase.js';
3
+ import { UCS } from './ucs.js';
4
+
5
+ /**
6
+ * UCSManager Class
7
+ * @extends TableManagerBase
8
+ */
9
+ export class UCSManager extends TableManagerBase {
10
+ /** Create a UCSManager */
11
+ constructor() {
12
+ super();
13
+
14
+ this.itemProperty = 'ucs';
15
+ }
16
+
17
+ /** Get the item class */
18
+ get itemClass() {
19
+ return UCS;
20
+ }
21
+
22
+ /** Add standard items */
23
+ addStandardItems() {
24
+ // No standard UCS items
25
+ }
26
+
27
+ /**
28
+ * Write the table to file in the dxf format
29
+ * @param {DXFFile} file
30
+ */
31
+ dxf(file) {
32
+ file.writeGroupCode('0', 'TABLE', DXFFile.Version.R2000);
33
+ file.writeGroupCode('2', 'UCS', DXFFile.Version.R2000);
34
+ file.writeGroupCode('5', this.handle, DXFFile.Version.R2000);
35
+ file.writeGroupCode('100', 'AcDbSymbolTable', DXFFile.Version.R2000);
36
+ file.writeGroupCode('70', this.itemCount(), DXFFile.Version.R2000);
37
+
38
+ for (let i = 0; i < this.itemCount(); i++) {
39
+ this.getItemByIndex(i).dxf(file);
40
+ }
41
+
42
+ file.writeGroupCode('0', 'ENDTAB', DXFFile.Version.R2000);
43
+ }
44
+ }
@@ -0,0 +1,28 @@
1
+ import { DXFFile } from '../lib/dxf/dxfFile.js';
2
+ import { Property } from '../properties/property.js';
3
+
4
+ /** View Class */
5
+ export class View {
6
+ /**
7
+ * Create a View
8
+ * @param {Object} data
9
+ */
10
+ constructor(data) {
11
+ // DXF Groupcode 5 - Handle
12
+ this.handle = Property.loadValue([data?.handle, data?.[5]]);
13
+ // DXF Groupcode 2 - Name
14
+ this.name = Property.loadValue([data?.name, data?.[2]], '');
15
+ }
16
+
17
+ /**
18
+ * Write the item to file in the dxf format
19
+ * @param {DXFFile} file
20
+ */
21
+ dxf(file) {
22
+ file.writeGroupCode('0', 'VIEW', DXFFile.Version.R2000);
23
+ file.writeGroupCode('5', this.handle, DXFFile.Version.R2000);
24
+ file.writeGroupCode('100', 'AcDbSymbolTableRecord', DXFFile.Version.R2000);
25
+ file.writeGroupCode('100', 'AcDbViewTableRecord', DXFFile.Version.R2000);
26
+ file.writeGroupCode('2', this.name, DXFFile.Version.R2000);
27
+ }
28
+ }
@@ -0,0 +1,44 @@
1
+ import { DXFFile } from '../lib/dxf/dxfFile.js';
2
+ import { TableManagerBase } from './tableManagerBase.js';
3
+ import { View } from './view.js';
4
+
5
+ /**
6
+ * ViewManager Class
7
+ * @extends TableManagerBase
8
+ */
9
+ export class ViewManager extends TableManagerBase {
10
+ /** Create a ViewManager */
11
+ constructor() {
12
+ super();
13
+
14
+ this.itemProperty = 'view';
15
+ }
16
+
17
+ /** Get the item class */
18
+ get itemClass() {
19
+ return View;
20
+ }
21
+
22
+ /** Add standard items */
23
+ addStandardItems() {
24
+ // No standard view items
25
+ }
26
+
27
+ /**
28
+ * Write the table to file in the dxf format
29
+ * @param {DXFFile} file
30
+ */
31
+ dxf(file) {
32
+ file.writeGroupCode('0', 'TABLE', DXFFile.Version.R2000);
33
+ file.writeGroupCode('2', 'VIEW', DXFFile.Version.R2000);
34
+ file.writeGroupCode('5', this.handle, DXFFile.Version.R2000);
35
+ file.writeGroupCode('100', 'AcDbSymbolTable', DXFFile.Version.R2000);
36
+ file.writeGroupCode('70', this.itemCount(), DXFFile.Version.R2000);
37
+
38
+ for (let i = 0; i < this.itemCount(); i++) {
39
+ this.getItemByIndex(i).dxf(file);
40
+ }
41
+
42
+ file.writeGroupCode('0', 'ENDTAB', DXFFile.Version.R2000);
43
+ }
44
+ }
@@ -0,0 +1,70 @@
1
+ import { DXFFile } from '../lib/dxf/dxfFile.js';
2
+ import { Property } from '../properties/property.js';
3
+
4
+ /** VPort Class */
5
+ export class VPort {
6
+ /**
7
+ * Create a VPort
8
+ * @param {Object} data
9
+ */
10
+ constructor(data) {
11
+ // DXF Groupcode 5 - Handle
12
+ this.handle = Property.loadValue([data?.handle, data?.[5]]);
13
+ // DXF Groupcode 2 - Name
14
+ this.name = Property.loadValue([data?.name, data?.[2]], '*ACTIVE');
15
+ // DXF Groupcode 12 - View center X
16
+ this.viewCenterX = Property.loadValue([data?.viewCenterX, data?.[12]], 0);
17
+ // DXF Groupcode 22 - View center Y
18
+ this.viewCenterY = Property.loadValue([data?.viewCenterY, data?.[22]], 0);
19
+ // DXF Groupcode 40 - VPort height
20
+ this.height = Property.loadValue([data?.height, data?.[40]], 0);
21
+ // DXF Groupcode 41 - VPort width/height ratio
22
+ this.ratio = Property.loadValue([data?.ratio, data?.[41]], 0);
23
+ }
24
+
25
+ /**
26
+ * Write the item to file in the dxf format
27
+ * @param {DXFFile} file
28
+ */
29
+ dxf(file) {
30
+ file.writeGroupCode('0', 'VPORT');
31
+ file.writeGroupCode('5', this.handle, DXFFile.Version.R2000);
32
+ file.writeGroupCode('100', 'AcDbSymbolTableRecord', DXFFile.Version.R2000);
33
+ file.writeGroupCode('100', 'AcDbViewportTableRecord', DXFFile.Version.R2000);
34
+ file.writeGroupCode('2', this.name);
35
+ file.writeGroupCode('70', '0'); // vport flags
36
+ file.writeGroupCode('10', '0.0'); // lower left corner x pos
37
+ file.writeGroupCode('20', '0.0'); // lower left corner y pos
38
+ file.writeGroupCode('11', '1.0'); // upper right corner x pos
39
+ file.writeGroupCode('21', '1.0'); // upper right corner y pos
40
+ file.writeGroupCode('12', this.viewCenterX); // view centre x pos
41
+ file.writeGroupCode('22', this.viewCenterY); // view centre y pos
42
+ file.writeGroupCode('13', '0.0'); // snap base point x
43
+ file.writeGroupCode('23', '0.0'); // snap base point y
44
+ file.writeGroupCode('14', '10.0'); // snap spacing x
45
+ file.writeGroupCode('24', '10.0'); // snap spacing y
46
+ file.writeGroupCode('15', '10.0'); // grid spacing x
47
+ file.writeGroupCode('25', '10.0'); // grid spacing y
48
+ file.writeGroupCode('16', '0.0'); // view direction (x) from target point
49
+ file.writeGroupCode('26', '0.0'); // view direction (y) from target point
50
+ file.writeGroupCode('36', '1.0'); // view direction (z) from target point
51
+ file.writeGroupCode('17', '0.0'); // view target point x
52
+ file.writeGroupCode('27', '0.0'); // view target point y
53
+ file.writeGroupCode('37', '0.0'); // view target point z
54
+ file.writeGroupCode('40', this.height); // VPort Height
55
+ file.writeGroupCode('41', this.ratio); // Vport height/width ratio
56
+ file.writeGroupCode('42', '50.0'); // Lens Length
57
+ file.writeGroupCode('43', '0.0'); // Front Clipping Plane
58
+ file.writeGroupCode('44', '0.0'); // Back Clipping Plane
59
+ file.writeGroupCode('50', '0.0'); // Snap Rotation Angle
60
+ file.writeGroupCode('51', '0.0'); // View Twist Angle
61
+ file.writeGroupCode('71', '0.0'); // Viewmode (System variable)
62
+ file.writeGroupCode('72', '1000'); // Circle sides
63
+ file.writeGroupCode('73', '1'); // fast zoom setting
64
+ file.writeGroupCode('74', '3'); // UCSICON Setting
65
+ file.writeGroupCode('75', '0'); // snap on/off
66
+ file.writeGroupCode('76', '1'); // grid on/off
67
+ file.writeGroupCode('77', '0'); // snap style
68
+ file.writeGroupCode('78', '0'); // snap isopair
69
+ }
70
+ }
@@ -0,0 +1,65 @@
1
+ import { DXFFile } from '../lib/dxf/dxfFile.js';
2
+ import { TableManagerBase } from './tableManagerBase.js';
3
+ import { VPort } from './vport.js';
4
+
5
+ import { DesignCore } from '../designCore.js';
6
+
7
+ /**
8
+ * VPortManager Class
9
+ * @extends TableManagerBase
10
+ */
11
+ export class VPortManager extends TableManagerBase {
12
+ /** Create a VPortManager */
13
+ constructor() {
14
+ super();
15
+
16
+ this.itemProperty = 'vport';
17
+ }
18
+
19
+ /** Get the item class */
20
+ get itemClass() {
21
+ return VPort;
22
+ }
23
+
24
+ /** Add standard items */
25
+ addStandardItems() {
26
+ this.addItem({ name: '*ACTIVE' });
27
+ }
28
+
29
+ /**
30
+ * Update the active vport from the scene
31
+ */
32
+ updateFromScene() {
33
+ const extents = DesignCore.Scene.boundingBox();
34
+ const vport = this.getItemByName('*ACTIVE');
35
+
36
+ if (extents && vport) {
37
+ const width = extents.xLength;
38
+ const height = extents.yLength;
39
+ vport.viewCenterX = extents.xMin + width / 2;
40
+ vport.viewCenterY = extents.yMin + height / 2;
41
+ vport.height = height;
42
+ vport.ratio = height === 0 ? 1 : width / height;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Write the table to file in the dxf format
48
+ * @param {DXFFile} file
49
+ */
50
+ dxf(file) {
51
+ this.updateFromScene();
52
+
53
+ file.writeGroupCode('0', 'TABLE');
54
+ file.writeGroupCode('2', 'VPORT');
55
+ file.writeGroupCode('5', this.handle, DXFFile.Version.R2000);
56
+ file.writeGroupCode('100', 'AcDbSymbolTable', DXFFile.Version.R2000);
57
+ file.writeGroupCode('70', this.itemCount());
58
+
59
+ for (let i = 0; i < this.itemCount(); i++) {
60
+ this.getItemByIndex(i).dxf(file);
61
+ }
62
+
63
+ file.writeGroupCode('0', 'ENDTAB');
64
+ }
65
+ }
@@ -0,0 +1,343 @@
1
+ import { Strings } from '../lib/strings.js';
2
+ import { ChamferFilletBase } from './chamferFilletBase.js';
3
+ import { Intersection } from '../lib/intersect.js';
4
+ import { Input, PromptOptions } from '../lib/inputManager.js';
5
+ import { Logging } from '../lib/logging.js';
6
+ import { Point } from '../entities/point.js';
7
+ import { Line } from '../entities/line.js';
8
+ import { BasePolyline } from '../entities/basePolyline.js';
9
+ import { AddState, RemoveState, UpdateState } from '../lib/stateManager.js';
10
+
11
+ import { DesignCore } from '../designCore.js';
12
+
13
+ /**
14
+ * Chamfer Command Class
15
+ * @extends ChamferFilletBase
16
+ */
17
+ export class Chamfer extends ChamferFilletBase {
18
+ /**
19
+ * Register the command
20
+ * @return {Object}
21
+ * command = name of the command
22
+ * shortcut = shortcut for the command
23
+ * type = type to group command in toolbars (omitted if not shown)
24
+ */
25
+ static register() {
26
+ const command = { command: 'Chamfer', shortcut: 'CHA' };
27
+ return command;
28
+ }
29
+
30
+ /**
31
+ * Execute method
32
+ * executes the workflow, requesting input required to perform the command
33
+ */
34
+ async execute() {
35
+ try {
36
+ this.#notifySettings();
37
+
38
+ while (true) {
39
+ const op1 = new PromptOptions(Strings.Input.SELECT, [Input.Type.SINGLESELECTION], ['Distance', 'Angle', 'Trim', 'Method']);
40
+ const input1 = await DesignCore.Scene.inputManager.requestInput(op1);
41
+ if (input1 === undefined) return;
42
+
43
+ if (Input.getType(input1) === Input.Type.STRING) {
44
+ if (input1 === 'Distance') {
45
+ const dop1 = new PromptOptions('Specify first chamfer distance', [Input.Type.NUMBER]);
46
+ const d1 = await DesignCore.Scene.inputManager.requestInput(dop1);
47
+ if (d1 === undefined) return;
48
+ if (d1 < 0) {
49
+ DesignCore.Core.notify(`${this.type} - ${Strings.Error.INVALIDNUMBER}`);
50
+ } else {
51
+ DesignCore.Scene.headers.chamferDistanceA = d1;
52
+ const dop2 = new PromptOptions('Specify second chamfer distance', [Input.Type.NUMBER]);
53
+ const d2 = await DesignCore.Scene.inputManager.requestInput(dop2);
54
+ if (d2 === undefined) return;
55
+ if (d2 < 0) {
56
+ DesignCore.Core.notify(`${this.type} - ${Strings.Error.INVALIDNUMBER}`);
57
+ } else {
58
+ DesignCore.Scene.headers.chamferDistanceB = d2;
59
+ }
60
+ }
61
+ this.#notifySettings();
62
+ } else if (input1 === 'Angle') {
63
+ const lop = new PromptOptions('Specify chamfer length on first line', [Input.Type.NUMBER]);
64
+ const len = await DesignCore.Scene.inputManager.requestInput(lop);
65
+ if (len === undefined) return;
66
+ if (len < 0) {
67
+ DesignCore.Core.notify(`${this.type} - ${Strings.Error.INVALIDNUMBER}`);
68
+ } else {
69
+ DesignCore.Scene.headers.chamferLength = len;
70
+ const aop = new PromptOptions(Strings.Input.ANGLE, [Input.Type.NUMBER]);
71
+ const ang = await DesignCore.Scene.inputManager.requestInput(aop);
72
+ if (ang === undefined) return;
73
+ if (ang <= 0 || ang >= 180) {
74
+ DesignCore.Core.notify(`${this.type} - ${Strings.Error.INVALIDNUMBER}`);
75
+ } else {
76
+ DesignCore.Scene.headers.chamferAngle = ang;
77
+ }
78
+ }
79
+ this.#notifySettings();
80
+ } else if (input1 === 'Trim') {
81
+ const top = new PromptOptions(Strings.Input.OPTION, [], ['Trim', 'No trim']);
82
+ const trimInput = await DesignCore.Scene.inputManager.requestInput(top);
83
+ if (trimInput === undefined) return;
84
+ DesignCore.Scene.headers.trimMode = (trimInput === 'Trim');
85
+ this.#notifySettings();
86
+ } else if (input1 === 'Method') {
87
+ const mop = new PromptOptions(Strings.Input.OPTION, [], ['Distance', 'Angle']);
88
+ const methodInput = await DesignCore.Scene.inputManager.requestInput(mop);
89
+ if (methodInput === undefined) return;
90
+ DesignCore.Scene.headers.chamferMode = (methodInput === 'Angle');
91
+ this.#notifySettings();
92
+ }
93
+ continue;
94
+ }
95
+
96
+ // input1 is a selection — validate and store first entity
97
+ const firstEntity = DesignCore.Scene.entities.get(input1.selectedItemIndex);
98
+ DesignCore.Scene.selectionManager.removeLastSelection();
99
+ if (!(firstEntity instanceof Line) && !(firstEntity instanceof BasePolyline)) {
100
+ DesignCore.Core.notify(`${firstEntity.type} - ${Strings.Message.NOCHAMFER}`);
101
+ continue;
102
+ }
103
+ if (!this.firstPick.setPick(firstEntity, input1.selectedPoint, `${Strings.Strings.ARC} - ${Strings.Message.NOCHAMFER}`)) continue;
104
+
105
+ // Prompt for second object
106
+ const op2 = new PromptOptions(Strings.Input.SELECT, [Input.Type.SINGLESELECTION]);
107
+ while (true) {
108
+ const input2 = await DesignCore.Scene.inputManager.requestInput(op2);
109
+ if (input2 === undefined) return;
110
+
111
+ const candidate = DesignCore.Scene.entities.get(input2.selectedItemIndex);
112
+ DesignCore.Scene.selectionManager.removeLastSelection();
113
+ if (!(candidate instanceof Line) && !(candidate instanceof BasePolyline)) {
114
+ DesignCore.Core.notify(`${this.type} - ${candidate.type} ${Strings.Message.NOCHAMFER}`);
115
+ continue;
116
+ }
117
+ if (!this.secondPick.setPick(candidate, input2.selectedPoint, `${Strings.Strings.ARC} - ${Strings.Message.NOCHAMFER}`)) continue;
118
+ // If both selections are the same polyline, segments must be consecutive
119
+ // or they must be the open-end segments of an open polyline.
120
+ if (candidate === firstEntity && firstEntity instanceof BasePolyline) {
121
+ const isConsecutive = firstEntity.areConsecutiveSegments(this.firstPick.segmentIndex, this.secondPick.segmentIndex);
122
+ const lastIdx = firstEntity.points.length - 1;
123
+ const isOpenEnds = !firstEntity.flags.hasFlag(1) &&
124
+ ((this.firstPick.segmentIndex === 1 && this.secondPick.segmentIndex === lastIdx) ||
125
+ (this.secondPick.segmentIndex === 1 && this.firstPick.segmentIndex === lastIdx));
126
+ if (!isConsecutive && !isOpenEnds) {
127
+ DesignCore.Core.notify(`${this.type} - ${Strings.Message.NONCONSECUTIVESEGMENTS}`);
128
+ continue;
129
+ }
130
+ }
131
+ break;
132
+ }
133
+
134
+ // Apply chamfer then reset
135
+ DesignCore.Scene.inputManager.executeCommand();
136
+ return;
137
+ }
138
+ } catch (err) {
139
+ Logging.instance.error(`${this.type} - ${err}`);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Preview the command during execution
145
+ */
146
+ preview() {
147
+ // No preview
148
+ }
149
+
150
+ /**
151
+ * Perform the chamfer
152
+ */
153
+ action() {
154
+ if (!this.firstPick.entity || !this.secondPick.entity) return;
155
+ if (!this.resolveCornerGeometry(Strings.Message.NOCHAMFER)) return;
156
+
157
+ const trimMode = DesignCore.Scene.headers.trimMode;
158
+ const chamferMode = DesignCore.Scene.headers.chamferMode;
159
+ const distA = DesignCore.Scene.headers.chamferDistanceA;
160
+
161
+ // distA = distB = 0: trim/extend both lines to the sharp intersection with no chamfer line
162
+ if (!chamferMode && distA === 0 && DesignCore.Scene.headers.chamferDistanceB === 0) {
163
+ if (trimMode) {
164
+ const stateChanges = this.applySharpTrim();
165
+ DesignCore.Scene.commit(stateChanges);
166
+ }
167
+ return;
168
+ }
169
+
170
+ // Compute the two chamfer endpoints on each line
171
+ const firstUnit = this.firstPick.clickUnit(this.intersectionPoint);
172
+ const secondUnit = this.secondPick.clickUnit(this.intersectionPoint);
173
+ let firstChamferPoint;
174
+ let secondChamferPoint;
175
+
176
+ if (!chamferMode) {
177
+ // Distance method: measure distA along line1 and distB along line2 from intersection
178
+ const distB = DesignCore.Scene.headers.chamferDistanceB;
179
+ firstChamferPoint = this.intersectionPoint.add(firstUnit.scale(distA));
180
+ secondChamferPoint = this.intersectionPoint.add(secondUnit.scale(distB));
181
+ } else {
182
+ // Angle method: measure chamferLength along line1, then project using chamferAngle to find
183
+ // where the chamfer line meets line2.
184
+ // chamferAngle is stored in degrees (as entered by the user).
185
+ const chamferLength = DesignCore.Scene.headers.chamferLength;
186
+ const alpha = DesignCore.Scene.headers.chamferAngle * (Math.PI / 180);
187
+
188
+ if (alpha <= 0 || alpha >= Math.PI) {
189
+ DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.INVALIDNUMBER}`);
190
+ return;
191
+ }
192
+
193
+ firstChamferPoint = this.intersectionPoint.add(firstUnit.scale(chamferLength));
194
+
195
+ // Rotate firstUnit by ±(π - alpha) to get the chamfer direction from
196
+ // firstChamferPoint. Choose the rotation that points toward line2 (positive
197
+ // dot product with secondUnit).
198
+ const rotAngle = Math.PI - alpha;
199
+ const origin = new Point(0, 0);
200
+ const candidate1 = firstUnit.rotate(origin, rotAngle);
201
+ const chamferDir = candidate1.dot(secondUnit) >= 0 ? candidate1 : firstUnit.rotate(origin, -rotAngle);
202
+
203
+ // Intersect chamfer ray from firstChamferPoint with the infinite line2
204
+ const chamferResult = Intersection.intersectSegmentSegment(
205
+ firstChamferPoint, firstChamferPoint.add(chamferDir),
206
+ this.secondPick.lineStart, this.secondPick.lineEnd,
207
+ true, true,
208
+ );
209
+
210
+ const chamferStatus = chamferResult.status;
211
+ if (chamferStatus === Intersection.Status.PARALLEL ||
212
+ chamferStatus === Intersection.Status.OVERLAPPING ||
213
+ chamferStatus === Intersection.Status.COINCIDENT) {
214
+ DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.PARALLELLINES}`);
215
+ return;
216
+ }
217
+
218
+ secondChamferPoint = chamferResult.points[0] || null;
219
+ if (!secondChamferPoint) {
220
+ // Chamfer direction is parallel to line2 — no intersection
221
+ DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.PARALLELLINES}`);
222
+ return;
223
+ }
224
+ }
225
+
226
+ // Verify both chamfer endpoints lie on the correct side of the intersection and
227
+ // not beyond the kept endpoints. Uses the same dot-product approach as Fillet so
228
+ // that segments are treated as infinite lines (extensions are allowed).
229
+ if (trimMode) {
230
+ const firstKeptDir = this.firstPick.lineKeptEnd(this.intersectionPoint).subtract(this.intersectionPoint);
231
+ const secondKeptDir = this.secondPick.lineKeptEnd(this.intersectionPoint).subtract(this.intersectionPoint);
232
+ const firstChamferDot = firstChamferPoint.subtract(this.intersectionPoint).dot(firstKeptDir);
233
+ const secondChamferDot = secondChamferPoint.subtract(this.intersectionPoint).dot(secondKeptDir);
234
+ if (firstChamferDot < 0 || firstChamferDot > firstKeptDir.dot(firstKeptDir) ||
235
+ secondChamferDot < 0 || secondChamferDot > secondKeptDir.dot(secondKeptDir)) {
236
+ DesignCore.Core.notify(`${this.type} - ${Strings.Error.MAXVALUE}`);
237
+ return;
238
+ }
239
+ }
240
+
241
+ const chamferLine = DesignCore.CommandManager.createNew('Line', {
242
+ points: [firstChamferPoint, secondChamferPoint],
243
+ });
244
+
245
+ const firstIsPolyline = this.firstPick.entity instanceof BasePolyline;
246
+ const secondIsPolyline = this.secondPick.entity instanceof BasePolyline;
247
+
248
+ // The standalone chamfer Line entity is added when no polyline trimming is involved.
249
+ // For polyline trim cases the chamfer segment is encoded as a straight polyline segment,
250
+ // except for the open-ends case where the chamfer line fills the gap between the two
251
+ // open ends and is added separately there.
252
+ const stateChanges = (!trimMode || (!firstIsPolyline && !secondIsPolyline)) ?
253
+ [new AddState(chamferLine)] :
254
+ [];
255
+
256
+ if (trimMode) {
257
+ if (!firstIsPolyline && !secondIsPolyline) {
258
+ // Line + Line: trim both lines
259
+ stateChanges.push(new UpdateState(this.firstPick.entity, { points: [this.firstPick.lineKeptEnd(this.intersectionPoint), firstChamferPoint] }));
260
+ stateChanges.push(new UpdateState(this.secondPick.entity, { points: [this.secondPick.lineKeptEnd(this.intersectionPoint), secondChamferPoint] }));
261
+ } else if (firstIsPolyline && secondIsPolyline && this.firstPick.entity === this.secondPick.entity) {
262
+ const closeSegIdx = this.firstPick.entity.points.length;
263
+ const lastIdx = closeSegIdx - 1;
264
+ const segDiff = Math.abs(this.firstPick.segmentIndex - this.secondPick.segmentIndex);
265
+ const isOpenEnds = !this.firstPick.entity.flags.hasFlag(1) && segDiff !== 1 && (
266
+ (this.firstPick.segmentIndex === 1 && this.secondPick.segmentIndex === lastIdx) ||
267
+ (this.firstPick.segmentIndex === lastIdx && this.secondPick.segmentIndex === 1)
268
+ );
269
+ const newPoints = this.firstPick.entity.points.map((p) => p.clone());
270
+ if (isOpenEnds) {
271
+ // Open ends: trim both endpoints to their chamfer points; chamfer line fills the gap.
272
+ stateChanges.push(new AddState(chamferLine));
273
+ if (this.firstPick.segmentIndex === 1) {
274
+ newPoints[0] = firstChamferPoint.clone();
275
+ newPoints[lastIdx] = secondChamferPoint.clone();
276
+ } else {
277
+ newPoints[0] = secondChamferPoint.clone();
278
+ newPoints[lastIdx] = firstChamferPoint.clone();
279
+ }
280
+ } else {
281
+ // Consecutive segments: replace the shared corner vertex with the two chamfer points
282
+ // as a straight polyline segment — no separate chamfer Line entity needed.
283
+ const seg1 = this.firstPick.segmentIndex;
284
+ const seg2 = this.secondPick.segmentIndex;
285
+ const isClosingWrap = (seg1 === closeSegIdx && seg2 === 1) || (seg2 === closeSegIdx && seg1 === 1);
286
+ const isFirstLower = seg1 < seg2;
287
+ const cornerIdx = isClosingWrap ? 0 : Math.min(seg1, seg2);
288
+ const lowerChamfer = (isClosingWrap ? seg1 === closeSegIdx : !isFirstLower) ? secondChamferPoint : firstChamferPoint;
289
+ const upperChamfer = (isClosingWrap ? seg1 === closeSegIdx : !isFirstLower) ? firstChamferPoint : secondChamferPoint;
290
+ newPoints.splice(cornerIdx, 1, lowerChamfer.clone(), upperChamfer.clone());
291
+ }
292
+ stateChanges.push(new UpdateState(this.firstPick.entity, { points: newPoints }));
293
+ } else {
294
+ // Line + Polyline or Polyline + Line: consume the line into the polyline.
295
+ const [poly, line] = firstIsPolyline ? [this.firstPick, this.secondPick] : [this.secondPick, this.firstPick];
296
+ const polyChamferPoint = poly === this.firstPick ? firstChamferPoint : secondChamferPoint;
297
+ const lineChamferPoint = line === this.firstPick ? firstChamferPoint : secondChamferPoint;
298
+ const polySegIdx = poly.segmentIndex;
299
+ const keepStart = poly.keepStart(this.intersectionPoint);
300
+ let newPoints;
301
+ if (keepStart) {
302
+ // Keep start portion: points[0..polySegIdx-1], then chamfer, then line end
303
+ newPoints = [
304
+ ...poly.entity.points.slice(0, polySegIdx).map((p) => p.clone()),
305
+ polyChamferPoint.clone(),
306
+ lineChamferPoint.clone(),
307
+ line.lineKeptEnd(this.intersectionPoint).clone(),
308
+ ];
309
+ } else {
310
+ // Keep end portion: line end, then chamfer, then points[polySegIdx..end]
311
+ newPoints = [
312
+ line.lineKeptEnd(this.intersectionPoint).clone(),
313
+ lineChamferPoint.clone(),
314
+ polyChamferPoint.clone(),
315
+ ...poly.entity.points.slice(polySegIdx).map((p) => p.clone()),
316
+ ];
317
+ }
318
+
319
+ stateChanges.push(new RemoveState(line.entity));
320
+ stateChanges.push(new UpdateState(poly.entity, { points: newPoints }));
321
+ }
322
+ }
323
+
324
+ DesignCore.Scene.commit(stateChanges);
325
+ }
326
+
327
+ /**
328
+ * Display the current chamfer settings to the command line
329
+ */
330
+ #notifySettings() {
331
+ const trimMode = DesignCore.Scene.headers.trimMode;
332
+ const chamferMode = DesignCore.Scene.headers.chamferMode;
333
+ if (chamferMode) {
334
+ const length = DesignCore.Scene.headers.chamferLength;
335
+ const angle = DesignCore.Scene.headers.chamferAngle;
336
+ DesignCore.Core.notify(`Current settings: Mode = ${trimMode ? 'TRIM' : 'NOTRIM'}, Method = ANGLE, Length = ${length}, Angle = ${angle}`);
337
+ } else {
338
+ const distA = DesignCore.Scene.headers.chamferDistanceA;
339
+ const distB = DesignCore.Scene.headers.chamferDistanceB;
340
+ DesignCore.Core.notify(`Current settings: Mode = ${trimMode ? 'TRIM' : 'NOTRIM'}, Method = DIST, Dist1 = ${distA}, Dist2 = ${distB}`);
341
+ }
342
+ }
343
+ }