meerk40t 0.9.3001__py2.py3-none-any.whl → 0.9.7020__py2.py3-none-any.whl

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 (446) hide show
  1. meerk40t/__init__.py +1 -1
  2. meerk40t/balormk/balor_params.py +167 -167
  3. meerk40t/balormk/clone_loader.py +457 -457
  4. meerk40t/balormk/controller.py +1566 -1512
  5. meerk40t/balormk/cylindermod.py +64 -0
  6. meerk40t/balormk/device.py +966 -1959
  7. meerk40t/balormk/driver.py +778 -591
  8. meerk40t/balormk/galvo_commands.py +1194 -0
  9. meerk40t/balormk/gui/balorconfig.py +237 -111
  10. meerk40t/balormk/gui/balorcontroller.py +191 -184
  11. meerk40t/balormk/gui/baloroperationproperties.py +116 -115
  12. meerk40t/balormk/gui/corscene.py +845 -0
  13. meerk40t/balormk/gui/gui.py +179 -147
  14. meerk40t/balormk/livelightjob.py +466 -382
  15. meerk40t/balormk/mock_connection.py +131 -109
  16. meerk40t/balormk/plugin.py +133 -135
  17. meerk40t/balormk/usb_connection.py +306 -301
  18. meerk40t/camera/__init__.py +1 -1
  19. meerk40t/camera/camera.py +514 -397
  20. meerk40t/camera/gui/camerapanel.py +1241 -1095
  21. meerk40t/camera/gui/gui.py +58 -58
  22. meerk40t/camera/plugin.py +441 -399
  23. meerk40t/ch341/__init__.py +27 -27
  24. meerk40t/ch341/ch341device.py +628 -628
  25. meerk40t/ch341/libusb.py +595 -589
  26. meerk40t/ch341/mock.py +171 -171
  27. meerk40t/ch341/windriver.py +157 -157
  28. meerk40t/constants.py +13 -0
  29. meerk40t/core/__init__.py +1 -1
  30. meerk40t/core/bindalias.py +550 -539
  31. meerk40t/core/core.py +47 -47
  32. meerk40t/core/cutcode/cubiccut.py +73 -73
  33. meerk40t/core/cutcode/cutcode.py +315 -312
  34. meerk40t/core/cutcode/cutgroup.py +141 -137
  35. meerk40t/core/cutcode/cutobject.py +192 -185
  36. meerk40t/core/cutcode/dwellcut.py +37 -37
  37. meerk40t/core/cutcode/gotocut.py +29 -29
  38. meerk40t/core/cutcode/homecut.py +29 -29
  39. meerk40t/core/cutcode/inputcut.py +34 -34
  40. meerk40t/core/cutcode/linecut.py +33 -33
  41. meerk40t/core/cutcode/outputcut.py +34 -34
  42. meerk40t/core/cutcode/plotcut.py +335 -335
  43. meerk40t/core/cutcode/quadcut.py +61 -61
  44. meerk40t/core/cutcode/rastercut.py +168 -148
  45. meerk40t/core/cutcode/waitcut.py +34 -34
  46. meerk40t/core/cutplan.py +1843 -1316
  47. meerk40t/core/drivers.py +330 -329
  48. meerk40t/core/elements/align.py +801 -669
  49. meerk40t/core/elements/branches.py +1858 -1507
  50. meerk40t/core/elements/clipboard.py +229 -219
  51. meerk40t/core/elements/element_treeops.py +4595 -2837
  52. meerk40t/core/elements/element_types.py +125 -105
  53. meerk40t/core/elements/elements.py +4315 -3617
  54. meerk40t/core/elements/files.py +117 -64
  55. meerk40t/core/elements/geometry.py +473 -224
  56. meerk40t/core/elements/grid.py +467 -316
  57. meerk40t/core/elements/materials.py +158 -94
  58. meerk40t/core/elements/notes.py +50 -38
  59. meerk40t/core/elements/offset_clpr.py +934 -912
  60. meerk40t/core/elements/offset_mk.py +963 -955
  61. meerk40t/core/elements/penbox.py +339 -267
  62. meerk40t/core/elements/placements.py +300 -83
  63. meerk40t/core/elements/render.py +785 -687
  64. meerk40t/core/elements/shapes.py +2618 -2092
  65. meerk40t/core/elements/testcases.py +105 -0
  66. meerk40t/core/elements/trace.py +651 -563
  67. meerk40t/core/elements/tree_commands.py +415 -409
  68. meerk40t/core/elements/undo_redo.py +116 -58
  69. meerk40t/core/elements/wordlist.py +319 -200
  70. meerk40t/core/exceptions.py +9 -9
  71. meerk40t/core/laserjob.py +220 -220
  72. meerk40t/core/logging.py +63 -63
  73. meerk40t/core/node/blobnode.py +83 -86
  74. meerk40t/core/node/bootstrap.py +105 -103
  75. meerk40t/core/node/branch_elems.py +40 -31
  76. meerk40t/core/node/branch_ops.py +45 -38
  77. meerk40t/core/node/branch_regmark.py +48 -41
  78. meerk40t/core/node/cutnode.py +29 -32
  79. meerk40t/core/node/effect_hatch.py +375 -257
  80. meerk40t/core/node/effect_warp.py +398 -0
  81. meerk40t/core/node/effect_wobble.py +441 -309
  82. meerk40t/core/node/elem_ellipse.py +404 -309
  83. meerk40t/core/node/elem_image.py +1082 -801
  84. meerk40t/core/node/elem_line.py +358 -292
  85. meerk40t/core/node/elem_path.py +259 -201
  86. meerk40t/core/node/elem_point.py +129 -102
  87. meerk40t/core/node/elem_polyline.py +310 -246
  88. meerk40t/core/node/elem_rect.py +376 -286
  89. meerk40t/core/node/elem_text.py +445 -418
  90. meerk40t/core/node/filenode.py +59 -40
  91. meerk40t/core/node/groupnode.py +138 -74
  92. meerk40t/core/node/image_processed.py +777 -766
  93. meerk40t/core/node/image_raster.py +156 -113
  94. meerk40t/core/node/layernode.py +31 -31
  95. meerk40t/core/node/mixins.py +135 -107
  96. meerk40t/core/node/node.py +1427 -1304
  97. meerk40t/core/node/nutils.py +117 -114
  98. meerk40t/core/node/op_cut.py +463 -335
  99. meerk40t/core/node/op_dots.py +296 -251
  100. meerk40t/core/node/op_engrave.py +414 -311
  101. meerk40t/core/node/op_image.py +755 -369
  102. meerk40t/core/node/op_raster.py +787 -522
  103. meerk40t/core/node/place_current.py +37 -40
  104. meerk40t/core/node/place_point.py +329 -126
  105. meerk40t/core/node/refnode.py +58 -47
  106. meerk40t/core/node/rootnode.py +225 -219
  107. meerk40t/core/node/util_console.py +48 -48
  108. meerk40t/core/node/util_goto.py +84 -65
  109. meerk40t/core/node/util_home.py +61 -61
  110. meerk40t/core/node/util_input.py +102 -102
  111. meerk40t/core/node/util_output.py +102 -102
  112. meerk40t/core/node/util_wait.py +65 -65
  113. meerk40t/core/parameters.py +709 -707
  114. meerk40t/core/planner.py +875 -785
  115. meerk40t/core/plotplanner.py +656 -652
  116. meerk40t/core/space.py +120 -113
  117. meerk40t/core/spoolers.py +706 -705
  118. meerk40t/core/svg_io.py +1836 -1549
  119. meerk40t/core/treeop.py +534 -445
  120. meerk40t/core/undos.py +278 -124
  121. meerk40t/core/units.py +784 -680
  122. meerk40t/core/view.py +393 -322
  123. meerk40t/core/webhelp.py +62 -62
  124. meerk40t/core/wordlist.py +513 -504
  125. meerk40t/cylinder/cylinder.py +247 -0
  126. meerk40t/cylinder/gui/cylindersettings.py +41 -0
  127. meerk40t/cylinder/gui/gui.py +24 -0
  128. meerk40t/device/__init__.py +1 -1
  129. meerk40t/device/basedevice.py +322 -123
  130. meerk40t/device/devicechoices.py +50 -0
  131. meerk40t/device/dummydevice.py +163 -128
  132. meerk40t/device/gui/defaultactions.py +618 -602
  133. meerk40t/device/gui/effectspanel.py +114 -0
  134. meerk40t/device/gui/formatterpanel.py +253 -290
  135. meerk40t/device/gui/warningpanel.py +337 -260
  136. meerk40t/device/mixins.py +13 -13
  137. meerk40t/dxf/__init__.py +1 -1
  138. meerk40t/dxf/dxf_io.py +766 -554
  139. meerk40t/dxf/plugin.py +47 -35
  140. meerk40t/external_plugins.py +79 -79
  141. meerk40t/external_plugins_build.py +28 -28
  142. meerk40t/extra/cag.py +112 -116
  143. meerk40t/extra/coolant.py +403 -0
  144. meerk40t/extra/encode_detect.py +204 -0
  145. meerk40t/extra/ezd.py +1165 -1165
  146. meerk40t/extra/hershey.py +834 -340
  147. meerk40t/extra/imageactions.py +322 -316
  148. meerk40t/extra/inkscape.py +628 -622
  149. meerk40t/extra/lbrn.py +424 -424
  150. meerk40t/extra/outerworld.py +283 -0
  151. meerk40t/extra/param_functions.py +1542 -1556
  152. meerk40t/extra/potrace.py +257 -253
  153. meerk40t/extra/serial_exchange.py +118 -0
  154. meerk40t/extra/updater.py +602 -453
  155. meerk40t/extra/vectrace.py +147 -146
  156. meerk40t/extra/winsleep.py +83 -83
  157. meerk40t/extra/xcs_reader.py +597 -0
  158. meerk40t/fill/fills.py +781 -335
  159. meerk40t/fill/patternfill.py +1061 -1061
  160. meerk40t/fill/patterns.py +614 -567
  161. meerk40t/grbl/control.py +87 -87
  162. meerk40t/grbl/controller.py +990 -903
  163. meerk40t/grbl/device.py +1084 -768
  164. meerk40t/grbl/driver.py +989 -771
  165. meerk40t/grbl/emulator.py +532 -497
  166. meerk40t/grbl/gcodejob.py +783 -767
  167. meerk40t/grbl/gui/grblconfiguration.py +373 -298
  168. meerk40t/grbl/gui/grblcontroller.py +485 -271
  169. meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
  170. meerk40t/grbl/gui/grbloperationconfig.py +105 -0
  171. meerk40t/grbl/gui/gui.py +147 -116
  172. meerk40t/grbl/interpreter.py +44 -44
  173. meerk40t/grbl/loader.py +22 -22
  174. meerk40t/grbl/mock_connection.py +56 -56
  175. meerk40t/grbl/plugin.py +294 -264
  176. meerk40t/grbl/serial_connection.py +93 -88
  177. meerk40t/grbl/tcp_connection.py +81 -79
  178. meerk40t/grbl/ws_connection.py +112 -0
  179. meerk40t/gui/__init__.py +1 -1
  180. meerk40t/gui/about.py +2042 -296
  181. meerk40t/gui/alignment.py +1644 -1608
  182. meerk40t/gui/autoexec.py +199 -0
  183. meerk40t/gui/basicops.py +791 -670
  184. meerk40t/gui/bufferview.py +77 -71
  185. meerk40t/gui/busy.py +232 -133
  186. meerk40t/gui/choicepropertypanel.py +1662 -1469
  187. meerk40t/gui/consolepanel.py +706 -542
  188. meerk40t/gui/devicepanel.py +687 -581
  189. meerk40t/gui/dialogoptions.py +110 -107
  190. meerk40t/gui/executejob.py +316 -306
  191. meerk40t/gui/fonts.py +90 -90
  192. meerk40t/gui/functionwrapper.py +252 -0
  193. meerk40t/gui/gui_mixins.py +729 -0
  194. meerk40t/gui/guicolors.py +205 -182
  195. meerk40t/gui/help_assets/help_assets.py +218 -201
  196. meerk40t/gui/helper.py +154 -0
  197. meerk40t/gui/hersheymanager.py +1440 -846
  198. meerk40t/gui/icons.py +3422 -2747
  199. meerk40t/gui/imagesplitter.py +555 -508
  200. meerk40t/gui/keymap.py +354 -344
  201. meerk40t/gui/laserpanel.py +897 -806
  202. meerk40t/gui/laserrender.py +1470 -1232
  203. meerk40t/gui/lasertoolpanel.py +805 -793
  204. meerk40t/gui/magnetoptions.py +436 -0
  205. meerk40t/gui/materialmanager.py +2944 -0
  206. meerk40t/gui/materialtest.py +1722 -1694
  207. meerk40t/gui/mkdebug.py +646 -359
  208. meerk40t/gui/mwindow.py +163 -140
  209. meerk40t/gui/navigationpanels.py +2605 -2467
  210. meerk40t/gui/notes.py +143 -142
  211. meerk40t/gui/opassignment.py +414 -410
  212. meerk40t/gui/operation_info.py +310 -299
  213. meerk40t/gui/plugin.py +500 -328
  214. meerk40t/gui/position.py +714 -669
  215. meerk40t/gui/preferences.py +901 -650
  216. meerk40t/gui/propertypanels/attributes.py +1461 -1131
  217. meerk40t/gui/propertypanels/blobproperty.py +117 -114
  218. meerk40t/gui/propertypanels/consoleproperty.py +83 -80
  219. meerk40t/gui/propertypanels/gotoproperty.py +77 -0
  220. meerk40t/gui/propertypanels/groupproperties.py +223 -217
  221. meerk40t/gui/propertypanels/hatchproperty.py +489 -469
  222. meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
  223. meerk40t/gui/propertypanels/inputproperty.py +59 -58
  224. meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
  225. meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
  226. meerk40t/gui/propertypanels/outputproperty.py +59 -58
  227. meerk40t/gui/propertypanels/pathproperty.py +389 -380
  228. meerk40t/gui/propertypanels/placementproperty.py +1214 -383
  229. meerk40t/gui/propertypanels/pointproperty.py +140 -136
  230. meerk40t/gui/propertypanels/propertywindow.py +313 -181
  231. meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
  232. meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
  233. meerk40t/gui/propertypanels/textproperty.py +770 -755
  234. meerk40t/gui/propertypanels/waitproperty.py +56 -55
  235. meerk40t/gui/propertypanels/warpproperty.py +121 -0
  236. meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
  237. meerk40t/gui/ribbon.py +2471 -2210
  238. meerk40t/gui/scene/scene.py +1100 -1051
  239. meerk40t/gui/scene/sceneconst.py +22 -22
  240. meerk40t/gui/scene/scenepanel.py +439 -349
  241. meerk40t/gui/scene/scenespacewidget.py +365 -365
  242. meerk40t/gui/scene/widget.py +518 -505
  243. meerk40t/gui/scenewidgets/affinemover.py +215 -215
  244. meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
  245. meerk40t/gui/scenewidgets/bedwidget.py +120 -97
  246. meerk40t/gui/scenewidgets/elementswidget.py +137 -107
  247. meerk40t/gui/scenewidgets/gridwidget.py +785 -745
  248. meerk40t/gui/scenewidgets/guidewidget.py +765 -765
  249. meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
  250. meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
  251. meerk40t/gui/scenewidgets/nodeselector.py +28 -28
  252. meerk40t/gui/scenewidgets/rectselectwidget.py +592 -346
  253. meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
  254. meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
  255. meerk40t/gui/scenewidgets/selectionwidget.py +2958 -2756
  256. meerk40t/gui/simpleui.py +362 -333
  257. meerk40t/gui/simulation.py +2451 -2094
  258. meerk40t/gui/snapoptions.py +208 -203
  259. meerk40t/gui/spoolerpanel.py +1227 -1180
  260. meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
  261. meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
  262. meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
  263. meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
  264. meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
  265. meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
  266. meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
  267. meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
  268. meerk40t/gui/themes.py +200 -78
  269. meerk40t/gui/tips.py +590 -0
  270. meerk40t/gui/toolwidgets/circlebrush.py +35 -35
  271. meerk40t/gui/toolwidgets/toolcircle.py +248 -242
  272. meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
  273. meerk40t/gui/toolwidgets/tooldraw.py +97 -90
  274. meerk40t/gui/toolwidgets/toolellipse.py +219 -212
  275. meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
  276. meerk40t/gui/toolwidgets/toolline.py +39 -144
  277. meerk40t/gui/toolwidgets/toollinetext.py +79 -236
  278. meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
  279. meerk40t/gui/toolwidgets/toolmeasure.py +163 -216
  280. meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
  281. meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
  282. meerk40t/gui/toolwidgets/toolparameter.py +754 -668
  283. meerk40t/gui/toolwidgets/toolplacement.py +108 -108
  284. meerk40t/gui/toolwidgets/toolpoint.py +68 -59
  285. meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
  286. meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
  287. meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
  288. meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
  289. meerk40t/gui/toolwidgets/toolrect.py +211 -207
  290. meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
  291. meerk40t/gui/toolwidgets/toolribbon.py +598 -113
  292. meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
  293. meerk40t/gui/toolwidgets/tooltext.py +98 -89
  294. meerk40t/gui/toolwidgets/toolvector.py +213 -204
  295. meerk40t/gui/toolwidgets/toolwidget.py +39 -39
  296. meerk40t/gui/usbconnect.py +98 -91
  297. meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
  298. meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
  299. meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
  300. meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
  301. meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
  302. meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
  303. meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
  304. meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
  305. meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
  306. meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
  307. meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
  308. meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
  309. meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
  310. meerk40t/gui/wordlisteditor.py +985 -931
  311. meerk40t/gui/wxmeerk40t.py +1447 -1169
  312. meerk40t/gui/wxmmain.py +5644 -4112
  313. meerk40t/gui/wxmribbon.py +1591 -1076
  314. meerk40t/gui/wxmscene.py +1631 -1453
  315. meerk40t/gui/wxmtree.py +2416 -2089
  316. meerk40t/gui/wxutils.py +1769 -1099
  317. meerk40t/gui/zmatrix.py +102 -102
  318. meerk40t/image/__init__.py +1 -1
  319. meerk40t/image/dither.py +429 -0
  320. meerk40t/image/imagetools.py +2793 -2269
  321. meerk40t/internal_plugins.py +150 -130
  322. meerk40t/kernel/__init__.py +63 -12
  323. meerk40t/kernel/channel.py +259 -212
  324. meerk40t/kernel/context.py +538 -538
  325. meerk40t/kernel/exceptions.py +41 -41
  326. meerk40t/kernel/functions.py +463 -414
  327. meerk40t/kernel/jobs.py +100 -100
  328. meerk40t/kernel/kernel.py +3828 -3571
  329. meerk40t/kernel/lifecycles.py +71 -71
  330. meerk40t/kernel/module.py +49 -49
  331. meerk40t/kernel/service.py +147 -147
  332. meerk40t/kernel/settings.py +383 -343
  333. meerk40t/lihuiyu/controller.py +883 -876
  334. meerk40t/lihuiyu/device.py +1181 -1069
  335. meerk40t/lihuiyu/driver.py +1466 -1372
  336. meerk40t/lihuiyu/gui/gui.py +127 -106
  337. meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
  338. meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
  339. meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
  340. meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
  341. meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
  342. meerk40t/lihuiyu/interpreter.py +53 -53
  343. meerk40t/lihuiyu/laserspeed.py +450 -450
  344. meerk40t/lihuiyu/loader.py +90 -90
  345. meerk40t/lihuiyu/parser.py +404 -404
  346. meerk40t/lihuiyu/plugin.py +101 -102
  347. meerk40t/lihuiyu/tcp_connection.py +111 -109
  348. meerk40t/main.py +231 -165
  349. meerk40t/moshi/builder.py +788 -781
  350. meerk40t/moshi/controller.py +505 -499
  351. meerk40t/moshi/device.py +495 -442
  352. meerk40t/moshi/driver.py +862 -696
  353. meerk40t/moshi/gui/gui.py +78 -76
  354. meerk40t/moshi/gui/moshicontrollergui.py +538 -522
  355. meerk40t/moshi/gui/moshidrivergui.py +87 -75
  356. meerk40t/moshi/plugin.py +43 -43
  357. meerk40t/network/console_server.py +140 -57
  358. meerk40t/network/kernelserver.py +10 -9
  359. meerk40t/network/tcp_server.py +142 -140
  360. meerk40t/network/udp_server.py +103 -77
  361. meerk40t/network/web_server.py +404 -0
  362. meerk40t/newly/controller.py +1158 -1144
  363. meerk40t/newly/device.py +874 -732
  364. meerk40t/newly/driver.py +540 -412
  365. meerk40t/newly/gui/gui.py +219 -188
  366. meerk40t/newly/gui/newlyconfig.py +116 -101
  367. meerk40t/newly/gui/newlycontroller.py +193 -186
  368. meerk40t/newly/gui/operationproperties.py +51 -51
  369. meerk40t/newly/mock_connection.py +82 -82
  370. meerk40t/newly/newly_params.py +56 -56
  371. meerk40t/newly/plugin.py +1214 -1246
  372. meerk40t/newly/usb_connection.py +322 -322
  373. meerk40t/rotary/gui/gui.py +52 -46
  374. meerk40t/rotary/gui/rotarysettings.py +240 -232
  375. meerk40t/rotary/rotary.py +202 -98
  376. meerk40t/ruida/control.py +291 -91
  377. meerk40t/ruida/controller.py +138 -1088
  378. meerk40t/ruida/device.py +676 -231
  379. meerk40t/ruida/driver.py +534 -472
  380. meerk40t/ruida/emulator.py +1494 -1491
  381. meerk40t/ruida/exceptions.py +4 -4
  382. meerk40t/ruida/gui/gui.py +71 -76
  383. meerk40t/ruida/gui/ruidaconfig.py +239 -72
  384. meerk40t/ruida/gui/ruidacontroller.py +187 -184
  385. meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
  386. meerk40t/ruida/loader.py +54 -52
  387. meerk40t/ruida/mock_connection.py +57 -109
  388. meerk40t/ruida/plugin.py +124 -87
  389. meerk40t/ruida/rdjob.py +2084 -945
  390. meerk40t/ruida/serial_connection.py +116 -0
  391. meerk40t/ruida/tcp_connection.py +146 -0
  392. meerk40t/ruida/udp_connection.py +73 -0
  393. meerk40t/svgelements.py +9671 -9669
  394. meerk40t/tools/driver_to_path.py +584 -579
  395. meerk40t/tools/geomstr.py +5583 -4680
  396. meerk40t/tools/jhfparser.py +357 -292
  397. meerk40t/tools/kerftest.py +904 -890
  398. meerk40t/tools/livinghinges.py +1168 -1033
  399. meerk40t/tools/pathtools.py +987 -949
  400. meerk40t/tools/pmatrix.py +234 -0
  401. meerk40t/tools/pointfinder.py +942 -942
  402. meerk40t/tools/polybool.py +941 -940
  403. meerk40t/tools/rasterplotter.py +1660 -547
  404. meerk40t/tools/shxparser.py +1047 -901
  405. meerk40t/tools/ttfparser.py +726 -446
  406. meerk40t/tools/zinglplotter.py +595 -593
  407. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/LICENSE +21 -21
  408. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/METADATA +150 -139
  409. meerk40t-0.9.7020.dist-info/RECORD +446 -0
  410. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/WHEEL +1 -1
  411. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/top_level.txt +0 -1
  412. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/zip-safe +1 -1
  413. meerk40t/balormk/elementlightjob.py +0 -159
  414. meerk40t-0.9.3001.dist-info/RECORD +0 -437
  415. test/bootstrap.py +0 -63
  416. test/test_cli.py +0 -12
  417. test/test_core_cutcode.py +0 -418
  418. test/test_core_elements.py +0 -144
  419. test/test_core_plotplanner.py +0 -397
  420. test/test_core_viewports.py +0 -312
  421. test/test_drivers_grbl.py +0 -108
  422. test/test_drivers_lihuiyu.py +0 -443
  423. test/test_drivers_newly.py +0 -113
  424. test/test_element_degenerate_points.py +0 -43
  425. test/test_elements_classify.py +0 -97
  426. test/test_elements_penbox.py +0 -22
  427. test/test_file_svg.py +0 -176
  428. test/test_fill.py +0 -155
  429. test/test_geomstr.py +0 -1523
  430. test/test_geomstr_nodes.py +0 -18
  431. test/test_imagetools_actualize.py +0 -306
  432. test/test_imagetools_wizard.py +0 -258
  433. test/test_kernel.py +0 -200
  434. test/test_laser_speeds.py +0 -3303
  435. test/test_length.py +0 -57
  436. test/test_lifecycle.py +0 -66
  437. test/test_operations.py +0 -251
  438. test/test_operations_hatch.py +0 -57
  439. test/test_ruida.py +0 -19
  440. test/test_spooler.py +0 -22
  441. test/test_tools_rasterplotter.py +0 -29
  442. test/test_wobble.py +0 -133
  443. test/test_zingl.py +0 -124
  444. {test → meerk40t/cylinder}/__init__.py +0 -0
  445. /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
  446. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/entry_points.txt +0 -0
meerk40t/dxf/dxf_io.py CHANGED
@@ -1,554 +1,766 @@
1
- import os.path
2
-
3
- import ezdxf
4
- from ezdxf import units
5
-
6
- from ..core.exceptions import BadFileError
7
- from ..core.units import UNITS_PER_INCH, UNITS_PER_MM
8
-
9
- try:
10
- # ezdxf <= 0.6.14
11
- from ezdxf.tools.rgb import DXF_DEFAULT_COLORS, int2rgb
12
- except ImportError:
13
- # ezdxf > 0.6.14
14
- from ezdxf import int2rgb
15
- from ezdxf.colors import DXF_DEFAULT_COLORS
16
-
17
- from ezdxf.units import decode
18
-
19
- from ..svgelements import (
20
- SVG_ATTR_VECTOR_EFFECT,
21
- SVG_VALUE_NON_SCALING_STROKE,
22
- Angle,
23
- Arc,
24
- Circle,
25
- Color,
26
- Ellipse,
27
- Matrix,
28
- Move,
29
- Path,
30
- Polygon,
31
- Polyline,
32
- Viewbox,
33
- )
34
-
35
-
36
- class DxfLoader:
37
- @staticmethod
38
- def load_types():
39
- yield "Drawing Exchange Format", ("dxf",), "image/vnd.dxf"
40
-
41
- @staticmethod
42
- def load(kernel, elements_service, pathname, **kwargs):
43
- """
44
- Load dxf content. Requires ezdxf which tends to also require Python 3.6 or greater.
45
-
46
- Dxf data has an origin point located in the lower left corner. +y -> top
47
- """
48
- try:
49
- dxf = ezdxf.readfile(pathname)
50
- except ezdxf.DXFError:
51
- try:
52
- # dxf is low quality. Attempt recovery.
53
- from ezdxf import recover
54
-
55
- dxf, auditor = recover.readfile(pathname)
56
- except ezdxf.DXFStructureError as e:
57
- # Recovery failed, return the BadFileError.
58
- raise BadFileError(str(e)) from e
59
-
60
- unit = dxf.header.get("$INSUNITS")
61
-
62
- if unit is not None and unit != 0:
63
- du = units.DrawingUnits(UNITS_PER_INCH, unit="in")
64
- scale = du.factor(decode(unit))
65
- else:
66
- scale = UNITS_PER_MM
67
-
68
- dxf_processor = DXFProcessor(elements_service, dxf=dxf, scale=scale)
69
- dxf_processor.process(dxf.entities, pathname)
70
- return True
71
-
72
-
73
- class DXFProcessor:
74
- def __init__(self, elements_modifier, dxf, scale=1.0):
75
- self.elements = elements_modifier
76
- self.dxf = dxf
77
- self.scale = scale
78
- self.elements_list = list()
79
- self.reverse = False
80
- self.requires_classification = True
81
- self.pathname = None
82
-
83
- def process(self, entities, pathname):
84
- self.pathname = pathname
85
- # basename = os.path.basename(pathname)
86
- context_node = self.elements.get(type="branch elems")
87
- file_node = context_node.add(type="file", filepath=pathname)
88
- file_node.focus()
89
- for entity in entities:
90
- self.parse(entity, file_node, self.elements_list)
91
- dxf_center = self.elements.setting(bool, "dxf_center", True)
92
- if dxf_center:
93
- bbox = file_node.bounds
94
- if bbox is not None:
95
- view = self.elements.device.view
96
- bw = view.unit_width
97
- bh = view.unit_height
98
- bx = 0
99
- by = 0
100
- x = bbox[0]
101
- y = bbox[1]
102
- w = bbox[2] - bbox[0]
103
- h = bbox[3] - bbox[1]
104
- if w == 0:
105
- w = 1
106
- if h == 0:
107
- h = 1
108
- if w > view.unit_width or h > view.unit_height:
109
- # Cannot fit to bed. Scale.
110
- bb = Viewbox(f"{x} {y} {w} {h}", preserve_aspect_ratio="xMidyMid")
111
- matrix = bb.transform(Viewbox(bx, by, bw, bh))
112
- for node in self.elements_list:
113
- node.matrix *= matrix
114
- node.modified()
115
- elif x < bx or y < by or x + w > bw or y + h > bh:
116
- # Is outside the bed but sized correctly, center
117
- bcx = bw / 2.0
118
- bcy = bh / 2.0
119
- cx = (bbox[0] + bbox[2]) / 2.0
120
- cy = (bbox[1] + bbox[3]) / 2.0
121
- matrix = Matrix.translate(bcx - cx, bcy - cy)
122
- for node in self.elements_list:
123
- node.matrix *= matrix
124
- node.modified()
125
- # else, is within the bed dimensions correctly, change nothing.
126
- if self.elements.classify_new:
127
- self.elements.classify(self.elements_list)
128
- return True
129
-
130
- def check_for_attributes(self, node, entity):
131
- dxf = self.dxf
132
- if entity.rgb is not None:
133
- if isinstance(entity.rgb, tuple):
134
- node.stroke = Color(*entity.rgb)
135
- else:
136
- node.stroke = Color(entity.rgb)
137
- else:
138
- c = entity.dxf.color
139
- if c == 256: # Bylayer.
140
- if entity.dxf.layer in dxf.layers:
141
- layer = dxf.layers.get(entity.dxf.layer)
142
- c = layer.color
143
- try:
144
- if c == 7:
145
- color = Color(
146
- "black"
147
- ) # Color 7 is black on light backgrounds, light on black.
148
- else:
149
- color = Color(*int2rgb(DXF_DEFAULT_COLORS[c]))
150
- except Exception:
151
- color = Color("black")
152
- node.stroke = color
153
-
154
- def parse(self, entity, context_node, e_list):
155
- if hasattr(entity, "transform_to_wcs"):
156
- try:
157
- entity.transform_to_wcs(entity.ocs())
158
- except AttributeError as e:
159
- pass
160
- if entity.dxftype() == "CIRCLE":
161
- m = Matrix()
162
- m.post_scale(self.scale, -self.scale)
163
- m.post_translate_y(self.elements.device.view.unit_height)
164
- try:
165
- cx, cy = entity.dxf.center
166
- except ValueError:
167
- # 3d center.
168
- cx, cy, cz = entity.dxf.center
169
- node = context_node.add(
170
- cx=cx,
171
- cy=cy,
172
- rx=entity.dxf.radius,
173
- ry=entity.dxf.radius,
174
- matrix=m,
175
- stroke_scale=False,
176
- type="elem ellipse",
177
- )
178
- self.check_for_attributes(node, entity)
179
- e_list.append(node)
180
- return
181
- elif entity.dxftype() == "ARC":
182
- # TODO: Ellipse used to make circ.arc_angle path.
183
- circ = Ellipse(center=entity.dxf.center, r=entity.dxf.radius)
184
- start_angle = Angle.degrees(entity.dxf.start_angle)
185
- end_angle = Angle.degrees(entity.dxf.end_angle)
186
- if end_angle < start_angle:
187
- end_angle += Angle.turns(1)
188
- element = Path(circ.arc_angle(start_angle, end_angle))
189
- element.values[SVG_ATTR_VECTOR_EFFECT] = SVG_VALUE_NON_SCALING_STROKE
190
- element.transform.post_scale(self.scale, -self.scale)
191
- element.transform.post_translate_y(self.elements.device.view.unit_height)
192
- path = abs(Path(element))
193
- if len(path) != 0:
194
- if not isinstance(path[0], Move):
195
- path = Move(path.first_point) + path
196
- path.approximate_arcs_with_cubics()
197
- node = context_node.add(path=path, type="elem path")
198
- self.check_for_attributes(node, entity)
199
- e_list.append(node)
200
- return
201
- elif entity.dxftype() == "ELLIPSE":
202
- # TODO: needs more math, axis is vector, ratio is to minor.
203
- # major axis is vector
204
- # ratio is the ratio of major to minor.
205
- element = Ellipse(
206
- center=entity.dxf.center,
207
- start_point=entity.start_point,
208
- end_point=entity.end_point,
209
- start_angle=entity.dxf.start_param,
210
- end_angle=entity.dxf.end_param,
211
- )
212
- element.values[SVG_ATTR_VECTOR_EFFECT] = SVG_VALUE_NON_SCALING_STROKE
213
- element.transform.post_scale(self.scale, -self.scale)
214
- element.transform.post_translate_y(self.elements.device.view.unit_height)
215
- node = context_node.add(shape=element, type="elem ellipse")
216
- self.check_for_attributes(node, entity)
217
- e_list.append(node)
218
- return
219
- elif entity.dxftype() == "LINE":
220
- # https://ezdxf.readthedocs.io/en/stable/dxfentities/line.html
221
- m = Matrix()
222
- m.post_scale(self.scale, -self.scale)
223
- m.post_translate_y(self.elements.device.view.unit_height)
224
- node = context_node.add(
225
- x1=entity.dxf.start[0],
226
- y1=entity.dxf.start[1],
227
- x2=entity.dxf.end[0],
228
- y2=entity.dxf.end[1],
229
- stroke_scale=False,
230
- matrix=m,
231
- type="elem line",
232
- )
233
- self.check_for_attributes(node, entity)
234
- e_list.append(node)
235
- return
236
- elif entity.dxftype() == "POINT":
237
- pos = entity.dxf.location
238
- if len(pos) == 2:
239
- x, y = pos
240
- else:
241
- x, y, z = pos
242
- node = context_node.add(x=x, y=y, matrix=Matrix(), type="elem point")
243
- self.check_for_attributes(node, entity)
244
- e_list.append(node)
245
- return
246
- elif entity.dxftype() == "POLYLINE":
247
- # https://ezdxf.readthedocs.io/en/stable/dxfentities/lwpolyline.html
248
- if entity.is_2d_polyline:
249
- if not entity.has_arc:
250
- if entity.is_closed:
251
- element = Polygon([(p[0], p[1]) for p in entity.points()])
252
- else:
253
- element = Polyline([(p[0], p[1]) for p in entity.points()])
254
- element.values[
255
- SVG_ATTR_VECTOR_EFFECT
256
- ] = SVG_VALUE_NON_SCALING_STROKE
257
- element.transform.post_scale(self.scale, -self.scale)
258
- element.transform.post_translate_y(
259
- self.elements.device.view.unit_height
260
- )
261
- node = context_node.add(shape=element, type="elem polyline")
262
- self.check_for_attributes(node, entity)
263
- e_list.append(node)
264
- return
265
- else:
266
- element = Path()
267
- bulge = 0
268
- for e in entity:
269
- point = e.dxf.location
270
- if bulge == 0:
271
- element.line((point[0], point[1]))
272
- else:
273
- element += Arc(
274
- start=element.current_point,
275
- end=(point[0], point[1]),
276
- bulge=bulge,
277
- )
278
- bulge = e.dxf.bulge
279
- if entity.is_closed:
280
- if bulge == 0:
281
- element.closed()
282
- else:
283
- element += Arc(
284
- start=element.current_point,
285
- end=element.z_point,
286
- bulge=bulge,
287
- )
288
- element.closed()
289
- element.values[
290
- SVG_ATTR_VECTOR_EFFECT
291
- ] = SVG_VALUE_NON_SCALING_STROKE
292
- element.transform.post_scale(self.scale, -self.scale)
293
- element.transform.post_translate_y(
294
- self.elements.device.view.unit_height
295
- )
296
- path = abs(Path(element))
297
- if len(path) != 0:
298
- if not isinstance(path[0], Move):
299
- path = Move(path.first_point) + path
300
- path.approximate_arcs_with_cubics()
301
- node = context_node.add(path=path, type="elem path")
302
- self.check_for_attributes(node, entity)
303
- e_list.append(node)
304
- return
305
- elif entity.dxftype() == "LWPOLYLINE":
306
- # https://ezdxf.readthedocs.io/en/stable/dxfentities/lwpolyline.html
307
- if not entity.has_arc:
308
- if entity.closed:
309
- element = Polygon(*[(p[0], p[1]) for p in entity])
310
- else:
311
- element = Polyline(*[(p[0], p[1]) for p in entity])
312
- element.values[SVG_ATTR_VECTOR_EFFECT] = SVG_VALUE_NON_SCALING_STROKE
313
- element.transform.post_scale(self.scale, -self.scale)
314
- element.transform.post_translate_y(
315
- self.elements.device.view.unit_height
316
- )
317
- node = context_node.add(shape=element, type="elem polyline")
318
- self.check_for_attributes(node, entity)
319
- e_list.append(node)
320
- return
321
- else:
322
- element = Path()
323
- bulge = 0
324
- for e in entity:
325
- if bulge == 0:
326
- element.line((e[0], e[1]))
327
- else:
328
- element += Arc(
329
- start=element.current_point, end=(e[0], e[1]), bulge=bulge
330
- )
331
- bulge = e[4]
332
- if entity.closed:
333
- if bulge == 0:
334
- element.closed()
335
- else:
336
- element += Arc(
337
- start=element.current_point,
338
- end=element.z_point,
339
- bulge=bulge,
340
- )
341
- element.closed()
342
- element.values[SVG_ATTR_VECTOR_EFFECT] = SVG_VALUE_NON_SCALING_STROKE
343
- element.transform.post_scale(self.scale, -self.scale)
344
- element.transform.post_translate_y(
345
- self.elements.device.view.unit_height
346
- )
347
- path = abs(Path(element))
348
- if len(path) != 0:
349
- if not isinstance(path[0], Move):
350
- path = Move(path.first_point) + path
351
- path.approximate_arcs_with_cubics()
352
- node = context_node.add(path=path, type="elem path")
353
- self.check_for_attributes(node, entity)
354
- e_list.append(node)
355
- return
356
- elif entity.dxftype() == "HATCH":
357
- # https://ezdxf.readthedocs.io/en/stable/dxfentities/hatch.html
358
- element = Path()
359
- if entity.bgcolor is not None:
360
- Path.fill = Color(*entity.bgcolor)
361
- for p in entity.paths:
362
- if p.path_type_flags & 2:
363
- for v in p.vertices:
364
- element.line(v[0], v[1])
365
- if p.is_closed:
366
- element.closed()
367
- else:
368
- for e in p.edges:
369
- if type(e) == "LineEdge":
370
- # https://ezdxf.readthedocs.io/en/stable/dxfentities/hatch.html#ezdxf.entities.LineEdge
371
- element.line(e.start, e.end)
372
- elif type(e) == "ArcEdge":
373
- # https://ezdxf.readthedocs.io/en/stable/dxfentities/hatch.html#ezdxf.entities.ArcEdge
374
- circ = Circle(
375
- center=e.center,
376
- radius=e.radius,
377
- )
378
- element += circ.arc_angle(
379
- Angle.degrees(e.start_angle), Angle.degrees(e.end_angle)
380
- )
381
- elif type(e) == "EllipseEdge":
382
- # https://ezdxf.readthedocs.io/en/stable/dxfentities/hatch.html#ezdxf.entities.EllipseEdge
383
- element += Arc(
384
- radius=e.radius,
385
- start_angle=Angle.degrees(e.start_angle),
386
- end_angle=Angle.degrees(e.end_angle),
387
- ccw=e.is_counter_clockwise,
388
- )
389
- elif type(e) == "SplineEdge":
390
- # https://ezdxf.readthedocs.io/en/stable/dxfentities/hatch.html#ezdxf.entities.SplineEdge
391
- if e.degree == 3:
392
- for i in range(len(e.knot_values)):
393
- control = e.control_values[i]
394
- knot = e.knot_values[i]
395
- element.quad(control, knot)
396
- elif e.degree == 4:
397
- for i in range(len(e.knot_values)):
398
- control1 = e.control_values[2 * i]
399
- control2 = e.control_values[2 * i + 1]
400
- knot = e.knot_values[i]
401
- element.cubic(control1, control2, knot)
402
- else:
403
- for i in range(len(e.knot_values)):
404
- knot = e.knot_values[i]
405
- element.line(knot)
406
- element.values[SVG_ATTR_VECTOR_EFFECT] = SVG_VALUE_NON_SCALING_STROKE
407
- element.transform.post_scale(self.scale, -self.scale)
408
- element.transform.post_translate_y(self.elements.device.view.unit_height)
409
- path = abs(Path(element))
410
- if len(path) != 0:
411
- if not isinstance(path[0], Move):
412
- path = Move(path.first_point) + path
413
- path.approximate_arcs_with_cubics()
414
- node = context_node.add(path=path, type="elem path")
415
- self.check_for_attributes(node, entity)
416
- e_list.append(node)
417
- return
418
- elif entity.dxftype() == "IMAGE":
419
- bottom_left_position = entity.dxf.insert
420
- size = entity.dxf.image_size
421
- imagedef = entity.image_def
422
- filename = imagedef.dxf.filename
423
- if not os.path.exists(filename) or os.path.isdir(filename):
424
- filename = os.path.join(os.path.dirname(self.pathname), filename)
425
- if not os.path.exists(filename) or os.path.isdir(filename):
426
- # Image def refers to a path that does not exist.
427
- return
428
-
429
- try:
430
- node = context_node.add(
431
- href=filename,
432
- x=bottom_left_position[0],
433
- y=bottom_left_position[1] - size[1],
434
- width=size[0],
435
- height=size[1],
436
- type="elem image",
437
- )
438
- except FileNotFoundError:
439
- return
440
- # Node.matrix is primary transformation.
441
- node.matrix.post_scale(self.scale, -self.scale)
442
- node.matrix.post_translate_y(self.elements.device.view.unit_height)
443
- self.check_for_attributes(node, entity)
444
- e_list.append(node)
445
- return
446
- elif entity.dxftype() == "MTEXT":
447
- insert = entity.dxf.insert
448
- node = context_node.add(
449
- text=entity.text,
450
- x=insert[0],
451
- y=insert[1],
452
- stroke_scaled=False,
453
- type="elem text",
454
- )
455
- node.matrix.post_scale(self.scale, -self.scale)
456
- node.matrix.post_translate_y(self.elements.device.view.unit_height)
457
-
458
- self.check_for_attributes(node, entity)
459
- e_list.append(node)
460
- return
461
- elif entity.dxftype() == "TEXT":
462
- insert = entity.dxf.insert
463
- node = context_node.add(
464
- text=entity.dxf.text,
465
- x=insert[0],
466
- y=insert[1],
467
- stroke_scaled=False,
468
- type="elem text",
469
- )
470
- node.matrix.post_scale(self.scale, -self.scale)
471
- node.matrix.post_translate_y(self.elements.device.view.unit_height)
472
- self.check_for_attributes(node, entity)
473
- e_list.append(node)
474
- return
475
- elif entity.dxftype() == "SOLID" or entity.dxftype() == "TRACE":
476
- # https://ezdxf.readthedocs.io/en/stable/dxfentities/solid.html
477
- element = Path()
478
- element.move((entity[0][0], entity[0][1]))
479
- element.line((entity[1][0], entity[1][1]))
480
- element.line((entity[2][0], entity[2][1]))
481
- element.line((entity[3][0], entity[3][1]))
482
- element.closed()
483
- element.fill = Color("black")
484
- element.values[SVG_ATTR_VECTOR_EFFECT] = SVG_VALUE_NON_SCALING_STROKE
485
- element.transform.post_scale(self.scale, -self.scale)
486
- element.transform.post_translate_y(self.elements.device.view.unit_height)
487
-
488
- path = abs(Path(element))
489
- node = context_node.add(path=path, type="elem path")
490
- self.check_for_attributes(node, entity)
491
- e_list.append(node)
492
- return
493
- elif entity.dxftype() == "SPLINE":
494
- element = Path()
495
- try:
496
- for b in entity.construction_tool().bezier_decomposition():
497
- if len(element) == 0:
498
- element.move((b[0][0], b[0][1]))
499
- if len(b) == 4:
500
- element.cubic(
501
- (b[1][0], b[1][1]), (b[2][0], b[2][1]), (b[3][0], b[3][1])
502
- )
503
- elif len(b) == 3:
504
- element.quad((b[1][0], b[1][1]), (b[2][0], b[2][1]))
505
- except (AttributeError, TypeError):
506
- # Fallback for rational b-splines.
507
- try:
508
- # Flattening version 0.15
509
- for q in entity.flattening(1, 15):
510
- element.line((q[0], q[1]))
511
- except AttributeError:
512
- # Version before 0.15
513
- try:
514
- for (
515
- bezier
516
- ) in entity.construction_tool().cubic_bezier_approximation(4):
517
- b = bezier.control_points
518
- if len(b) == 4:
519
- element.cubic(
520
- (b[1][0], b[1][1]),
521
- (b[2][0], b[2][1]),
522
- (b[3][0], b[3][1]),
523
- )
524
- elif len(b) == 3:
525
- element.quad((b[1][0], b[1][1]), (b[2][0], b[2][1]))
526
- except (AttributeError, TypeError):
527
- # Fallback for versions of EZDXF prior to 0.13
528
- element.move(entity.control_points[0])
529
- for i in range(1, entity.dxf.n_control_points):
530
- element.line(entity.control_points[i])
531
- if entity.closed:
532
- element.closed()
533
- element.values[SVG_ATTR_VECTOR_EFFECT] = SVG_VALUE_NON_SCALING_STROKE
534
- element.transform.post_scale(self.scale, -self.scale)
535
- element.transform.post_translate_y(self.elements.device.view.unit_height)
536
- path = abs(element)
537
- if len(path) != 0:
538
- if not isinstance(path[0], Move):
539
- path = Move(path.first_point) + path
540
- node = context_node.add(path=path, type="elem path")
541
- self.check_for_attributes(node, entity)
542
- e_list.append(node)
543
- return
544
- elif entity.dxftype() == "INSERT":
545
- # Insert creates virtual grouping.
546
- context_node = context_node.add(type="group")
547
- for e in entity.virtual_entities():
548
- if e is None:
549
- continue
550
- self.parse(e, context_node, e_list)
551
- return
552
- else:
553
- # We need a channel comment here so that this is not silently ignored.
554
- return # Might be something unsupported.
1
+ import os.path
2
+
3
+ import ezdxf
4
+ from ezdxf import units
5
+
6
+ from ..core.exceptions import BadFileError
7
+ from ..core.units import UNITS_PER_INCH, UNITS_PER_MM
8
+
9
+ try:
10
+ # ezdxf <= 0.6.14
11
+ from ezdxf.tools.rgb import DXF_DEFAULT_COLORS, int2rgb
12
+ except ImportError:
13
+ # ezdxf > 0.6.14
14
+ from ezdxf import int2rgb
15
+ from ezdxf.colors import DXF_DEFAULT_COLORS
16
+
17
+ import math
18
+
19
+ from ezdxf.units import decode
20
+
21
+ from meerk40t.svgelements import (
22
+ SVG_ATTR_VECTOR_EFFECT,
23
+ SVG_VALUE_NON_SCALING_STROKE,
24
+ Angle,
25
+ Arc,
26
+ Circle,
27
+ Color,
28
+ Ellipse,
29
+ Matrix,
30
+ Move,
31
+ Path,
32
+ Point,
33
+ Polygon,
34
+ Polyline,
35
+ Viewbox,
36
+ )
37
+ from meerk40t.tools.geomstr import Geomstr
38
+
39
+
40
+ class DxfLoader:
41
+ @staticmethod
42
+ def load_types():
43
+ yield "Drawing Exchange Format", ("dxf",), "image/vnd.dxf"
44
+
45
+ @staticmethod
46
+ def load(kernel, elements_service, pathname, **kwargs):
47
+ """
48
+ Load dxf content. Requires ezdxf which tends to also require Python 3.6 or greater.
49
+
50
+ Dxf data has an origin point located in the lower left corner. +y -> top
51
+ """
52
+ try:
53
+ dxf = ezdxf.readfile(pathname)
54
+ except ezdxf.DXFError:
55
+ try:
56
+ # dxf is low quality. Attempt recovery.
57
+ from ezdxf import recover
58
+
59
+ dxf, auditor = recover.readfile(pathname)
60
+ except ezdxf.DXFStructureError as e:
61
+ # Recovery failed, return the BadFileError.
62
+ raise BadFileError(str(e)) from e
63
+
64
+ unit = dxf.header.get("$INSUNITS")
65
+ if unit is not None and unit != 0:
66
+ du = units.DrawingUnits(UNITS_PER_INCH, unit="in")
67
+ scale = du.factor(decode(unit))
68
+ else:
69
+ scale = UNITS_PER_MM
70
+
71
+ dxf_processor = DXFProcessor(elements_service, dxf=dxf, scale=scale)
72
+ dxf_processor.process(dxf.entities, pathname)
73
+ return True
74
+
75
+
76
+ class DXFProcessor:
77
+ def __init__(self, elements_modifier, dxf, scale=1.0):
78
+ self.elements = elements_modifier
79
+ self.dxf = dxf
80
+ self.scale = scale
81
+ self.elements_list = []
82
+ self.reverse = False
83
+ self.requires_classification = True
84
+ self.pathname = None
85
+ self.try_unsupported = True
86
+ # Path stroke width
87
+ self.std_stroke = 1000
88
+
89
+ def process(self, entities, pathname):
90
+ self.pathname = pathname
91
+ # basename = os.path.basename(pathname)
92
+ context_node = self.elements.get(type="branch elems")
93
+ file_node = context_node.add(type="file", filepath=pathname)
94
+ file_node.focus()
95
+ for entity in entities:
96
+ self.parse(entity, file_node, self.elements_list)
97
+ dxf_center = self.elements.setting(bool, "dxf_center", True)
98
+ self.try_unsupported = self.elements.setting(bool, "dxf_try_unsupported", True)
99
+ if dxf_center:
100
+ bbox = file_node.bounds
101
+ if bbox is not None:
102
+ view = self.elements.device.view
103
+ bw = view.unit_width
104
+ bh = view.unit_height
105
+ bx = 0
106
+ by = 0
107
+ x = bbox[0]
108
+ y = bbox[1]
109
+ w = bbox[2] - bbox[0]
110
+ h = bbox[3] - bbox[1]
111
+ if w == 0:
112
+ w = 1
113
+ if h == 0:
114
+ h = 1
115
+ if w > view.unit_width or h > view.unit_height:
116
+ # Cannot fit to bed. Scale.
117
+ bb = Viewbox(f"{x} {y} {w} {h}", preserve_aspect_ratio="xMidyMid")
118
+ matrix = bb.transform(Viewbox(bx, by, bw, bh))
119
+ for node in self.elements_list:
120
+ node.matrix *= matrix
121
+ node.modified()
122
+ elif x < bx or y < by or x + w > bw or y + h > bh:
123
+ # Is outside the bed but sized correctly, center
124
+ bcx = bw / 2.0
125
+ bcy = bh / 2.0
126
+ cx = (bbox[0] + bbox[2]) / 2.0
127
+ cy = (bbox[1] + bbox[3]) / 2.0
128
+ matrix = Matrix.translate(bcx - cx, bcy - cy)
129
+ for node in self.elements_list:
130
+ node.matrix *= matrix
131
+ node.modified()
132
+ # else, is within the bed dimensions correctly, change nothing.
133
+ if self.elements.classify_new:
134
+ self.elements.classify(self.elements_list)
135
+ return True
136
+
137
+ def check_for_attributes(self, node, entity):
138
+ dxf = self.dxf
139
+ if entity.rgb is not None:
140
+ if isinstance(entity.rgb, tuple):
141
+ node.stroke = Color(*entity.rgb)
142
+ else:
143
+ node.stroke = Color(entity.rgb)
144
+ else:
145
+ c = entity.dxf.color
146
+ if c == 256 and entity.dxf.layer in dxf.layers:
147
+ layer = dxf.layers.get(entity.dxf.layer)
148
+ c = layer.color
149
+ try:
150
+ color = (
151
+ Color("black")
152
+ if c == 7
153
+ else Color(*int2rgb(DXF_DEFAULT_COLORS[c]))
154
+ )
155
+ # Color 7 is black on light backgrounds, light on black.
156
+ except Exception:
157
+ color = Color("black")
158
+ node.stroke = color
159
+
160
+ # def debug_entity(self, entity):
161
+ # print (f"Entity: {entity.dxftype()}")
162
+ # for key in dir(entity):
163
+ # if key.startswith("_"):
164
+ # continue
165
+ # var = getattr(entity, key)
166
+ # if callable(var):
167
+ # continue
168
+ # print (f"e.{key}={var}")
169
+ # for key in dir(entity.dxf):
170
+ # if key.startswith("_"):
171
+ # continue
172
+ # var = getattr(entity.dxf, key)
173
+ # if callable(var):
174
+ # continue
175
+ # print (f"e.dxf.{key}={var}")
176
+
177
+ def parse(self, entity, context_node, e_list):
178
+
179
+ def get_angles(entity):
180
+ start_angle = 0
181
+ end_angle = math.tau
182
+ if hasattr(entity.dxf, "start_angle"):
183
+ start_angle = Angle.degrees(entity.dxf.start_angle)
184
+ end_angle = Angle.degrees(entity.dxf.end_angle)
185
+ else:
186
+ # Due to the flipped nature of dxf we need to mirror them
187
+ start_angle = entity.dxf.start_param
188
+ end_angle = entity.dxf.end_param
189
+ if start_angle >= end_angle:
190
+ end_angle += Angle.turns(1)
191
+ return start_angle, end_angle
192
+
193
+ if hasattr(entity, "transform_to_wcs"):
194
+ try:
195
+ entity.transform_to_wcs(entity.ocs())
196
+ except AttributeError:
197
+ pass
198
+ dxftype = entity.dxftype()
199
+ # if dxftype in ("TEXT", "MTEXT"):
200
+ # self.debug_entity(entity)
201
+
202
+ if dxftype == "CIRCLE":
203
+ m = Matrix()
204
+ m.post_scale(self.scale, -self.scale)
205
+ m.post_translate_y(self.elements.device.view.unit_height)
206
+ try:
207
+ cx, cy = entity.dxf.center
208
+ except ValueError:
209
+ # 3d center.
210
+ cx, cy, cz = entity.dxf.center
211
+ node = context_node.add(
212
+ cx=cx,
213
+ cy=cy,
214
+ rx=entity.dxf.radius,
215
+ ry=entity.dxf.radius,
216
+ matrix=m,
217
+ stroke_scale=False,
218
+ stroke_width=self.std_stroke,
219
+ type="elem ellipse",
220
+ )
221
+ self.check_for_attributes(node, entity)
222
+ e_list.append(node)
223
+ return
224
+ elif dxftype == "ARC":
225
+ center = (entity.dxf.center) # Center point of the circle (3D, but we'll use x,y)
226
+ a = entity.dxf.radius
227
+ b = entity.dxf.radius
228
+ start_angle, end_angle = get_angles(entity)
229
+ angle = 0
230
+ geom = Geomstr()
231
+ geom.arc_as_cubics(
232
+ start_t=start_angle,
233
+ end_t=end_angle,
234
+ cx=center[0],
235
+ cy=center[1],
236
+ rx=a,
237
+ ry=b,
238
+ rotation=angle,
239
+ )
240
+ node = context_node.add(
241
+ type="elem path",
242
+ geometry=geom,
243
+ stroke_scale=False,
244
+ stroke_width=self.std_stroke,
245
+ )
246
+ node.matrix.post_scale(self.scale, -self.scale)
247
+ node.matrix.post_translate_y(self.elements.device.view.unit_height)
248
+ self.check_for_attributes(node, entity)
249
+ e_list.append(node)
250
+ return
251
+ elif dxftype == "ELLIPSE":
252
+ center = (entity.dxf.center) # Center point of the ellipse (3D, but we'll use x,y)
253
+ major_axis = entity.dxf.major_axis # Vector representing the major axis
254
+ ratio = entity.dxf.ratio # Ratio of minor to major axis
255
+ start_angle, end_angle = get_angles(entity)
256
+
257
+ # Calculate the angle of the major axis in the XY plane
258
+ angle = math.atan2(major_axis[1], major_axis[0])
259
+
260
+ # Calculate the lengths of the major and minor axes
261
+ a = math.sqrt(
262
+ major_axis[0] ** 2 + major_axis[1] ** 2
263
+ ) # Length of the major axis (in XY plane)
264
+ b = a * ratio # Length of the minor axis
265
+ # geom = Geomstr.ellipse(
266
+ # start_t=start_angle,
267
+ # end_t=end_angle,
268
+ # cx=center[0],
269
+ # cy=center[1],
270
+ # rx=a,
271
+ # ry=b,
272
+ # rotation=angle,
273
+ # )
274
+ geom = Geomstr()
275
+ geom.arc_as_cubics(
276
+ start_t=start_angle,
277
+ end_t=end_angle,
278
+ cx=center[0],
279
+ cy=center[1],
280
+ rx=a,
281
+ ry=b,
282
+ rotation=angle,
283
+ )
284
+ node = context_node.add(
285
+ type="elem path",
286
+ geometry=geom,
287
+ stroke_scale=False,
288
+ stroke_width=self.std_stroke,
289
+ )
290
+ node.matrix.post_scale(self.scale, -self.scale)
291
+ node.matrix.post_translate_y(self.elements.device.view.unit_height)
292
+ self.check_for_attributes(node, entity)
293
+ e_list.append(node)
294
+ return
295
+ elif dxftype == "LINE":
296
+ # https://ezdxf.readthedocs.io/en/stable/dxfentities/line.html
297
+ m = Matrix()
298
+ m.post_scale(self.scale, -self.scale)
299
+ m.post_translate_y(self.elements.device.view.unit_height)
300
+ node = context_node.add(
301
+ x1=entity.dxf.start[0],
302
+ y1=entity.dxf.start[1],
303
+ x2=entity.dxf.end[0],
304
+ y2=entity.dxf.end[1],
305
+ stroke_scale=False,
306
+ stroke_width=self.std_stroke,
307
+ matrix=m,
308
+ type="elem line",
309
+ )
310
+ self.check_for_attributes(node, entity)
311
+ e_list.append(node)
312
+ return
313
+ elif dxftype == "POINT":
314
+ pos = entity.dxf.location
315
+ if len(pos) == 2:
316
+ x, y = pos
317
+ else:
318
+ x, y, z = pos
319
+ m = Matrix()
320
+ m.post_translate_y(self.elements.device.view.unit_height)
321
+
322
+ node = context_node.add(x=x, y=y, matrix=m, type="elem point")
323
+ self.check_for_attributes(node, entity)
324
+ e_list.append(node)
325
+ return
326
+ elif dxftype == "POLYLINE":
327
+ # https://ezdxf.readthedocs.io/en/stable/dxfentities/polyline.html
328
+ supported = entity.is_2d_polyline or self.try_unsupported
329
+ if supported:
330
+ if not entity.has_arc:
331
+ if entity.is_closed:
332
+ element = Polygon([(p[0], p[1]) for p in entity.points()])
333
+ else:
334
+ element = Polyline([(p[0], p[1]) for p in entity.points()])
335
+ element.values[SVG_ATTR_VECTOR_EFFECT] = (
336
+ SVG_VALUE_NON_SCALING_STROKE
337
+ )
338
+ element.transform.post_scale(self.scale, -self.scale)
339
+ element.transform.post_translate_y(
340
+ self.elements.device.view.unit_height
341
+ )
342
+ node = context_node.add(
343
+ shape=element,
344
+ type="elem polyline",
345
+ stroke_scale=False,
346
+ stroke_width=self.std_stroke,
347
+ )
348
+ else:
349
+ element = Path()
350
+ bulge = 0
351
+ for idx, e in enumerate(entity):
352
+ point = e.dxf.location
353
+ if bulge == 0:
354
+ element.line((point[0], point[1]))
355
+ else:
356
+ element += Arc(
357
+ start=element.current_point,
358
+ end=(point[0], point[1]),
359
+ bulge=bulge,
360
+ )
361
+ bulge = e.dxf.bulge
362
+ if entity.is_closed:
363
+ if bulge == 0:
364
+ element.closed()
365
+ else:
366
+ element += Arc(
367
+ start=element.current_point,
368
+ end=element.z_point,
369
+ bulge=bulge,
370
+ )
371
+ element.closed()
372
+ element.values[SVG_ATTR_VECTOR_EFFECT] = (
373
+ SVG_VALUE_NON_SCALING_STROKE
374
+ )
375
+ element.transform.post_scale(self.scale, -self.scale)
376
+ element.transform.post_translate_y(
377
+ self.elements.device.view.unit_height
378
+ )
379
+ path = abs(Path(element))
380
+ if len(path) != 0:
381
+ if not isinstance(path[0], Move):
382
+ path = Move(path.first_point) + path
383
+ path.approximate_arcs_with_cubics()
384
+ node = context_node.add(
385
+ path=path,
386
+ type="elem path",
387
+ stroke_scale=False,
388
+ stroke_width=self.std_stroke,
389
+ )
390
+ self.check_for_attributes(node, entity)
391
+ e_list.append(node)
392
+ return
393
+ elif dxftype == "LWPOLYLINE":
394
+ # https://ezdxf.readthedocs.io/en/stable/dxfentities/lwpolyline.html
395
+ if not entity.has_arc:
396
+ if entity.closed:
397
+ element = Polygon(*[(p[0], p[1]) for p in entity])
398
+ else:
399
+ element = Polyline(*[(p[0], p[1]) for p in entity])
400
+ element.values[SVG_ATTR_VECTOR_EFFECT] = SVG_VALUE_NON_SCALING_STROKE
401
+ element.transform.post_scale(self.scale, -self.scale)
402
+ element.transform.post_translate_y(
403
+ self.elements.device.view.unit_height
404
+ )
405
+ node = context_node.add(
406
+ shape=element,
407
+ type="elem polyline",
408
+ stroke_scale=False,
409
+ stroke_width=self.std_stroke,
410
+ )
411
+ else:
412
+ element = Path()
413
+ bulge = 0
414
+ for e in entity:
415
+ if bulge == 0:
416
+ element.line((e[0], e[1]))
417
+ else:
418
+ element += Arc(
419
+ start=element.current_point, end=(e[0], e[1]), bulge=bulge
420
+ )
421
+ bulge = e[4]
422
+ if entity.closed:
423
+ if bulge == 0:
424
+ element.closed()
425
+ else:
426
+ element += Arc(
427
+ start=element.current_point,
428
+ end=element.z_point,
429
+ bulge=bulge,
430
+ )
431
+ element.closed()
432
+ element.values[SVG_ATTR_VECTOR_EFFECT] = SVG_VALUE_NON_SCALING_STROKE
433
+ element.transform.post_scale(self.scale, -self.scale)
434
+ element.transform.post_translate_y(
435
+ self.elements.device.view.unit_height
436
+ )
437
+ path = abs(Path(element))
438
+ if len(path) != 0:
439
+ if not isinstance(path[0], Move):
440
+ path = Move(path.first_point) + path
441
+ path.approximate_arcs_with_cubics()
442
+ node = context_node.add(
443
+ path=path,
444
+ type="elem path",
445
+ stroke_scale=False,
446
+ stroke_width=self.std_stroke,
447
+ )
448
+ self.check_for_attributes(node, entity)
449
+ e_list.append(node)
450
+ return
451
+ elif dxftype == "HATCH":
452
+ # https://ezdxf.readthedocs.io/en/stable/dxfentities/hatch.html
453
+ element = Path()
454
+ if entity.bgcolor is not None:
455
+ Path.fill = Color(*entity.bgcolor)
456
+ for p in entity.paths:
457
+ if p.path_type_flags & 2:
458
+ for v in p.vertices:
459
+ element.line(v[0], v[1])
460
+ if p.is_closed:
461
+ element.closed()
462
+ else:
463
+ for e in p.edges:
464
+ if type(e) == "LineEdge":
465
+ # https://ezdxf.readthedocs.io/en/stable/dxfentities/hatch.html#ezdxf.entities.LineEdge
466
+ element.line(e.start, e.end)
467
+ elif type(e) == "ArcEdge":
468
+ # https://ezdxf.readthedocs.io/en/stable/dxfentities/hatch.html#ezdxf.entities.ArcEdge
469
+ circ = Circle(
470
+ center=e.center,
471
+ radius=e.radius,
472
+ )
473
+ element += circ.arc_angle(
474
+ Angle.degrees(e.start_angle), Angle.degrees(e.end_angle)
475
+ )
476
+ elif type(e) == "EllipseEdge":
477
+ # https://ezdxf.readthedocs.io/en/stable/dxfentities/hatch.html#ezdxf.entities.EllipseEdge
478
+ element += Arc(
479
+ radius=e.radius,
480
+ start_angle=Angle.degrees(e.start_angle),
481
+ end_angle=Angle.degrees(e.end_angle),
482
+ ccw=e.is_counter_clockwise,
483
+ )
484
+ elif type(e) == "SplineEdge":
485
+ # https://ezdxf.readthedocs.io/en/stable/dxfentities/hatch.html#ezdxf.entities.SplineEdge
486
+ if e.degree == 3:
487
+ for i in range(len(e.knot_values)):
488
+ control = e.control_values[i]
489
+ knot = e.knot_values[i]
490
+ element.quad(control, knot)
491
+ elif e.degree == 4:
492
+ for i in range(len(e.knot_values)):
493
+ control1 = e.control_values[2 * i]
494
+ control2 = e.control_values[2 * i + 1]
495
+ knot = e.knot_values[i]
496
+ element.cubic(control1, control2, knot)
497
+ else:
498
+ for i in range(len(e.knot_values)):
499
+ knot = e.knot_values[i]
500
+ element.line(knot)
501
+ element.values[SVG_ATTR_VECTOR_EFFECT] = SVG_VALUE_NON_SCALING_STROKE
502
+ element.transform.post_scale(self.scale, -self.scale)
503
+ element.transform.post_translate_y(self.elements.device.view.unit_height)
504
+ path = abs(Path(element))
505
+ if len(path) != 0:
506
+ if not isinstance(path[0], Move):
507
+ path = Move(path.first_point) + path
508
+ path.approximate_arcs_with_cubics()
509
+ node = context_node.add(
510
+ path=path,
511
+ type="elem path",
512
+ stroke_scale=False,
513
+ stroke_width=self.std_stroke,
514
+ )
515
+ self.check_for_attributes(node, entity)
516
+ e_list.append(node)
517
+ return
518
+ elif dxftype == "IMAGE":
519
+ bottom_left_position = entity.dxf.insert
520
+ targetmatrix = Matrix()
521
+ targetmatrix.post_scale(self.scale, -self.scale)
522
+ targetmatrix.post_translate_y(self.elements.device.view.unit_height)
523
+ # So what's our targetposition then?
524
+ targetpos = targetmatrix.point_in_matrix_space(
525
+ Point(bottom_left_position[0], bottom_left_position[1])
526
+ )
527
+
528
+ size_img = entity.dxf.image_size
529
+ w_scale = entity.dxf.u_pixel[0]
530
+ h_scale = entity.dxf.v_pixel[1]
531
+ size = (size_img[0] * w_scale, size_img[1] * h_scale)
532
+ imagedef = entity.image_def
533
+ fname1 = imagedef.dxf.filename
534
+ fname2 = os.path.normpath(
535
+ os.path.join(os.path.dirname(self.pathname), fname1)
536
+ )
537
+ candidates = [
538
+ fname1,
539
+ fname2,
540
+ ]
541
+ # LibreCad 2.2 has a bug - it stores the relative path with a '../' too few
542
+ # So let's add another option
543
+ if fname1.startswith("../"):
544
+ fname1 = "../" + fname1
545
+ fname2 = os.path.normpath(
546
+ os.path.join(os.path.dirname(self.pathname), fname1)
547
+ )
548
+ candidates.append(fname1)
549
+ candidates.append(fname2)
550
+
551
+ was_found = False
552
+ for filename in candidates:
553
+ if not os.path.exists(filename) or os.path.isdir(filename):
554
+ continue
555
+ was_found = True
556
+ break
557
+ if not was_found:
558
+ return
559
+
560
+ x_pos = bottom_left_position[0]
561
+ y_pos = bottom_left_position[1]
562
+ dxf_units_per_inch = self.scale / UNITS_PER_INCH
563
+ width_in_inches = size[0] * dxf_units_per_inch
564
+ height_in_inches = size[1] * dxf_units_per_inch
565
+ dpix = size_img[0] / width_in_inches
566
+ dpiy = size_img[1] / height_in_inches
567
+
568
+ # Node.matrix is primary transformation.
569
+ matrix = Matrix()
570
+ matrix.post_scale(1, -1)
571
+ matrix.post_translate_x(x_pos)
572
+ matrix.post_translate_y(y_pos)
573
+ matrix.post_scale(self.scale, -self.scale)
574
+ matrix.post_translate_y(self.elements.device.view.unit_height)
575
+ try:
576
+ node = context_node.add(
577
+ href=filename,
578
+ width=size[0],
579
+ height=size[1],
580
+ dpi=dpix,
581
+ matrix=matrix,
582
+ type="elem image",
583
+ )
584
+ except FileNotFoundError:
585
+ return
586
+ try:
587
+ from PIL import ImageOps
588
+
589
+ node.image = ImageOps.exif_transpose(node.image)
590
+ except ImportError:
591
+ pass
592
+ self.check_for_attributes(node, entity)
593
+ # We don't seem to get the position right, so let's look
594
+ # at our bottom_left position again and fix the gap
595
+ bb = node.bounds
596
+ dx = targetpos.x - bb[0]
597
+ dy = targetpos.y - bb[3]
598
+ if dx != 0:
599
+ node.matrix.post_translate_x(dx)
600
+ if dy != 0:
601
+ node.matrix.post_translate_y(dy)
602
+ e_list.append(node)
603
+ return
604
+ elif dxftype == "MTEXT":
605
+ insert = entity.dxf.insert
606
+ # node = context_node.add(
607
+ # text=entity.text,
608
+ # x=insert[0],
609
+ # y=insert[1],
610
+ # stroke_scaled=False,
611
+ # type="elem text",
612
+ # )
613
+ # node.matrix.post_scale(1, -1, insert[0], insert[1])
614
+ # if hasattr(entity.dxf, "height") and entity.dxf.height != 0:
615
+ # # 72pt = 1inch
616
+ # fontheight = 12 * 25.4 / 72 # in mm
617
+ # factor = entity.dxf.height / fontheight
618
+ # print (f"Scale according to height {entity.dxf.height} -> {factor:.3f}")
619
+ # node.matrix.post_scale(factor, factor, insert[0], insert[1])
620
+ # elif hasattr(entity.dxf, "width") and entity.dxf.width != 0:
621
+ # # 72pt = 1inch
622
+ # # We are guessing....
623
+ # fontwidth = len(entity.dxf.text) * 6 * 25.4 / 72 # in mm
624
+ # factor = entity.dxf.width / fontwidth
625
+ # print (f"Scale according to width {entity.dxf.width} ({fontwidth:.2f} -> {factor:.3f}")
626
+ # node.matrix.post_scale(factor, factor, insert[0], insert[1])
627
+ # node.matrix.post_scale(self.scale, -self.scale)
628
+ # node.matrix.post_translate_y(self.elements.device.view.unit_height)
629
+ # self.check_for_attributes(node, entity)
630
+ txt = entity.dxf.text if hasattr(entity.dxf, "text") else entity.text
631
+ node = self.elements.kernel.root.fonts.create_linetext_node(0, 0, txt)
632
+ bb = node.bounds
633
+ if hasattr(entity.dxf, "height") and entity.dxf.height != 0:
634
+ fontheight = (bb[2] - bb[0]) / UNITS_PER_MM
635
+ factor = entity.dxf.height / fontheight
636
+ node.matrix.post_scale(factor, factor, bb[0], bb[1])
637
+ elif hasattr(entity.dxf, "width") and entity.dxf.width != 0:
638
+ fontwidth = (bb[3] - bb[1]) / UNITS_PER_MM
639
+ factor = entity.dxf.width / fontwidth
640
+ node.matrix.post_scale(factor, factor, bb[0], bb[1])
641
+ bb = node.bounds
642
+
643
+ node.matrix.post_translate(insert[0] * UNITS_PER_MM - bb[0], -1 * insert[1] * UNITS_PER_MM - bb[1])
644
+ node.matrix.post_translate_y(self.elements.device.view.unit_height)
645
+ self.check_for_attributes(node, entity)
646
+ context_node.add_node(node)
647
+
648
+ e_list.append(node)
649
+ return
650
+ elif dxftype == "TEXT":
651
+ insert = entity.dxf.insert
652
+ node = context_node.add(
653
+ text=entity.dxf.text,
654
+ x=insert[0],
655
+ y=insert[1],
656
+ stroke_scaled=False,
657
+ type="elem text",
658
+ )
659
+ node.matrix.post_scale(1, -1, insert[0], insert[1])
660
+ if hasattr(entity.dxf, "height") and entity.dxf.height != 0:
661
+ # 72pt = 1inch
662
+ fontheight = 12 * 25.4 / 72 # in mm
663
+ factor = entity.dxf.height / fontheight
664
+ node.matrix.post_scale(factor, factor, insert[0], insert[1])
665
+ elif hasattr(entity.dxf, "width") and entity.dxf.width != 0:
666
+ # 72pt = 1inch
667
+ # We are guessing....
668
+ fontwidth = len(entity.dxf.text) * 6 * 25.4 / 72 # in mm
669
+ factor = entity.dxf.width / fontwidth
670
+ node.matrix.post_scale(factor, factor, insert[0], insert[1])
671
+
672
+ node.matrix.post_scale(self.scale, -self.scale)
673
+ node.matrix.post_translate_y(self.elements.device.view.unit_height)
674
+ self.check_for_attributes(node, entity)
675
+ e_list.append(node)
676
+ return
677
+ elif dxftype == "SOLID" or dxftype == "TRACE":
678
+ # https://ezdxf.readthedocs.io/en/stable/dxfentities/solid.html
679
+ element = Path()
680
+ element.move((entity[0][0], entity[0][1]))
681
+ element.line((entity[1][0], entity[1][1]))
682
+ element.line((entity[2][0], entity[2][1]))
683
+ element.line((entity[3][0], entity[3][1]))
684
+ element.closed()
685
+ element.fill = Color("black")
686
+ element.values[SVG_ATTR_VECTOR_EFFECT] = SVG_VALUE_NON_SCALING_STROKE
687
+ element.transform.post_scale(self.scale, -self.scale)
688
+ element.transform.post_translate_y(self.elements.device.view.unit_height)
689
+
690
+ path = abs(Path(element))
691
+ node = context_node.add(
692
+ path=path,
693
+ type="elem path",
694
+ stroke_scale=False,
695
+ stroke_width=self.std_stroke,
696
+ )
697
+ self.check_for_attributes(node, entity)
698
+ e_list.append(node)
699
+ return
700
+ elif dxftype == "SPLINE":
701
+ element = Path()
702
+ try:
703
+ for b in entity.construction_tool().bezier_decomposition():
704
+ if len(element) == 0:
705
+ element.move((b[0][0], b[0][1]))
706
+ if len(b) == 4:
707
+ element.cubic(
708
+ (b[1][0], b[1][1]), (b[2][0], b[2][1]), (b[3][0], b[3][1])
709
+ )
710
+ elif len(b) == 3:
711
+ element.quad((b[1][0], b[1][1]), (b[2][0], b[2][1]))
712
+ except (AttributeError, TypeError):
713
+ # Fallback for rational b-splines.
714
+ try:
715
+ # Flattening version 0.15
716
+ for q in entity.flattening(1, 15):
717
+ element.line((q[0], q[1]))
718
+ except AttributeError:
719
+ # Version before 0.15
720
+ try:
721
+ for (
722
+ bezier
723
+ ) in entity.construction_tool().cubic_bezier_approximation(4):
724
+ b = bezier.control_points
725
+ if len(b) == 4:
726
+ element.cubic(
727
+ (b[1][0], b[1][1]),
728
+ (b[2][0], b[2][1]),
729
+ (b[3][0], b[3][1]),
730
+ )
731
+ elif len(b) == 3:
732
+ element.quad((b[1][0], b[1][1]), (b[2][0], b[2][1]))
733
+ except (AttributeError, TypeError):
734
+ # Fallback for versions of EZDXF prior to 0.13
735
+ element.move(entity.control_points[0])
736
+ for i in range(1, entity.dxf.n_control_points):
737
+ element.line(entity.control_points[i])
738
+ if entity.closed:
739
+ element.closed()
740
+ element.values[SVG_ATTR_VECTOR_EFFECT] = SVG_VALUE_NON_SCALING_STROKE
741
+ element.transform.post_scale(self.scale, -self.scale)
742
+ element.transform.post_translate_y(self.elements.device.view.unit_height)
743
+ path = abs(element)
744
+ if len(path) != 0 and not isinstance(path[0], Move):
745
+ path = Move(path.first_point) + path
746
+ node = context_node.add(
747
+ path=path,
748
+ type="elem path",
749
+ stroke_scale=False,
750
+ stroke_width=self.std_stroke,
751
+ )
752
+ self.check_for_attributes(node, entity)
753
+ e_list.append(node)
754
+ return
755
+ elif dxftype == "INSERT":
756
+ # Insert creates virtual grouping.
757
+ context_node = context_node.add(type="group")
758
+ for e in entity.virtual_entities():
759
+ if e is None:
760
+ continue
761
+ self.parse(e, context_node, e_list)
762
+ return
763
+ else:
764
+ # We need a channel comment here so that this is not silently ignored.
765
+ # print (f"Unknown type: {dxftype}")
766
+ return # Might be something unsupported.