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
@@ -1,1232 +1,1470 @@
1
- from copy import copy
2
- from math import ceil, isnan, sqrt
3
-
4
- import wx
5
- from PIL import Image
6
-
7
- from meerk40t.core.elements.element_types import place_nodes
8
- from meerk40t.core.node.node import Fillrule, Linecap, Linejoin, Node
9
- from meerk40t.svgelements import (
10
- Arc,
11
- Close,
12
- Color,
13
- CubicBezier,
14
- Line,
15
- Matrix,
16
- Move,
17
- QuadraticBezier,
18
- )
19
-
20
- from ..core.cutcode.cubiccut import CubicCut
21
- from ..core.cutcode.cutcode import CutCode
22
- from ..core.cutcode.dwellcut import DwellCut
23
- from ..core.cutcode.gotocut import GotoCut
24
- from ..core.cutcode.homecut import HomeCut
25
- from ..core.cutcode.inputcut import InputCut
26
- from ..core.cutcode.linecut import LineCut
27
- from ..core.cutcode.outputcut import OutputCut
28
- from ..core.cutcode.plotcut import PlotCut
29
- from ..core.cutcode.quadcut import QuadCut
30
- from ..core.cutcode.rastercut import RasterCut
31
- from ..core.cutcode.waitcut import WaitCut
32
- from ..tools.geomstr import ( # , TYPE_RAMP
33
- TYPE_ARC,
34
- TYPE_CUBIC,
35
- TYPE_LINE,
36
- TYPE_QUAD,
37
- Geomstr,
38
- )
39
- from .fonts import wxfont_to_svg
40
- from .icons import icons8_image
41
- from .zmatrix import ZMatrix
42
-
43
- DRAW_MODE_FILLS = 0x000001
44
- DRAW_MODE_GUIDES = 0x000002
45
- DRAW_MODE_GRID = 0x000004
46
- DRAW_MODE_LASERPATH = 0x000008
47
- DRAW_MODE_RETICLE = 0x000010
48
- DRAW_MODE_SELECTION = 0x000020
49
- DRAW_MODE_STROKES = 0x000040
50
- DRAW_MODE_CACHE = 0x000080 # Set means do not cache.
51
- DRAW_MODE_REFRESH = 0x000100
52
- DRAW_MODE_ANIMATE = 0x000200
53
- DRAW_MODE_PATH = 0x000400
54
- DRAW_MODE_IMAGE = 0x000800
55
- DRAW_MODE_TEXT = 0x001000
56
- DRAW_MODE_BACKGROUND = 0x002000
57
- DRAW_MODE_POINTS = 0x004000
58
- DRAW_MODE_REGMARKS = 0x008000
59
- DRAW_MODE_VARIABLES = 0x010000
60
-
61
- DRAW_MODE_ICONS = 0x0040000
62
- DRAW_MODE_INVERT = 0x400000
63
- DRAW_MODE_FLIPXY = 0x800000
64
- DRAW_MODE_LINEWIDTH = 0x1000000
65
- DRAW_MODE_ALPHABLACK = 0x2000000 # Set means do not alphablack images
66
- DRAW_MODE_ORIGIN = 0x4000000
67
- DRAW_MODE_EDIT = 0x8000000
68
-
69
-
70
- def swizzlecolor(c):
71
- if c is None:
72
- return None
73
- if isinstance(c, int):
74
- c = Color(argb=c)
75
- try:
76
- return c.blue << 16 | c.green << 8 | c.red
77
- except (ValueError, TypeError):
78
- return None
79
-
80
-
81
- def as_wx_color(c):
82
- if c is None:
83
- return None
84
- if isinstance(c, int):
85
- c = Color(argb=c)
86
- return wx.Colour(red=c.red, green=c.green, blue=c.blue, alpha=c.alpha)
87
-
88
-
89
- def svgfont_to_wx(textnode):
90
- """
91
- Translates all svg-text-properties to their wxfont-equivalents
92
- @param textnode:
93
- @return:
94
- """
95
- if not hasattr(textnode, "wxfont"):
96
- textnode.wxfont = wx.Font()
97
- wxfont = textnode.wxfont
98
- # if the font_list is empty, then we do have a not properly initialised textnode,
99
- # that needs to be resolved...
100
- if textnode.font_family is None:
101
- wxfont_to_svg(textnode)
102
-
103
- svg_to_wx_family(textnode, wxfont)
104
- svg_to_wx_fontstyle(textnode, wxfont)
105
- try:
106
- wxfont.SetNumericWeight(textnode.weight) # Gets numeric weight.
107
- except AttributeError:
108
- # Running version wx4.0. No set Numeric Weight, can only set bold or normal.
109
- weight = textnode.weight
110
- wxfont.SetWeight(
111
- wx.FONTWEIGHT_BOLD if weight > 600 else wx.FONTWEIGHT_NORMAL
112
- ) # Gets numeric weight.
113
-
114
- font_size = textnode.font_size
115
- try:
116
- wxfont.SetFractionalPointSize(font_size)
117
- except AttributeError:
118
- # If we cannot set the fractional point size, we scale up to adjust to fractional levels.
119
- integer_font_size = int(round(font_size))
120
- scale = font_size / integer_font_size
121
- if scale != 1.0:
122
- textnode.matrix.pre_scale(scale, scale)
123
- textnode.font_size = integer_font_size
124
- wxfont.SetPointSize(integer_font_size)
125
- wxfont.SetUnderlined(textnode.underline)
126
- wxfont.SetStrikethrough(textnode.strikethrough)
127
-
128
-
129
- def svg_to_wx_family(textnode, wxfont):
130
- font_list = textnode.font_list
131
- # if font_list is None:
132
- # print("Fontlist is empty...")
133
- # else:
134
- # print ("Fontlist was: ", font_list)
135
- for ff in font_list:
136
- if ff == "fantasy":
137
- family = wx.FONTFAMILY_DECORATIVE
138
- wxfont.SetFamily(family)
139
- return
140
- elif ff == "serif":
141
- family = wx.FONTFAMILY_ROMAN
142
- wxfont.SetFamily(family)
143
- return
144
- elif ff == "cursive":
145
- family = wx.FONTFAMILY_SCRIPT
146
- wxfont.SetFamily(family)
147
- return
148
- elif ff == "sans-serif":
149
- family = wx.FONTFAMILY_SWISS
150
- wxfont.SetFamily(family)
151
- return
152
- elif ff == "monospace":
153
- family = wx.FONTFAMILY_TELETYPE
154
- wxfont.SetFamily(family)
155
- return
156
- if wxfont.SetFaceName(ff):
157
- # We found a correct face name.
158
- return
159
-
160
-
161
- def svg_to_wx_fontstyle(textnode, wxfont):
162
- ff = textnode.font_style
163
- if ff == "normal":
164
- fontstyle = wx.FONTSTYLE_NORMAL
165
- elif ff == "italic":
166
- fontstyle = wx.FONTSTYLE_ITALIC
167
- elif ff == "oblique":
168
- fontstyle = wx.FONTSTYLE_SLANT
169
- else:
170
- fontstyle = wx.FONTSTYLE_NORMAL
171
- wxfont.SetStyle(fontstyle)
172
-
173
-
174
- class LaserRender:
175
- """
176
- Laser Render provides GUI relevant methods of displaying the given elements.
177
- """
178
-
179
- def __init__(self, context):
180
- self.context = context
181
- self.context.setting(int, "draw_mode", 0)
182
- self.pen = wx.Pen()
183
- self.brush = wx.Brush()
184
- self.color = wx.Colour()
185
-
186
- def render_tree(self, node, gc, draw_mode=None, zoomscale=1.0, alpha=255):
187
- if not self.render_node(
188
- node, gc, draw_mode=draw_mode, zoomscale=zoomscale, alpha=alpha
189
- ):
190
- for c in node.children:
191
- self.render_tree(
192
- c, gc, draw_mode=draw_mode, zoomscale=zoomscale, alpha=alpha
193
- )
194
-
195
- def render(self, nodes, gc, draw_mode=None, zoomscale=1.0, alpha=255):
196
- """
197
- Render scene information.
198
-
199
- @param nodes: Node types to render.
200
- @param gc: graphics context
201
- @param draw_mode: draw mode flags for rendering
202
- @param zoomscale: zoomscale at which to render nodes
203
- @param alpha: render transparency
204
- @return:
205
- """
206
- if draw_mode is None:
207
- draw_mode = self.context.draw_mode
208
- if draw_mode & (DRAW_MODE_TEXT | DRAW_MODE_IMAGE | DRAW_MODE_PATH) != 0:
209
- if draw_mode & DRAW_MODE_PATH: # Do not draw paths.
210
- path_elements = (
211
- "elem ellipse",
212
- "elem path",
213
- "elem point",
214
- "elem polyline",
215
- "elem rect",
216
- "elem line",
217
- "effect hatch",
218
- "effect wobble",
219
- )
220
- nodes = [e for e in nodes if e.type not in path_elements]
221
- if draw_mode & DRAW_MODE_IMAGE: # Do not draw images.
222
- nodes = [e for e in nodes if hasattr(e, "as_image")]
223
- if draw_mode & DRAW_MODE_TEXT: # Do not draw text.
224
- nodes = [e for e in nodes if e.type != "elem text"]
225
- if draw_mode & DRAW_MODE_REGMARKS: # Do not draw regmarked items.
226
- nodes = [e for e in nodes if e._parent.type != "branch reg"]
227
- nodes = [e for e in nodes if not e.type in place_nodes]
228
- _nodes = list(nodes)
229
- variable_translation = draw_mode & DRAW_MODE_VARIABLES
230
- nodecopy = [e for e in _nodes]
231
- self.validate_text_nodes(nodecopy, variable_translation)
232
-
233
- for node in _nodes:
234
- if node.type == "reference":
235
- # Reference nodes should be drawn per-usual, recurse.
236
- self.render_node(
237
- node.node, gc, draw_mode=draw_mode, zoomscale=zoomscale, alpha=alpha
238
- )
239
- continue
240
- self.render_node(
241
- node, gc, draw_mode=draw_mode, zoomscale=zoomscale, alpha=alpha
242
- )
243
-
244
- def render_node(self, node, gc, draw_mode=None, zoomscale=1.0, alpha=255):
245
- """
246
- Renders the specific node.
247
- @param node:
248
- @param gc:
249
- @param draw_mode:
250
- @param zoomscale:
251
- @param alpha:
252
- @return: True if rendering was done, False if rendering could not be done.
253
- """
254
- if hasattr(node, "is_visible"):
255
- if not node.is_visible:
256
- return False
257
- if hasattr(node, "output"):
258
- if not node.output:
259
- return False
260
-
261
- try:
262
- # Try to draw node, assuming it already has a known render method.
263
- node.draw(node, gc, draw_mode, zoomscale=zoomscale, alpha=alpha)
264
- return True
265
- except AttributeError:
266
- # No known render method, we must define the function to draw nodes.
267
- if node.type in (
268
- "elem path",
269
- "elem ellipse",
270
- "elem rect",
271
- "elem line",
272
- "elem polyline",
273
- "effect hatch",
274
- "effect wobble",
275
- ):
276
- node.draw = self.draw_vector
277
- node._make_cache = self.cache_geomstr
278
- elif node.type == "elem point":
279
- node.draw = self.draw_point_node
280
- elif node.type in place_nodes:
281
- node.draw = self.draw_placement_node
282
- elif hasattr(node, "as_image"):
283
- node.draw = self.draw_image_node
284
- elif node.type == "elem text":
285
- node.draw = self.draw_text_node
286
- elif node.type == "cutcode":
287
- node.draw = self.draw_cutcode_node
288
- else:
289
- return False
290
- # We have now defined that function, draw it.
291
- node.draw(node, gc, draw_mode, zoomscale=zoomscale, alpha=alpha)
292
- return True
293
-
294
- def make_path(self, gc, path):
295
- """
296
- Takes a svgelements.Path and converts it to a GraphicsContext.Graphics Path
297
- """
298
- p = gc.CreatePath()
299
- init = False
300
- for e in path.segments(transformed=True):
301
- if isinstance(e, Move):
302
- p.MoveToPoint(e.end[0], e.end[1])
303
- init = True
304
- elif isinstance(e, Line):
305
- if not init:
306
- init = True
307
- p.MoveToPoint(e.start[0], e.start[1])
308
- p.AddLineToPoint(e.end[0], e.end[1])
309
- elif isinstance(e, Close):
310
- if not init:
311
- init = True
312
- p.MoveToPoint(e.start[0], e.start[1])
313
- p.CloseSubpath()
314
- elif isinstance(e, QuadraticBezier):
315
- if not init:
316
- init = True
317
- p.MoveToPoint(e.start[0], e.start[1])
318
- p.AddQuadCurveToPoint(e.control[0], e.control[1], e.end[0], e.end[1])
319
- elif isinstance(e, CubicBezier):
320
- if not init:
321
- init = True
322
- p.MoveToPoint(e.start[0], e.start[1])
323
- p.AddCurveToPoint(
324
- e.control1[0],
325
- e.control1[1],
326
- e.control2[0],
327
- e.control2[1],
328
- e.end[0],
329
- e.end[1],
330
- )
331
- elif isinstance(e, Arc):
332
- if not init:
333
- init = True
334
- p.MoveToPoint(e.start[0], e.start[1])
335
- for curve in e.as_cubic_curves():
336
- p.AddCurveToPoint(
337
- curve.control1[0],
338
- curve.control1[1],
339
- curve.control2[0],
340
- curve.control2[1],
341
- curve.end[0],
342
- curve.end[1],
343
- )
344
- return p
345
-
346
- def make_geomstr(self, gc, path, node=None):
347
- """
348
- Takes a Geomstr path and converts it to a GraphicsContext.Graphics path
349
-
350
- This also creates a point list of the relevant nodes and creates a ._cache_edit value to be used by node
351
- editing view.
352
- """
353
- p = gc.CreatePath()
354
- pts = list()
355
- for subpath in path.as_subpaths():
356
- if len(subpath) == 0:
357
- continue
358
- end = None
359
- for e in subpath.segments:
360
- seg_type = int(e[2].real)
361
- start = e[0]
362
- if end != start:
363
- # Start point does not equal previous end point.
364
- p.MoveToPoint(start.real, start.imag)
365
- c0 = e[1]
366
- c1 = e[3]
367
- end = e[4]
368
-
369
- if seg_type == TYPE_LINE:
370
- p.AddLineToPoint(end.real, end.imag)
371
- pts.append(start)
372
- pts.append(end)
373
- elif seg_type == TYPE_QUAD:
374
- p.AddQuadCurveToPoint(c0.real, c0.imag, end.real, end.imag)
375
- pts.append(c0)
376
- pts.append(start)
377
- pts.append(end)
378
- elif seg_type == TYPE_ARC:
379
- radius = Geomstr.arc_radius(None, line=e)
380
- center = Geomstr.arc_center(None, line=e)
381
- start_t = Geomstr.angle(None, center, start)
382
- end_t = Geomstr.angle(None, center, end)
383
- p.AddArc(
384
- center.real,
385
- center.imag,
386
- radius,
387
- start_t,
388
- end_t,
389
- clockwise="ccw" != Geomstr.orientation(None, start, c0, end),
390
- )
391
- pts.append(c0)
392
- pts.append(start)
393
- pts.append(end)
394
- elif seg_type == TYPE_CUBIC:
395
- p.AddCurveToPoint(
396
- c0.real, c0.imag, c1.real, c1.imag, end.real, end.imag
397
- )
398
- pts.append(c0)
399
- pts.append(c1)
400
- pts.append(start)
401
- pts.append(end)
402
- else:
403
- print(f"Unknown seg_type: {seg_type}")
404
- if subpath.first_point == end:
405
- p.CloseSubpath()
406
- if node is not None:
407
- graphics_path_2 = gc.CreatePath()
408
- for pt in pts:
409
- graphics_path_2.AddCircle(pt.real, pt.imag, 5000)
410
- node._cache_edit = graphics_path_2
411
-
412
- return p
413
-
414
- def _set_linecap_by_node(self, node):
415
- if not hasattr(node, "linecap") or node.linecap is None:
416
- self.pen.SetCap(wx.CAP_ROUND)
417
- else:
418
- if node.linecap == Linecap.CAP_BUTT:
419
- self.pen.SetCap(wx.CAP_BUTT)
420
- elif node.linecap == Linecap.CAP_ROUND:
421
- self.pen.SetCap(wx.CAP_ROUND)
422
- elif node.linecap == Linecap.CAP_SQUARE:
423
- self.pen.SetCap(wx.CAP_PROJECTING)
424
- else:
425
- self.pen.SetCap(wx.CAP_ROUND)
426
-
427
- def _set_linejoin_by_node(self, node):
428
- if not hasattr(node, "linejoin") or node.linejoin is None:
429
- self.pen.SetJoin(wx.JOIN_MITER)
430
- else:
431
- if node.linejoin == Linejoin.JOIN_ARCS:
432
- self.pen.SetJoin(wx.JOIN_ROUND)
433
- elif node.linejoin == Linejoin.JOIN_BEVEL:
434
- self.pen.SetJoin(wx.JOIN_BEVEL)
435
- elif node.linejoin == Linejoin.JOIN_MITER:
436
- self.pen.SetJoin(wx.JOIN_MITER)
437
- elif node.linejoin == Linejoin.JOIN_MITER_CLIP:
438
- self.pen.SetJoin(wx.JOIN_MITER)
439
- else:
440
- self.pen.SetJoin(wx.JOIN_ROUND)
441
-
442
- def _get_fillstyle(self, node):
443
- if not hasattr(node, "fillrule") or node.fillrule is None:
444
- return wx.WINDING_RULE
445
- else:
446
- if node.fillrule == Fillrule.FILLRULE_EVENODD:
447
- return wx.ODDEVEN_RULE
448
- else:
449
- return wx.WINDING_RULE
450
-
451
- def _set_penwidth(self, width):
452
- try:
453
- if isnan(width):
454
- width = 1.0
455
- try:
456
- self.pen.SetWidth(width)
457
- except TypeError:
458
- self.pen.SetWidth(int(width))
459
- except OverflowError:
460
- pass # Exceeds 32 bit signed integer.
461
-
462
- def set_pen(self, gc, stroke, alpha=None):
463
- c = stroke
464
- if c is not None and c != "none":
465
- swizzle_color = swizzlecolor(c)
466
- if alpha is None:
467
- alpha = c.alpha
468
- self.color.SetRGBA(swizzle_color | alpha << 24) # wx has BBGGRR
469
- self.pen.SetColour(self.color)
470
- gc.SetPen(self.pen)
471
- else:
472
- gc.SetPen(wx.TRANSPARENT_PEN)
473
-
474
- def set_brush(self, gc, fill, alpha=None):
475
- c = fill
476
- if c is not None and c != "none":
477
- swizzle_color = swizzlecolor(c)
478
- if alpha is None:
479
- alpha = c.alpha
480
- self.color.SetRGBA(swizzle_color | alpha << 24) # wx has BBGGRR
481
- self.brush.SetColour(self.color)
482
- gc.SetBrush(self.brush)
483
- else:
484
- gc.SetBrush(wx.TRANSPARENT_BRUSH)
485
-
486
- def draw_cutcode_node(
487
- self,
488
- node: Node,
489
- gc: wx.GraphicsContext,
490
- draw_mode,
491
- zoomscale=1.0,
492
- alpha=255,
493
- x: int = 0,
494
- y: int = 0,
495
- ):
496
- cutcode = node.cutcode
497
- self.draw_cutcode(cutcode, gc, x, y)
498
-
499
- def draw_cutcode(
500
- self, cutcode: CutCode, gc: wx.GraphicsContext, x: int = 0, y: int = 0
501
- ):
502
- """
503
- Draw cutcode object into wxPython graphics code.
504
-
505
- This code accepts x,y offset values. The cutcode laser offset can be set with a
506
- command with the rest of the cutcode remaining the same. So drawing the cutcode
507
- requires knowing what, if any offset is currently being applied.
508
-
509
- @param cutcode: flat cutcode object to draw.
510
- @param gc: wx.graphics context
511
- @param x: offset in x direction
512
- @param y: offset in y direction
513
- @return:
514
- """
515
- highlight_color = Color("magenta")
516
- wx_color = wx.Colour(swizzlecolor(highlight_color))
517
- highlight_pen = wx.Pen(wx_color, width=3, style=wx.PENSTYLE_SHORT_DASH)
518
- p = None
519
- last_point = None
520
- color = None
521
- for cut in cutcode:
522
- if cut.highlighted:
523
- c = highlight_color
524
- else:
525
- c = cut.color
526
- if c is None:
527
- c = 0
528
- try:
529
- if c.value is None:
530
- c = 0
531
- except AttributeError:
532
- pass
533
- if c is not color:
534
- color = c
535
- last_point = None
536
- if p is not None:
537
- gc.StrokePath(p)
538
- del p
539
- p = gc.CreatePath()
540
- self._set_penwidth(7.0)
541
- self.set_pen(gc, c, alpha=127)
542
- start = cut.start
543
- end = cut.end
544
- if p is None:
545
- p = gc.CreatePath()
546
- if last_point != start:
547
- p.MoveToPoint(start[0] + x, start[1] + y)
548
- if isinstance(cut, LineCut):
549
- # Standard line cut. Applies to path object.
550
- p.AddLineToPoint(end[0] + x, end[1] + y)
551
- elif isinstance(cut, QuadCut):
552
- # Standard quadratic bezier cut
553
- p.AddQuadCurveToPoint(
554
- cut.c()[0] + x, cut.c()[1] + y, end[0] + x, end[1] + y
555
- )
556
- elif isinstance(cut, CubicCut):
557
- # Standard cubic bezier cut
558
- p.AddCurveToPoint(
559
- cut.c1()[0] + x,
560
- cut.c1()[1] + y,
561
- cut.c2()[0] + x,
562
- cut.c2()[1] + y,
563
- end[0] + x,
564
- end[1] + y,
565
- )
566
- elif isinstance(cut, RasterCut):
567
- # Rastercut object.
568
- image = cut.image
569
- gc.PushState()
570
- matrix = Matrix.scale(cut.step_x, cut.step_y)
571
- matrix.post_translate(
572
- cut.offset_x + x, cut.offset_y + y
573
- ) # Adjust image xy
574
- gc.ConcatTransform(wx.GraphicsContext.CreateMatrix(gc, ZMatrix(matrix)))
575
- try:
576
- cache = cut._cache
577
- cache_id = cut._cache_id
578
- except AttributeError:
579
- cache = None
580
- cache_id = -1
581
- if cache_id != id(image):
582
- # Cached image is invalid.
583
- cache = None
584
- if cache is None:
585
- # No valid cache. Generate.
586
- cut._cache_width, cut._cache_height = image.size
587
- try:
588
- cut._cache = self.make_thumbnail(image, maximum=5000)
589
- except (MemoryError, RuntimeError):
590
- cut._cache = None
591
- cut._cache_id = id(image)
592
- if cut._cache is not None:
593
- # Cache exists and is valid.
594
- gc.DrawBitmap(cut._cache, 0, 0, cut._cache_width, cut._cache_height)
595
- if cut.highlighted:
596
- # gc.SetBrush(wx.RED_BRUSH)
597
- gc.SetPen(highlight_pen)
598
- gc.DrawRectangle(0, 0, cut._cache_width, cut._cache_height)
599
- else:
600
- # Image was too large to cache, draw a red rectangle instead.
601
- gc.SetBrush(wx.RED_BRUSH)
602
- gc.DrawRectangle(0, 0, cut._cache_width, cut._cache_height)
603
- gc.DrawBitmap(
604
- icons8_image.GetBitmap(),
605
- 0,
606
- 0,
607
- cut._cache_width,
608
- cut._cache_height,
609
- )
610
- gc.PopState()
611
- elif isinstance(cut, PlotCut):
612
- p.MoveToPoint(start[0] + x, start[1] + y)
613
- for ox, oy, pon, px, py in cut.plot:
614
- if pon == 0:
615
- p.MoveToPoint(px + x, py + y)
616
- else:
617
- p.AddLineToPoint(px + x, py + y)
618
- elif isinstance(cut, DwellCut):
619
- pass
620
- elif isinstance(cut, WaitCut):
621
- pass
622
- elif isinstance(cut, HomeCut):
623
- p.MoveToPoint(0, 0)
624
- elif isinstance(cut, GotoCut):
625
- p.MoveToPoint(start[0] + x, start[1] + y)
626
- elif isinstance(cut, InputCut):
627
- pass
628
- elif isinstance(cut, OutputCut):
629
- pass
630
- last_point = end
631
- if p is not None:
632
- gc.StrokePath(p)
633
- del p
634
-
635
- def cache_geomstr(self, node, gc):
636
- try:
637
- matrix = node.matrix
638
- node._cache_matrix = copy(matrix)
639
- except AttributeError:
640
- node._cache_matrix = Matrix()
641
- geom = node.as_geometry()
642
- cache = self.make_geomstr(gc, geom, node=node)
643
- node._cache = cache
644
-
645
- def draw_vector(self, node, gc, draw_mode, zoomscale=1.0, alpha=255):
646
- """
647
- Draw routine for vector objects.
648
-
649
- Vector objects are expected to have a _make_cache routine which attaches a `_cache_matrix` and a `_cache`
650
- attribute to them which can be drawn as a GraphicsPath.
651
- """
652
- if hasattr(node, "mktext"):
653
- newtext = self.context.elements.wordlist_translate(
654
- node.mktext, elemnode=node, increment=False
655
- )
656
- oldtext = getattr(node, "_translated_text", "")
657
- if newtext != oldtext:
658
- node._translated_text = newtext
659
- kernel = self.context.elements.kernel
660
- for property_op in kernel.lookup_all("path_updater/.*"):
661
- property_op(kernel.root, node)
662
- if hasattr(node, "_cache"):
663
- node._cache = None
664
- try:
665
- matrix = node.matrix
666
- except AttributeError:
667
- matrix = Matrix()
668
- gc.PushState()
669
- try:
670
- cache = node._cache
671
- except AttributeError:
672
- cache = None
673
- if cache is None:
674
- node._make_cache(node, gc)
675
-
676
- try:
677
- cache_matrix = node._cache_matrix
678
- except AttributeError:
679
- cache_matrix = None
680
-
681
- stroke_factor = 1
682
- if matrix != cache_matrix:
683
- # Calculate the relative change matrix and apply it to this shape.
684
- q = ~node._cache_matrix * matrix
685
- gc.ConcatTransform(wx.GraphicsContext.CreateMatrix(gc, ZMatrix(q)))
686
- # Applying the matrix will scale our stroke, so we scale the stroke back down.
687
- if q.determinant == 0:
688
- # That should not be the case, but is often true for degenerate objects...
689
- stroke_factor = 1.0
690
- else:
691
- stroke_factor = 1.0 / sqrt(abs(q.determinant))
692
- self._set_linecap_by_node(node)
693
- self._set_linejoin_by_node(node)
694
- sw = node.implied_stroke_width * stroke_factor
695
- if draw_mode & DRAW_MODE_LINEWIDTH:
696
- # No stroke rendering.
697
- sw = 1000
698
- self._set_penwidth(sw)
699
- self.set_pen(
700
- gc,
701
- node.stroke,
702
- alpha=alpha,
703
- )
704
- self.set_brush(gc, node.fill, alpha=alpha)
705
- if draw_mode & DRAW_MODE_FILLS == 0 and node.fill is not None:
706
- gc.FillPath(node._cache, fillStyle=self._get_fillstyle(node))
707
- if draw_mode & DRAW_MODE_STROKES == 0 and node.stroke is not None:
708
- gc.StrokePath(node._cache)
709
-
710
- if node.emphasized and draw_mode & DRAW_MODE_EDIT:
711
- try:
712
- edit = node._cache_edit
713
- gc.StrokePath(edit)
714
- except AttributeError:
715
- pass
716
- gc.PopState()
717
-
718
- def draw_placement_node(self, node, gc, draw_mode, zoomscale=1.0, alpha=255):
719
- """Default draw routine for the placement operation."""
720
- if node.type == "place current":
721
- # no idea how to draw yet...
722
- return
723
- gc.PushState()
724
- matrix = Matrix()
725
- if node.rotation is not None and node.rotation != 0:
726
- matrix.post_rotate(node.rotation, node.x, node.y)
727
- gc.ConcatTransform(wx.GraphicsContext.CreateMatrix(gc, ZMatrix(matrix)))
728
- # First x
729
- dif = 20 * zoomscale
730
- x_from = node.x
731
- y_from = node.y
732
- if node.corner == 0:
733
- # Top Left
734
- x_to = x_from + dif
735
- y_to = y_from + dif
736
- x_sign = 1
737
- y_sign = 1
738
- elif node.corner == 1:
739
- # Top Right
740
- x_to = x_from - dif
741
- y_to = y_from + dif
742
- x_sign = -1
743
- y_sign = 1
744
- elif node.corner == 2:
745
- # Bottom Right
746
- x_to = x_from - dif
747
- y_to = y_from - dif
748
- x_sign = -1
749
- y_sign = -1
750
- elif node.corner == 3:
751
- # Bottom Left
752
- x_to = x_from + dif
753
- y_to = y_from - dif
754
- x_sign = 1
755
- y_sign = -1
756
- else:
757
- # Center
758
- x_from -= dif
759
- y_from -= dif
760
- x_to = x_from + 2 * dif
761
- y_to = y_from + 2 * dif
762
- x_sign = 1
763
- y_sign = 1
764
- rpen = wx.Pen(wx.Colour(red=255, green=0, blue=0, alpha=alpha))
765
- gpen = wx.Pen(wx.Colour(red=0, green=255, blue=0, alpha=alpha))
766
- gc.SetPen(rpen)
767
- dif = 5 * zoomscale
768
- gc.StrokeLine(x_from, node.y, x_to, node.y)
769
- gc.StrokeLine(x_to - x_sign * dif, node.y - y_sign * dif, x_to, node.y)
770
- gc.StrokeLine(x_to - x_sign * dif, node.y + y_sign * dif, x_to, node.y)
771
- gc.SetPen(gpen)
772
- gc.StrokeLine(node.x, y_from, node.x, y_to)
773
- gc.StrokeLine(node.x - x_sign * dif, y_to - y_sign * dif, node.x, y_to)
774
- gc.StrokeLine(node.x + x_sign * dif, y_to - y_sign * dif, node.x, y_to)
775
-
776
- loops = 1
777
- if hasattr(node, "loops") and node.loops is not None:
778
- # No zero or negative values please
779
- loops = max(1, node.loops)
780
- if loops > 1:
781
- symbol = f"{loops}x"
782
- font_size = 10 * zoomscale
783
- if font_size < 1.0:
784
- font_size = 1.0
785
- try:
786
- font = wx.Font(
787
- font_size,
788
- wx.FONTFAMILY_SWISS,
789
- wx.FONTSTYLE_NORMAL,
790
- wx.FONTWEIGHT_NORMAL,
791
- )
792
- except TypeError:
793
- font = wx.Font(
794
- int(font_size),
795
- wx.FONTFAMILY_SWISS,
796
- wx.FONTSTYLE_NORMAL,
797
- wx.FONTWEIGHT_NORMAL,
798
- )
799
- gc.SetFont(font, wx.Colour(red=255, green=0, blue=0, alpha=alpha))
800
- (t_width, t_height) = gc.GetTextExtent(symbol)
801
- x = (x_from + x_to) / 2 - t_width / 2
802
- y = (y_from + y_to) / 2 - t_height / 2
803
- # is corner center then shift it a bit more
804
- if node.corner == 4:
805
- x += 0.25 * (x_to - x_from)
806
- y += 0.25 * (y_to - y_from)
807
- gc.DrawText(symbol, x, y)
808
-
809
- gc.PopState()
810
-
811
- def draw_point_node(self, node, gc, draw_mode, zoomscale=1.0, alpha=255):
812
- """Default draw routine for the laser path element."""
813
- if draw_mode & DRAW_MODE_POINTS:
814
- return
815
- point = node.point
816
- gc.PushState()
817
- gc.SetPen(wx.BLACK_PEN)
818
- dif = 5 * zoomscale
819
- gc.StrokeLine(point.x - dif, point.y, point.x + dif, point.y)
820
- gc.StrokeLine(point.x, point.y - dif, point.x, point.y + dif)
821
- gc.PopState()
822
-
823
- def draw_text_node(self, node, gc, draw_mode=0, zoomscale=1.0, alpha=255):
824
- if node is None:
825
- return
826
- text = node.text
827
- if text is None or text == "":
828
- return
829
-
830
- try:
831
- matrix = node.matrix
832
- except AttributeError:
833
- matrix = None
834
-
835
- svgfont_to_wx(node)
836
- font = node.wxfont
837
-
838
- gc.PushState()
839
- if matrix is not None and not matrix.is_identity():
840
- gc.ConcatTransform(wx.GraphicsContext.CreateMatrix(gc, ZMatrix(matrix)))
841
- #
842
- # sw = node.implied_stroke_width
843
- # if draw_mode & DRAW_MODE_LINEWIDTH:
844
- # # No stroke rendering.
845
- # sw = 1000
846
- # self._set_penwidth(sw)
847
- # self.set_pen(
848
- # gc,
849
- # node.stroke,
850
- # alpha=alpha,
851
- # )
852
- # self.set_brush(gc, node.fill, alpha=255)
853
-
854
- if node.fill is None or node.fill == "none":
855
- fill_color = wx.BLACK
856
- else:
857
- fill_color = as_wx_color(node.fill)
858
- gc.SetFont(font, fill_color)
859
-
860
- if draw_mode & DRAW_MODE_VARIABLES:
861
- # Only if flag show the translated values
862
- text = self.context.elements.wordlist_translate(
863
- text, elemnode=node, increment=False
864
- )
865
- if node.texttransform is not None:
866
- ttf = node.texttransform.lower()
867
- if ttf == "capitalize":
868
- text = text.capitalize()
869
- elif ttf == "uppercase":
870
- text = text.upper()
871
- if ttf == "lowercase":
872
- text = text.lower()
873
- xmin, ymin, xmax, ymax = node.bbox(transformed=False)
874
- height = ymax - ymin
875
- width = xmax - xmin
876
- dy = 0
877
- dx = 0
878
- if node.anchor == "middle":
879
- dx -= width / 2
880
- elif node.anchor == "end":
881
- dx -= width
882
- gc.DrawText(text, dx, dy)
883
- gc.PopState()
884
-
885
- def draw_image_node(self, node, gc, draw_mode, zoomscale=1.0, alpha=255):
886
- image, bounds = node.as_image()
887
- gc.PushState()
888
-
889
- try:
890
- image = node.active_image
891
- matrix = node.active_matrix
892
- bounds = 0, 0, image.width, image.height
893
- if matrix is not None and not matrix.is_identity():
894
- gc.ConcatTransform(wx.GraphicsContext.CreateMatrix(gc, ZMatrix(matrix)))
895
- except AttributeError:
896
- pass
897
-
898
- cache = None
899
- try:
900
- cache = node._cache
901
- except AttributeError:
902
- pass
903
- if cache is None:
904
- try:
905
- max_allowed = node.max_allowed
906
- except AttributeError:
907
- max_allowed = 2048
908
- node._cache_width, node._cache_height = image.size
909
- node._cache = self.make_thumbnail(
910
- image,
911
- maximum=max_allowed,
912
- alphablack=draw_mode & DRAW_MODE_ALPHABLACK == 0,
913
- )
914
- node._cache_width, node._cache_height = image.size
915
- try:
916
- cache = self.make_thumbnail(
917
- image, alphablack=draw_mode & DRAW_MODE_ALPHABLACK == 0
918
- )
919
- min_x, min_y, max_x, max_y = bounds
920
- gc.DrawBitmap(cache, min_x, min_y, max_x - min_x, max_y - min_y)
921
- except MemoryError:
922
- pass
923
- gc.PopState()
924
- if hasattr(node, "message"):
925
- txt = node.message
926
- if txt is not None:
927
- gc.PushState()
928
- gc.SetTransform(wx.GraphicsContext.CreateMatrix(gc, ZMatrix(None)))
929
- font = wx.Font()
930
- font.SetPointSize(20)
931
- gc.SetFont(font, wx.BLACK)
932
- gc.DrawText(txt, 30, 30)
933
- gc.PopState()
934
-
935
- def measure_text(self, node):
936
- """
937
- Use default measure text routines to calculate height etc.
938
-
939
- Use the real draw of the font to calculate actual size.
940
- A 'real' height routine needs to draw the string on an
941
- empty canvas and find the first and last dots on a line...
942
- We are creating a temporary bitmap and paint on it...
943
-
944
- @param node:
945
- @return:
946
- """
947
- dimension_x = 1000
948
- dimension_y = 500
949
- scaling = 1
950
- bmp = wx.Bitmap(dimension_x, dimension_y, 32)
951
- dc = wx.MemoryDC()
952
- dc.SelectObject(bmp)
953
- dc.SetBackground(wx.BLACK_BRUSH)
954
- dc.Clear()
955
- gc = wx.GraphicsContext.Create(dc)
956
-
957
- draw_mode = self.context.draw_mode
958
- if draw_mode & DRAW_MODE_VARIABLES:
959
- # Only if flag show the translated values
960
- text = self.context.elements.wordlist_translate(
961
- node.text, elemnode=node, increment=False
962
- )
963
- node.bounds_with_variables_translated = True
964
- else:
965
- text = node.text
966
- node.bounds_with_variables_translated = False
967
- if node.texttransform:
968
- ttf = node.texttransform.lower()
969
- if ttf == "capitalize":
970
- text = text.capitalize()
971
- elif ttf == "uppercase":
972
- text = text.upper()
973
- if ttf == "lowercase":
974
- text = text.lower()
975
- svgfont_to_wx(node)
976
- use_font = node.wxfont
977
- gc.SetFont(use_font, wx.WHITE)
978
- f_width, f_height, f_descent, f_external_leading = gc.GetFullTextExtent(text)
979
- needs_revision = False
980
- revision_factor = 3
981
- if revision_factor * f_width >= dimension_x:
982
- dimension_x = revision_factor * f_width
983
- needs_revision = True
984
- if revision_factor * f_height > dimension_y:
985
- dimension_y = revision_factor * f_height
986
- needs_revision = True
987
- if needs_revision:
988
- # We need to create an independent instance of the font
989
- # as we may to need to change the font_size temporarily
990
- fontdesc = node.wxfont.GetNativeFontInfoDesc()
991
- use_font = wx.Font(fontdesc)
992
- while True:
993
- try:
994
- fsize = use_font.GetFractionalPointSize()
995
- fsize_org = node.wxfont.GetFractionalPointSize()
996
- except AttributeError:
997
- fsize = use_font.GetPointSize()
998
- fsize_org = node.wxfont.GetPointSize()
999
- # print (f"Revised bounds: {dimension_x} x {dimension_y}, font_size={fsize} (original={fsize_org}")
1000
- if fsize < 100 or dimension_x < 2000 or dimension_y < 1000:
1001
- break
1002
- # We consume an enormous amount of time and memory to create insanely big
1003
- # temporary canvasses, so we intentionally reduce the resolution and accept
1004
- # smaller deviations...
1005
- scaling *= 10
1006
- fsize /= 10
1007
- dimension_x /= 10
1008
- dimension_y /= 10
1009
- try:
1010
- use_font.SetFractionalPointSize(fsize)
1011
- except AttributeError:
1012
- use_font.SetPointSize(int(fsize))
1013
-
1014
- gc.Destroy()
1015
- dc.SelectObject(wx.NullBitmap)
1016
- dc.Destroy()
1017
- del dc
1018
- bmp = wx.Bitmap(int(dimension_x), int(dimension_y), 32)
1019
- dc = wx.MemoryDC()
1020
- dc.SelectObject(bmp)
1021
- dc.SetBackground(wx.BLACK_BRUSH)
1022
- dc.Clear()
1023
- gc = wx.GraphicsContext.Create(dc)
1024
- gc.SetFont(use_font, wx.WHITE)
1025
-
1026
- gc.DrawText(text, 0, 0)
1027
- try:
1028
- img = bmp.ConvertToImage()
1029
- buf = img.GetData()
1030
- image = Image.frombuffer(
1031
- "RGB", tuple(bmp.GetSize()), bytes(buf), "raw", "RGB", 0, 1
1032
- )
1033
- node.text_cache = image
1034
- img_bb = image.getbbox()
1035
- if img_bb is None:
1036
- node.raw_bbox = None
1037
- else:
1038
- newbb = (
1039
- scaling * img_bb[0],
1040
- scaling * img_bb[1],
1041
- scaling * img_bb[2],
1042
- scaling * img_bb[3],
1043
- )
1044
- node.raw_bbox = newbb
1045
- except MemoryError:
1046
- node.text_cache = None
1047
- node.raw_bbox = None
1048
- node.ascent = f_height - f_descent
1049
- if node.baseline != "hanging":
1050
- node.matrix.pre_translate(0, -node.ascent)
1051
- if node.baseline == "middle":
1052
- node.matrix.pre_translate(0, node.ascent / 2)
1053
- node.baseline = "hanging"
1054
- dc.SelectObject(wx.NullBitmap)
1055
- dc.Destroy()
1056
- del dc
1057
-
1058
- def validate_text_nodes(self, nodes, translate_variables):
1059
- self.context.elements.set_start_time("validate_text_nodes")
1060
- for item in nodes:
1061
- if item.type == "elem text" and (
1062
- item._bounds_dirty
1063
- or item._paint_bounds_dirty
1064
- or item.bounds_with_variables_translated != translate_variables
1065
- ):
1066
- # We never drew this cleanly; our initial bounds calculations will be off if we don't premeasure
1067
- self.measure_text(item)
1068
- item.set_dirty_bounds()
1069
- dummy = item.bounds
1070
- self.context.elements.set_end_time("validate_text_nodes")
1071
-
1072
- def make_raster(
1073
- self,
1074
- nodes,
1075
- bounds,
1076
- width=None,
1077
- height=None,
1078
- bitmap=False,
1079
- step_x=1,
1080
- step_y=1,
1081
- keep_ratio=False,
1082
- ):
1083
- """
1084
- Make Raster turns an iterable of elements and a bounds into an image of the designated size, taking into account
1085
- the step size. The physical pixels in the image is reduced by the step size then the matrix for the element is
1086
- scaled up by the same amount. This makes step size work like inverse dpi and correctly sets the image scale to
1087
- the step scale for 1:1 sizes independent of the scale.
1088
-
1089
- This function requires both wxPython and Pillow.
1090
-
1091
- @param nodes: elements to render.
1092
- @param bounds: bounds of those elements for the viewport.
1093
- @param width: desired width of the resulting raster
1094
- @param height: desired height of the resulting raster
1095
- @param bitmap: bitmap to use rather than provisioning
1096
- @param step_x: raster step rate, scale rate of the image.
1097
- @param step_y: raster step rate, scale rate of the image.
1098
- @param keep_ratio: get a picture with the same height / width
1099
- ratio as the original
1100
- @return:
1101
- """
1102
- if bounds is None:
1103
- return None
1104
- x_min = float("inf")
1105
- y_min = float("inf")
1106
- x_max = -float("inf")
1107
- y_max = -float("inf")
1108
- if not isinstance(nodes, (tuple, list)):
1109
- _nodes = [nodes]
1110
- else:
1111
- _nodes = nodes
1112
-
1113
- # if it's a raster we will always translate text variables...
1114
- variable_translation = True
1115
- nodecopy = [e for e in _nodes]
1116
- self.validate_text_nodes(nodecopy, variable_translation)
1117
-
1118
- for item in _nodes:
1119
- # bb = item.bounds
1120
- bb = item.paint_bounds
1121
- if bb is None:
1122
- # Fall back to bounds
1123
- bb = item.bounds
1124
- if bb is None:
1125
- continue
1126
- if bb[0] < x_min:
1127
- x_min = bb[0]
1128
- if bb[1] < y_min:
1129
- y_min = bb[1]
1130
- if bb[2] > x_max:
1131
- x_max = bb[2]
1132
- if bb[3] > y_max:
1133
- y_max = bb[3]
1134
- raster_width = max(x_max - x_min, 1)
1135
- raster_height = max(y_max - y_min, 1)
1136
- if width is None:
1137
- width = raster_width / step_x
1138
- if height is None:
1139
- height = raster_height / step_y
1140
- width = max(width, 1)
1141
- height = max(height, 1)
1142
- bmp = wx.Bitmap(int(ceil(abs(width))), int(ceil(abs(height))), 32)
1143
- dc = wx.MemoryDC()
1144
- dc.SelectObject(bmp)
1145
- dc.SetBackground(wx.WHITE_BRUSH)
1146
- dc.Clear()
1147
-
1148
- matrix = Matrix()
1149
-
1150
- # Scale affine matrix up by step amount scaled down.
1151
- try:
1152
- scale_x = width / raster_width
1153
- except ZeroDivisionError:
1154
- scale_x = 1
1155
-
1156
- try:
1157
- scale_y = height / raster_height
1158
- except ZeroDivisionError:
1159
- scale_y = 1
1160
- if keep_ratio:
1161
- scale_x = min(scale_x, scale_y)
1162
- scale_y = scale_x
1163
- matrix.post_translate(-x_min, -y_min)
1164
- matrix.post_scale(scale_x, scale_y)
1165
- if scale_y < 0:
1166
- matrix.pre_translate(0, -raster_height)
1167
- if scale_x < 0:
1168
- matrix.pre_translate(-raster_width, 0)
1169
-
1170
- gc = wx.GraphicsContext.Create(dc)
1171
- gc.dc = dc
1172
- gc.SetInterpolationQuality(wx.INTERPOLATION_BEST)
1173
- gc.PushState()
1174
- if not matrix.is_identity():
1175
- gc.ConcatTransform(wx.GraphicsContext.CreateMatrix(gc, ZMatrix(matrix)))
1176
- gc.SetBrush(wx.WHITE_BRUSH)
1177
- gc.DrawRectangle(x_min - 1, y_min - 1, x_max + 1, y_max + 1)
1178
- self.render(_nodes, gc, draw_mode=DRAW_MODE_CACHE | DRAW_MODE_VARIABLES)
1179
- img = bmp.ConvertToImage()
1180
- buf = img.GetData()
1181
- image = Image.frombuffer(
1182
- "RGB", tuple(bmp.GetSize()), bytes(buf), "raw", "RGB", 0, 1
1183
- )
1184
-
1185
- gc.PopState()
1186
- dc.SelectObject(wx.NullBitmap)
1187
- gc.Destroy()
1188
- del gc.dc
1189
- del dc
1190
- if bitmap:
1191
- return bmp
1192
- return image
1193
-
1194
- def make_thumbnail(
1195
- self, pil_data, maximum=None, width=None, height=None, alphablack=True
1196
- ):
1197
- """Resizes the given pil image into wx.Bitmap object that fits the constraints."""
1198
- image_width, image_height = pil_data.size
1199
- if width is not None and height is None:
1200
- height = width * image_height / float(image_width)
1201
- if width is None and height is not None:
1202
- width = height * image_width / float(image_height)
1203
- if width is None and height is None:
1204
- width = image_width
1205
- height = image_height
1206
- if maximum is not None and (width > maximum or height > maximum):
1207
- scale_x = maximum / width
1208
- scale_y = maximum / height
1209
- scale = min(scale_x, scale_y)
1210
- width = int(round(width * scale))
1211
- height = int(round(height * scale))
1212
- if image_width != width or image_height != height:
1213
- pil_data = pil_data.resize((width, height))
1214
- else:
1215
- pil_data = pil_data.copy()
1216
- if not alphablack:
1217
- return wx.Bitmap.FromBufferRGBA(
1218
- width, height, pil_data.convert("RGBA").tobytes()
1219
- )
1220
- if "transparency" in pil_data.info:
1221
- pil_data = pil_data.convert("RGBA")
1222
- try:
1223
- # If transparent we paste 0 into the pil_data
1224
- mask = pil_data.getchannel("A").point(lambda e: 255 - e)
1225
- pil_data.paste(mask, None, mask)
1226
- except ValueError:
1227
- pass
1228
- if pil_data.mode != "L":
1229
- pil_data = pil_data.convert("L")
1230
- black = Image.new("RGBA", pil_data.size, "black")
1231
- black.putalpha(pil_data.point(lambda e: 255 - e))
1232
- return wx.Bitmap.FromBufferRGBA(width, height, black.tobytes())
1
+ from copy import copy
2
+ from math import ceil, isnan, sqrt
3
+
4
+ import wx
5
+ from PIL import Image
6
+
7
+ from meerk40t.core.elements.element_types import place_nodes
8
+ from meerk40t.core.node.node import Fillrule, Linecap, Linejoin, Node
9
+ from meerk40t.svgelements import (
10
+ Arc,
11
+ Close,
12
+ Color,
13
+ CubicBezier,
14
+ Line,
15
+ Matrix,
16
+ Move,
17
+ QuadraticBezier,
18
+ )
19
+
20
+ from ..core.cutcode.cubiccut import CubicCut
21
+ from ..core.cutcode.cutcode import CutCode
22
+ from ..core.cutcode.dwellcut import DwellCut
23
+ from ..core.cutcode.gotocut import GotoCut
24
+ from ..core.cutcode.homecut import HomeCut
25
+ from ..core.cutcode.inputcut import InputCut
26
+ from ..core.cutcode.linecut import LineCut
27
+ from ..core.cutcode.outputcut import OutputCut
28
+ from ..core.cutcode.plotcut import PlotCut
29
+ from ..core.cutcode.quadcut import QuadCut
30
+ from ..core.cutcode.rastercut import RasterCut
31
+ from ..core.cutcode.waitcut import WaitCut
32
+ from ..tools.geomstr import ( # , TYPE_RAMP
33
+ TYPE_ARC,
34
+ TYPE_CUBIC,
35
+ TYPE_LINE,
36
+ TYPE_QUAD,
37
+ Geomstr,
38
+ )
39
+ from .fonts import wxfont_to_svg
40
+ from .icons import icons8_image
41
+ from .zmatrix import ZMatrix
42
+ from meerk40t.gui.wxutils import get_gc_scale
43
+
44
+ DRAW_MODE_FILLS = 0x000001
45
+ DRAW_MODE_GUIDES = 0x000002
46
+ DRAW_MODE_GRID = 0x000004
47
+ DRAW_MODE_LASERPATH = 0x000008
48
+ DRAW_MODE_RETICLE = 0x000010
49
+ DRAW_MODE_SELECTION = 0x000020
50
+ DRAW_MODE_STROKES = 0x000040
51
+ DRAW_MODE_CACHE = 0x000080 # Set means do not cache.
52
+ DRAW_MODE_REFRESH = 0x000100
53
+ DRAW_MODE_ANIMATE = 0x000200
54
+ DRAW_MODE_PATH = 0x000400
55
+ DRAW_MODE_IMAGE = 0x000800
56
+ DRAW_MODE_TEXT = 0x001000
57
+ DRAW_MODE_BACKGROUND = 0x002000
58
+ DRAW_MODE_POINTS = 0x004000
59
+ DRAW_MODE_REGMARKS = 0x008000
60
+ DRAW_MODE_VARIABLES = 0x010000
61
+
62
+ DRAW_MODE_ICONS = 0x0040000
63
+ DRAW_MODE_INVERT = 0x400000
64
+ DRAW_MODE_FLIPXY = 0x800000
65
+ DRAW_MODE_LINEWIDTH = 0x1000000
66
+ DRAW_MODE_ALPHABLACK = 0x2000000 # Set means do not alphablack images
67
+ DRAW_MODE_ORIGIN = 0x4000000
68
+ DRAW_MODE_EDIT = 0x8000000
69
+
70
+
71
+ def swizzlecolor(c):
72
+ if c is None:
73
+ return None
74
+ if isinstance(c, int):
75
+ c = Color(argb=c)
76
+ try:
77
+ return c.blue << 16 | c.green << 8 | c.red
78
+ except (ValueError, TypeError):
79
+ return None
80
+
81
+
82
+ def as_wx_color(c):
83
+ if c is None:
84
+ return None
85
+ if isinstance(c, int):
86
+ c = Color(argb=c)
87
+ return wx.Colour(red=c.red, green=c.green, blue=c.blue, alpha=c.alpha)
88
+
89
+
90
+ def svgfont_to_wx(textnode):
91
+ """
92
+ Translates all svg-text-properties to their wxfont-equivalents
93
+ @param textnode:
94
+ @return:
95
+ """
96
+ if not hasattr(textnode, "wxfont"):
97
+ textnode.wxfont = wx.Font()
98
+ if textnode.font_weight is not None:
99
+ try:
100
+ fw = float(textnode.font_weight)
101
+ if fw > 1000:
102
+ textnode.font_weight = 1000
103
+ except ValueError:
104
+ pass
105
+ wxfont = textnode.wxfont
106
+ # if the font_list is empty, then we do have a not properly initialised textnode,
107
+ # that needs to be resolved...
108
+ if textnode.font_family is None:
109
+ wxfont_to_svg(textnode)
110
+
111
+ svg_to_wx_family(textnode, wxfont)
112
+ svg_to_wx_fontstyle(textnode, wxfont)
113
+ try:
114
+ wxfont.SetNumericWeight(textnode.weight) # Gets numeric weight.
115
+ except AttributeError:
116
+ # Running version wx4.0. No set Numeric Weight, can only set bold or normal.
117
+ weight = textnode.weight
118
+ wxfont.SetWeight(
119
+ wx.FONTWEIGHT_BOLD if weight > 600 else wx.FONTWEIGHT_NORMAL
120
+ ) # Gets numeric weight.
121
+
122
+ try:
123
+ font_size = float(textnode.font_size)
124
+ except ValueError:
125
+ font_size = 10
126
+ try:
127
+ wxfont.SetFractionalPointSize(font_size)
128
+ except AttributeError:
129
+ # If we cannot set the fractional point size, we scale up to adjust to fractional levels.
130
+ integer_font_size = int(round(font_size))
131
+ scale = font_size / integer_font_size
132
+ if scale != 1.0:
133
+ textnode.matrix.pre_scale(scale, scale)
134
+ textnode.font_size = integer_font_size
135
+ wxfont.SetPointSize(integer_font_size)
136
+ wxfont.SetUnderlined(textnode.underline)
137
+ wxfont.SetStrikethrough(textnode.strikethrough)
138
+
139
+
140
+ def svg_to_wx_family(textnode, wxfont):
141
+ font_list = textnode.font_list
142
+ # if font_list is None:
143
+ # print("Fontlist is empty...")
144
+ # else:
145
+ # print ("Fontlist was: ", font_list)
146
+ for ff in font_list:
147
+ if ff == "fantasy":
148
+ family = wx.FONTFAMILY_DECORATIVE
149
+ wxfont.SetFamily(family)
150
+ return
151
+ elif ff == "serif":
152
+ family = wx.FONTFAMILY_ROMAN
153
+ wxfont.SetFamily(family)
154
+ return
155
+ elif ff == "cursive":
156
+ family = wx.FONTFAMILY_SCRIPT
157
+ wxfont.SetFamily(family)
158
+ return
159
+ elif ff == "sans-serif":
160
+ family = wx.FONTFAMILY_SWISS
161
+ wxfont.SetFamily(family)
162
+ return
163
+ elif ff == "monospace":
164
+ family = wx.FONTFAMILY_TELETYPE
165
+ wxfont.SetFamily(family)
166
+ return
167
+ if wxfont.SetFaceName(ff):
168
+ # We found a correct face name.
169
+ return
170
+
171
+
172
+ def svg_to_wx_fontstyle(textnode, wxfont):
173
+ ff = textnode.font_style
174
+ if ff == "normal":
175
+ fontstyle = wx.FONTSTYLE_NORMAL
176
+ elif ff == "italic":
177
+ fontstyle = wx.FONTSTYLE_ITALIC
178
+ elif ff == "oblique":
179
+ fontstyle = wx.FONTSTYLE_SLANT
180
+ else:
181
+ fontstyle = wx.FONTSTYLE_NORMAL
182
+ wxfont.SetStyle(fontstyle)
183
+
184
+
185
+ class LaserRender:
186
+ """
187
+ Laser Render provides GUI relevant methods of displaying the given elements.
188
+ """
189
+
190
+ def __init__(self, context):
191
+ self.context = context
192
+ self.context.setting(int, "draw_mode", 0)
193
+ self.pen = wx.Pen()
194
+ self.brush = wx.Brush()
195
+ self.color = wx.Colour()
196
+ self.caches_generated = 0
197
+ self.nodes_rendered = 0
198
+ self.nodes_skipped = 0
199
+ self._visible_area = None
200
+ self.suppress_it = False
201
+
202
+ def set_visible_area(self, box):
203
+ self._visible_area = box
204
+
205
+ def render_tree(self, node, gc, draw_mode=None, zoomscale=1.0, alpha=255):
206
+ if not self.render_node(
207
+ node, gc, draw_mode=draw_mode, zoomscale=zoomscale, alpha=alpha
208
+ ):
209
+ for c in node.children:
210
+ self.render_tree(
211
+ c, gc, draw_mode=draw_mode, zoomscale=zoomscale, alpha=alpha
212
+ )
213
+
214
+ def render(self, nodes, gc, draw_mode=None, zoomscale=1.0, alpha=255, msg="unknown"):
215
+ """
216
+ Render scene information.
217
+
218
+ @param nodes: Node types to render.
219
+ @param gc: graphics context
220
+ @param draw_mode: draw mode flags for rendering
221
+ @param zoomscale: zoomscale at which to render nodes
222
+ @param alpha: render transparency
223
+ @return:
224
+ """
225
+ # gc_win = gc.GetWindow()
226
+ # gc_mat = gc.GetTransform().Get()
227
+ # print (f"Window handle: {gc_win}, matrix: {gc_mat}")
228
+ self.suppress_it = self.context.setting(bool, "supress_non_visible", True)
229
+ self.context.elements.set_start_time(f"renderscene_{msg}")
230
+ self.caches_generated = 0
231
+ self.nodes_rendered = 0
232
+ self.nodes_skipped = 0
233
+ if draw_mode is None:
234
+ draw_mode = self.context.draw_mode
235
+ if draw_mode & (DRAW_MODE_TEXT | DRAW_MODE_IMAGE | DRAW_MODE_PATH) != 0:
236
+ if draw_mode & DRAW_MODE_PATH: # Do not draw paths.
237
+ path_elements = (
238
+ "elem ellipse",
239
+ "elem path",
240
+ "elem point",
241
+ "elem polyline",
242
+ "elem rect",
243
+ "elem line",
244
+ "effect hatch",
245
+ "effect wobble",
246
+ "effect warp",
247
+ )
248
+ nodes = [e for e in nodes if e.type not in path_elements]
249
+ if draw_mode & DRAW_MODE_IMAGE: # Do not draw images.
250
+ nodes = [e for e in nodes if hasattr(e, "as_image")]
251
+ if draw_mode & DRAW_MODE_TEXT: # Do not draw text.
252
+ nodes = [e for e in nodes if e.type != "elem text"]
253
+ if draw_mode & DRAW_MODE_REGMARKS: # Do not draw regmarked items.
254
+ nodes = [e for e in nodes if e._parent.type != "branch reg"]
255
+ nodes = [e for e in nodes if e.type not in place_nodes]
256
+ _nodes = list(nodes)
257
+ variable_translation = draw_mode & DRAW_MODE_VARIABLES
258
+ nodecopy = list(_nodes)
259
+ self.validate_text_nodes(nodecopy, variable_translation)
260
+
261
+ for node in _nodes:
262
+ if node.type == "reference":
263
+ # Reference nodes should be drawn per-usual, recurse.
264
+ self.render_node(
265
+ node.node, gc, draw_mode=draw_mode, zoomscale=zoomscale, alpha=alpha
266
+ )
267
+ continue
268
+ self.render_node(
269
+ node, gc, draw_mode=draw_mode, zoomscale=zoomscale, alpha=alpha
270
+ )
271
+ self.context.elements.set_end_time(f"renderscene_{msg}", message=f"Rendered: {self.nodes_rendered}, skipped: {self.nodes_skipped}, caches created: {self.caches_generated}")
272
+
273
+ def render_node(self, node, gc, draw_mode=None, zoomscale=1.0, alpha=255):
274
+ """
275
+ Renders the specific node.
276
+ @param node:
277
+ @param gc:
278
+ @param draw_mode:
279
+ @param zoomscale:
280
+ @param alpha:
281
+ @return: True if rendering was done, False if rendering could not be done.
282
+ """
283
+ node_bb = node.bounds if hasattr(node, "bounds") else None
284
+ vis_bb = self._visible_area
285
+ if self.suppress_it and vis_bb is not None and node_bb is not None and (
286
+ node_bb[0] > vis_bb[2] or
287
+ node_bb[1] > vis_bb[3] or
288
+ node_bb[2] < vis_bb[0] or
289
+ node_bb[3] < vis_bb[1]
290
+ ):
291
+ self.nodes_skipped += 1
292
+ return False
293
+ if hasattr(node, "hidden") and node.hidden:
294
+ self.nodes_skipped += 1
295
+ return False
296
+ if hasattr(node, "is_visible") and not node.is_visible:
297
+ self.nodes_skipped += 1
298
+ return False
299
+ if hasattr(node, "output") and not node.output:
300
+ self.nodes_skipped += 1
301
+ return False
302
+ self.nodes_rendered += 1
303
+ if not hasattr(node, "draw"): # or not hasattr(node, "_make_cache"):
304
+ # No known render method, we must define the function to draw nodes.
305
+ if node.type in (
306
+ "elem path",
307
+ "elem ellipse",
308
+ "elem rect",
309
+ "elem line",
310
+ "elem polyline",
311
+ "effect hatch",
312
+ "effect wobble",
313
+ "effect warp",
314
+ ):
315
+ node.draw = self.draw_vector
316
+ # node._make_cache = self.cache_geomstr
317
+ elif node.type == "elem point":
318
+ node.draw = self.draw_point_node
319
+ elif node.type in place_nodes:
320
+ node.draw = self.draw_placement_node
321
+ elif hasattr(node, "as_image"):
322
+ node.draw = self.draw_image_node
323
+ elif node.type == "elem text":
324
+ node.draw = self.draw_text_node
325
+ elif node.type == "cutcode":
326
+ node.draw = self.draw_cutcode_node
327
+ elif node.type == "group":
328
+ node.draw = self.draw_nothing
329
+ else:
330
+ # print (f"This node has no method: {node.type}")
331
+ return False
332
+ # We have now defined that function, draw it.
333
+ node.draw(node, gc, draw_mode, zoomscale=zoomscale, alpha=alpha)
334
+ if getattr(node, "label_display", False) and node.label:
335
+ # Display label
336
+ col = self.context.root.setting(str, "label_display_color", "#ff0000ff")
337
+ self.display_label(
338
+ node, gc, draw_mode, zoomscale=zoomscale, alpha=alpha, color=col
339
+ )
340
+ return True
341
+
342
+ def make_path(self, gc, path):
343
+ """
344
+ Takes a svgelements.Path and converts it to a GraphicsContext.Graphics Path
345
+ """
346
+ p = gc.CreatePath()
347
+ init = False
348
+ for e in path.segments(transformed=True):
349
+ if isinstance(e, Move):
350
+ p.MoveToPoint(e.end[0], e.end[1])
351
+ init = True
352
+ elif isinstance(e, Line):
353
+ if not init:
354
+ init = True
355
+ p.MoveToPoint(e.start[0], e.start[1])
356
+ p.AddLineToPoint(e.end[0], e.end[1])
357
+ elif isinstance(e, Close):
358
+ if not init:
359
+ init = True
360
+ p.MoveToPoint(e.start[0], e.start[1])
361
+ p.CloseSubpath()
362
+ elif isinstance(e, QuadraticBezier):
363
+ if not init:
364
+ init = True
365
+ p.MoveToPoint(e.start[0], e.start[1])
366
+ p.AddQuadCurveToPoint(e.control[0], e.control[1], e.end[0], e.end[1])
367
+ elif isinstance(e, CubicBezier):
368
+ if not init:
369
+ init = True
370
+ p.MoveToPoint(e.start[0], e.start[1])
371
+ p.AddCurveToPoint(
372
+ e.control1[0],
373
+ e.control1[1],
374
+ e.control2[0],
375
+ e.control2[1],
376
+ e.end[0],
377
+ e.end[1],
378
+ )
379
+ elif isinstance(e, Arc):
380
+ if not init:
381
+ init = True
382
+ p.MoveToPoint(e.start[0], e.start[1])
383
+ for curve in e.as_cubic_curves():
384
+ p.AddCurveToPoint(
385
+ curve.control1[0],
386
+ curve.control1[1],
387
+ curve.control2[0],
388
+ curve.control2[1],
389
+ curve.end[0],
390
+ curve.end[1],
391
+ )
392
+ return p
393
+
394
+ def make_geomstr(self, gc, path, node=None, settings=None):
395
+ """
396
+ Takes a Geomstr path and converts it to a GraphicsContext.Graphics path
397
+
398
+ This also creates a point list of the relevant nodes and creates a ._cache_edit value to be used by node
399
+ editing view.
400
+ """
401
+ p = gc.CreatePath()
402
+ pts = list()
403
+ for subpath in path.as_subpaths():
404
+ if len(subpath) == 0:
405
+ continue
406
+ end = None
407
+ for e in subpath.segments:
408
+ seg_type = int(e[2].real)
409
+ if settings is not None and settings != int(e[2].imag):
410
+ continue
411
+ start = e[0]
412
+ if end != start:
413
+ # Start point does not equal previous end point.
414
+ p.MoveToPoint(start.real, start.imag)
415
+ c0 = e[1]
416
+ c1 = e[3]
417
+ end = e[4]
418
+
419
+ if seg_type == TYPE_LINE:
420
+ p.AddLineToPoint(end.real, end.imag)
421
+ pts.extend((start, end))
422
+ elif seg_type == TYPE_QUAD:
423
+ p.AddQuadCurveToPoint(c0.real, c0.imag, end.real, end.imag)
424
+ pts.append(c0)
425
+ pts.extend((start, end))
426
+ elif seg_type == TYPE_ARC:
427
+ radius = Geomstr.arc_radius(None, line=e)
428
+ center = Geomstr.arc_center(None, line=e)
429
+ start_t = Geomstr.angle(None, center, start)
430
+ end_t = Geomstr.angle(None, center, end)
431
+ p.AddArc(
432
+ center.real,
433
+ center.imag,
434
+ radius,
435
+ start_t,
436
+ end_t,
437
+ clockwise=Geomstr.orientation(None, start, c0, end) != "ccw",
438
+ )
439
+ pts.append(c0)
440
+ pts.extend((start, end))
441
+ elif seg_type == TYPE_CUBIC:
442
+ p.AddCurveToPoint(
443
+ c0.real, c0.imag, c1.real, c1.imag, end.real, end.imag
444
+ )
445
+ pts.extend((c0, c1, start, end))
446
+ else:
447
+ print(f"Unknown seg_type: {seg_type}")
448
+ if subpath.first_point == end:
449
+ p.CloseSubpath()
450
+ if node is not None:
451
+ graphics_path_2 = gc.CreatePath()
452
+ for pt in pts:
453
+ graphics_path_2.AddCircle(pt.real, pt.imag, 5000)
454
+ node._cache_edit = graphics_path_2
455
+
456
+ return p
457
+
458
+ def _set_linecap_by_node(self, node):
459
+ if not hasattr(node, "linecap") or node.linecap is None:
460
+ self.pen.SetCap(wx.CAP_ROUND)
461
+ else:
462
+ if node.linecap == Linecap.CAP_BUTT:
463
+ self.pen.SetCap(wx.CAP_BUTT)
464
+ elif node.linecap == Linecap.CAP_ROUND:
465
+ self.pen.SetCap(wx.CAP_ROUND)
466
+ elif node.linecap == Linecap.CAP_SQUARE:
467
+ self.pen.SetCap(wx.CAP_PROJECTING)
468
+ else:
469
+ self.pen.SetCap(wx.CAP_ROUND)
470
+
471
+ def _set_linejoin_by_node(self, node):
472
+ if not hasattr(node, "linejoin"):
473
+ self.pen.SetJoin(wx.JOIN_BEVEL)
474
+ elif node.linejoin is None:
475
+ self.pen.SetJoin(wx.JOIN_MITER)
476
+ else:
477
+ if node.linejoin == Linejoin.JOIN_ARCS:
478
+ self.pen.SetJoin(wx.JOIN_ROUND)
479
+ elif node.linejoin == Linejoin.JOIN_BEVEL:
480
+ self.pen.SetJoin(wx.JOIN_BEVEL)
481
+ elif node.linejoin == Linejoin.JOIN_MITER:
482
+ self.pen.SetJoin(wx.JOIN_MITER)
483
+ elif node.linejoin == Linejoin.JOIN_MITER_CLIP:
484
+ self.pen.SetJoin(wx.JOIN_MITER)
485
+ else:
486
+ self.pen.SetJoin(wx.JOIN_ROUND)
487
+
488
+ def _get_fillstyle(self, node):
489
+ if not hasattr(node, "fillrule") or node.fillrule is None:
490
+ return wx.WINDING_RULE
491
+ if node.fillrule == Fillrule.FILLRULE_EVENODD:
492
+ return wx.ODDEVEN_RULE
493
+ else:
494
+ return wx.WINDING_RULE
495
+
496
+ @staticmethod
497
+ def _penwidth(pen, width):
498
+ try:
499
+ if isnan(width):
500
+ width = 1.0
501
+ try:
502
+ pen.SetWidth(width)
503
+ except TypeError:
504
+ pen.SetWidth(int(width))
505
+ except OverflowError:
506
+ pass # Exceeds 32 bit signed integer.
507
+
508
+ def _set_penwidth(self, width):
509
+ self._penwidth(self.pen, width)
510
+
511
+ def set_pen(self, gc, stroke, alpha=None):
512
+ c = stroke
513
+ if c is not None and c != "none":
514
+ swizzle_color = swizzlecolor(c)
515
+ if alpha is None:
516
+ alpha = c.alpha
517
+ self.color.SetRGBA(swizzle_color | alpha << 24) # wx has BBGGRR
518
+ self.pen.SetColour(self.color)
519
+ gc.SetPen(self.pen)
520
+ else:
521
+ gc.SetPen(wx.TRANSPARENT_PEN)
522
+
523
+ def set_brush(self, gc, fill, alpha=None):
524
+ c = fill
525
+ if c is not None and c != "none":
526
+ swizzle_color = swizzlecolor(c)
527
+ if alpha is None:
528
+ alpha = c.alpha
529
+ self.color.SetRGBA(swizzle_color | alpha << 24) # wx has BBGGRR
530
+ self.brush.SetColour(self.color)
531
+ gc.SetBrush(self.brush)
532
+ else:
533
+ gc.SetBrush(wx.TRANSPARENT_BRUSH)
534
+
535
+ def draw_nothing(
536
+ self,
537
+ node: Node,
538
+ gc: wx.GraphicsContext,
539
+ draw_mode,
540
+ zoomscale=1.0,
541
+ alpha=255,
542
+ x: int = 0,
543
+ y: int = 0,
544
+ ):
545
+ # We don't do anything, just a placeholder
546
+ return
547
+
548
+ def draw_cutcode_node(
549
+ self,
550
+ node: Node,
551
+ gc: wx.GraphicsContext,
552
+ draw_mode,
553
+ zoomscale=1.0,
554
+ alpha=255,
555
+ x: int = 0,
556
+ y: int = 0,
557
+ ):
558
+ cutcode = node.cutcode
559
+ self.draw_cutcode(cutcode, gc, x, y)
560
+
561
+ def draw_cutcode(
562
+ self,
563
+ cutcode: CutCode,
564
+ gc: wx.GraphicsContext,
565
+ x: int = 0, y: int = 0,
566
+ raster_as_image: bool = True,
567
+ residual = None,
568
+ laserspot_width = None,
569
+ ):
570
+ """
571
+ Draw cutcode object into wxPython graphics code.
572
+
573
+ This code accepts x,y offset values. The cutcode laser offset can be set with a
574
+ command with the rest of the cutcode remaining the same. So drawing the cutcode
575
+ requires knowing what, if any offset is currently being applied.
576
+
577
+ @param cutcode: flat cutcode object to draw.
578
+ @param gc: wx.graphics context
579
+ @param x: offset in x direction
580
+ @param y: offset in y direction
581
+ @return:
582
+ """
583
+
584
+ def establish_linewidth(scale, spot_width):
585
+ default_pix = 1 / scale
586
+ # print (gcscale, laserspot_width, 1/gcscale)
587
+ pixelwidth = spot_width if spot_width is not None else default_pix
588
+ # How many pixels should the laserspotwidth be like,
589
+ # in any case at least 1 pixel, as otherwise it
590
+ # wouldn't show up under Linux/Darwin
591
+ return max(default_pix, pixelwidth)
592
+
593
+ def process_cut(cut, p, last_point):
594
+
595
+ def process_as_image():
596
+ image = cut.image
597
+ gc.PushState()
598
+ matrix = Matrix.scale(cut.step_x, cut.step_y)
599
+ matrix.post_translate(
600
+ cut.offset_x + x, cut.offset_y + y
601
+ ) # Adjust image xy
602
+ gc.ConcatTransform(wx.GraphicsContext.CreateMatrix(gc, ZMatrix(matrix)))
603
+ _gcscale = get_gc_scale(gc)
604
+ try:
605
+ cache = cut._cache
606
+ except AttributeError:
607
+ cache = None
608
+ if cache is None:
609
+ # No valid cache. Generate.
610
+ cut._cache_width, cut._cache_height = image.size
611
+ try:
612
+ cut._cache = self.make_thumbnail(image, maximum=5000)
613
+ except (MemoryError, RuntimeError):
614
+ cut._cache = None
615
+ cut._cache_id = id(image)
616
+ if cut._cache is not None:
617
+ # Cache exists and is valid.
618
+ gc.DrawBitmap(cut._cache, 0, 0, cut._cache_width, cut._cache_height)
619
+ if cut.highlighted:
620
+ # gc.SetBrush(wx.RED_BRUSH)
621
+ self._penwidth(highlight_pen, 3 / gcscale)
622
+ gc.SetPen(highlight_pen)
623
+ gc.DrawRectangle(0, 0, cut._cache_width, cut._cache_height)
624
+ else:
625
+ # Image was too large to cache, draw a red rectangle instead.
626
+ gc.SetBrush(wx.RED_BRUSH)
627
+ gc.DrawRectangle(0, 0, cut._cache_width, cut._cache_height)
628
+ gc.DrawBitmap(
629
+ icons8_image.GetBitmap(),
630
+ 0,
631
+ 0,
632
+ cut._cache_width,
633
+ cut._cache_height,
634
+ )
635
+ gc.PopState()
636
+
637
+ def process_as_raster():
638
+ try:
639
+ cache = cut._plotcache
640
+ except AttributeError:
641
+ cache = None
642
+ if cache is None:
643
+ process_as_image()
644
+ return
645
+ p.MoveToPoint(start[0] + x, start[1] + y)
646
+ todraw = cache
647
+ if residual is None:
648
+ maxcount = -1
649
+ else:
650
+ maxcount = int(len(todraw) * residual)
651
+ count = 0
652
+ for px, py, pon in todraw:
653
+ if px is None or py is None:
654
+ # Passthrough
655
+ continue
656
+ if pon == 0:
657
+ p.MoveToPoint(px + x, py + y)
658
+ else:
659
+ p.AddLineToPoint(px + x, py + y)
660
+ count += 1
661
+ if 0 < maxcount < count:
662
+ break
663
+
664
+ def process_as_plot():
665
+ p.MoveToPoint(start[0] + x, start[1] + y)
666
+ try:
667
+ cache = cut._plotcache
668
+ except AttributeError:
669
+ cache = None
670
+ if cache is None:
671
+ return
672
+ todraw = cache
673
+ if residual is None:
674
+ maxcount = -1
675
+ else:
676
+ maxcount = int(len(todraw) * residual)
677
+ count = 0
678
+ for ox, oy, pon, px, py in todraw:
679
+ if pon == 0:
680
+ p.MoveToPoint(px + x, py + y)
681
+ else:
682
+ p.AddLineToPoint(px + x, py + y)
683
+ count += 1
684
+ if 0 < maxcount < count:
685
+ break
686
+
687
+ start = cut.start
688
+ end = cut.end
689
+ if last_point != start:
690
+ p.MoveToPoint(start[0] + x, start[1] + y)
691
+
692
+ if isinstance(cut, LineCut):
693
+ # Standard line cut. Applies to path object.
694
+ p.AddLineToPoint(end[0] + x, end[1] + y)
695
+ elif isinstance(cut, QuadCut):
696
+ # Standard quadratic bezier cut
697
+ p.AddQuadCurveToPoint(
698
+ cut.c()[0] + x, cut.c()[1] + y, end[0] + x, end[1] + y
699
+ )
700
+ elif isinstance(cut, CubicCut):
701
+ # Standard cubic bezier cut
702
+ p.AddCurveToPoint(
703
+ cut.c1()[0] + x,
704
+ cut.c1()[1] + y,
705
+ cut.c2()[0] + x,
706
+ cut.c2()[1] + y,
707
+ end[0] + x,
708
+ end[1] + y,
709
+ )
710
+ elif isinstance(cut, RasterCut):
711
+ # Rastercut object.
712
+ if raster_as_image:
713
+ process_as_image()
714
+ else:
715
+ process_as_raster()
716
+
717
+ elif isinstance(cut, PlotCut):
718
+ process_as_plot()
719
+ elif isinstance(cut, DwellCut):
720
+ pass
721
+ elif isinstance(cut, WaitCut):
722
+ pass
723
+ elif isinstance(cut, HomeCut):
724
+ p.MoveToPoint(0, 0)
725
+ elif isinstance(cut, GotoCut):
726
+ p.MoveToPoint(start[0] + x, start[1] + y)
727
+ elif isinstance(cut, InputCut):
728
+ pass
729
+ elif isinstance(cut, OutputCut):
730
+ pass
731
+ return end
732
+
733
+ gcscale = get_gc_scale(gc)
734
+ pixelwidth = establish_linewidth(gcscale, laserspot_width)
735
+ defaultwidth = 1 / gcscale
736
+ if defaultwidth > 0.25 * pixelwidth:
737
+ defaultwidth = 0
738
+ # print (f"Scale: {gcscale} - {mat_param}")
739
+ highlight_color = Color("magenta")
740
+ wx_color = wx.Colour(swizzlecolor(highlight_color))
741
+ highlight_pen = wx.Pen(wx_color)
742
+ highlight_pen.SetStyle(wx.PENSTYLE_SHORT_DASH)
743
+ p = None
744
+ last_point = None
745
+ color = None
746
+ for cut in cutcode:
747
+ if hasattr(cut, "visible") and getattr(cut, "visible") is False:
748
+ continue
749
+ c = highlight_color if cut.highlighted else cut.color
750
+ if c is None:
751
+ c = 0
752
+ try:
753
+ if c.value is None:
754
+ c = 0
755
+ except AttributeError:
756
+ pass
757
+ if c is not color:
758
+ if p is not None:
759
+ gc.StrokePath(p)
760
+ if defaultwidth:
761
+ self._penwidth(self.pen, defaultwidth)
762
+ self.set_pen(gc, color, 192)
763
+ gc.StrokePath(p)
764
+ del p
765
+ color = c
766
+ last_point = None
767
+ p = gc.CreatePath()
768
+ self._penwidth(self.pen, pixelwidth)
769
+ alphavalue = 192 if laserspot_width is None else 64
770
+ self.set_pen(gc, c, alpha=alphavalue)
771
+ if p is None:
772
+ p = gc.CreatePath()
773
+ last_point = process_cut(cut, p, last_point)
774
+ if p is not None:
775
+ gc.StrokePath(p)
776
+ if defaultwidth:
777
+ self._penwidth(self.pen, defaultwidth)
778
+ self.set_pen(gc, c, 192)
779
+ gc.StrokePath(p)
780
+ del p
781
+
782
+ def cache_geomstr(self, node, gc):
783
+ self.caches_generated += 1
784
+
785
+ try:
786
+ matrix = node.matrix
787
+ node._cache_matrix = copy(matrix)
788
+ except AttributeError:
789
+ node._cache_matrix = Matrix()
790
+ if hasattr(node, "final_geometry"):
791
+ geom = node.final_geometry()
792
+ else:
793
+ geom = node.as_geometry()
794
+ cache = self.make_geomstr(gc, geom, node=node)
795
+ node._cache = cache
796
+
797
+ def draw_vector(self, node, gc, draw_mode, zoomscale=1.0, alpha=255):
798
+ """
799
+ Draw routine for vector objects.
800
+
801
+ Vector objects are expected to have a _make_cache routine which attaches a `_cache_matrix` and a `_cache`
802
+ attribute to them which can be drawn as a GraphicsPath.
803
+ """
804
+ if hasattr(node, "mktext"):
805
+ newtext = self.context.elements.wordlist_translate(
806
+ node.mktext, elemnode=node, increment=False
807
+ )
808
+ oldtext = getattr(node, "_translated_text", "")
809
+ if newtext != oldtext:
810
+ node._translated_text = newtext
811
+ kernel = self.context.elements.kernel
812
+ for property_op in kernel.lookup_all("path_updater/.*"):
813
+ property_op(kernel.root, node)
814
+ if hasattr(node, "_cache"):
815
+ node._cache = None
816
+ try:
817
+ matrix = node.matrix
818
+ except AttributeError:
819
+ matrix = Matrix()
820
+ gc.PushState()
821
+ try:
822
+ cache = node._cache
823
+ except AttributeError:
824
+ cache = None
825
+ if cache is None:
826
+ self.cache_geomstr(node, gc)
827
+
828
+ try:
829
+ cache_matrix = node._cache_matrix
830
+ except AttributeError:
831
+ cache_matrix = None
832
+
833
+ stroke_factor = 1
834
+ if matrix != cache_matrix and cache_matrix is not None:
835
+ # Calculate the relative change matrix and apply it to this shape.
836
+ q = ~cache_matrix * matrix
837
+ gc.ConcatTransform(wx.GraphicsContext.CreateMatrix(gc, ZMatrix(q)))
838
+ # Applying the matrix will scale our stroke, so we scale the stroke back down.
839
+ stroke_factor = 1.0 if q.determinant == 0 else 1.0 / sqrt(abs(q.determinant))
840
+ self._set_linecap_by_node(node)
841
+ self._set_linejoin_by_node(node)
842
+ sw = node.implied_stroke_width * stroke_factor
843
+ if draw_mode & DRAW_MODE_LINEWIDTH:
844
+ # No stroke rendering.
845
+ sw = 1000
846
+ self._set_penwidth(sw)
847
+ self.set_pen(
848
+ gc,
849
+ node.stroke,
850
+ alpha=alpha,
851
+ )
852
+ self.set_brush(gc, node.fill, alpha=alpha)
853
+ if draw_mode & DRAW_MODE_FILLS == 0 and node.fill is not None:
854
+ gc.FillPath(node._cache, fillStyle=self._get_fillstyle(node))
855
+ if draw_mode & DRAW_MODE_STROKES == 0 and node.stroke is not None:
856
+ gc.StrokePath(node._cache)
857
+
858
+ if node.emphasized and draw_mode & DRAW_MODE_EDIT:
859
+ try:
860
+ edit = node._cache_edit
861
+ gc.StrokePath(edit)
862
+ except AttributeError:
863
+ pass
864
+ gc.PopState()
865
+
866
+ def draw_placement_node(self, node, gc, draw_mode, zoomscale=1.0, alpha=255):
867
+ """Default draw routine for the placement operation."""
868
+ if node.type == "place current":
869
+ # no idea how to draw yet...
870
+ return
871
+ gc.PushState()
872
+ matrix = Matrix()
873
+ if node.rotation is not None and node.rotation != 0:
874
+ matrix.post_rotate(node.rotation, node.x, node.y)
875
+ gc.ConcatTransform(wx.GraphicsContext.CreateMatrix(gc, ZMatrix(matrix)))
876
+ # First x
877
+ dif = 20 * zoomscale
878
+ x_from = node.x
879
+ y_from = node.y
880
+ if node.corner == 0:
881
+ # Top Left
882
+ x_to = x_from + dif
883
+ y_to = y_from + dif
884
+ x_sign = 1
885
+ y_sign = 1
886
+ elif node.corner == 1:
887
+ # Top Right
888
+ x_to = x_from - dif
889
+ y_to = y_from + dif
890
+ x_sign = -1
891
+ y_sign = 1
892
+ elif node.corner == 2:
893
+ # Bottom Right
894
+ x_to = x_from - dif
895
+ y_to = y_from - dif
896
+ x_sign = -1
897
+ y_sign = -1
898
+ elif node.corner == 3:
899
+ # Bottom Left
900
+ x_to = x_from + dif
901
+ y_to = y_from - dif
902
+ x_sign = 1
903
+ y_sign = -1
904
+ else:
905
+ # Center
906
+ x_from -= dif
907
+ y_from -= dif
908
+ x_to = x_from + 2 * dif
909
+ y_to = y_from + 2 * dif
910
+ x_sign = 1
911
+ y_sign = 1
912
+ width = 1.0 * zoomscale
913
+ rpen = wx.Pen(wx.Colour(red=255, green=0, blue=0, alpha=alpha))
914
+ self._penwidth(rpen, width)
915
+
916
+ gpen = wx.Pen(wx.Colour(red=0, green=255, blue=0, alpha=alpha))
917
+ self._penwidth(gpen, width)
918
+ gc.SetPen(rpen)
919
+ dif = 5 * zoomscale
920
+ gc.StrokeLine(x_from, node.y, x_to, node.y)
921
+ gc.StrokeLine(x_to - x_sign * dif, node.y - y_sign * dif, x_to, node.y)
922
+ gc.StrokeLine(x_to - x_sign * dif, node.y + y_sign * dif, x_to, node.y)
923
+ gc.SetPen(gpen)
924
+ gc.StrokeLine(node.x, y_from, node.x, y_to)
925
+ gc.StrokeLine(node.x - x_sign * dif, y_to - y_sign * dif, node.x, y_to)
926
+ gc.StrokeLine(node.x + x_sign * dif, y_to - y_sign * dif, node.x, y_to)
927
+
928
+ loops = 1
929
+ if hasattr(node, "loops") and node.loops is not None:
930
+ # No zero or negative values please
931
+ try:
932
+ loops = int(node.loops)
933
+ except ValueError:
934
+ loops = 1
935
+ if loops < 1:
936
+ loops = 1
937
+ if loops > 1:
938
+ symbol = f"{loops}x"
939
+ font_size = 10 * zoomscale
940
+ if font_size < 1.0:
941
+ font_size = 1.0
942
+ try:
943
+ font = wx.Font(
944
+ font_size,
945
+ wx.FONTFAMILY_SWISS,
946
+ wx.FONTSTYLE_NORMAL,
947
+ wx.FONTWEIGHT_NORMAL,
948
+ )
949
+ except TypeError:
950
+ font = wx.Font(
951
+ int(font_size),
952
+ wx.FONTFAMILY_SWISS,
953
+ wx.FONTSTYLE_NORMAL,
954
+ wx.FONTWEIGHT_NORMAL,
955
+ )
956
+ gc.SetFont(font, wx.Colour(red=255, green=0, blue=0, alpha=alpha))
957
+ (t_width, t_height) = gc.GetTextExtent(symbol)
958
+ x = (x_from + x_to) / 2 - t_width / 2
959
+ y = (y_from + y_to) / 2 - t_height / 2
960
+ # is corner center then shift it a bit more
961
+ if node.corner == 4:
962
+ x += 0.25 * (x_to - x_from)
963
+ y += 0.25 * (y_to - y_from)
964
+ gc.DrawText(symbol, x, y)
965
+ symbol = ""
966
+ if hasattr(node, "nx") and hasattr(node, "ny"):
967
+ nx = node.nx
968
+ if nx is None:
969
+ nx = 1
970
+ ny = node.ny
971
+ if ny is None:
972
+ ny = 1
973
+ if nx != 1 or ny != 1:
974
+ symbol = f"{nx},{ny}"
975
+ if symbol:
976
+ font_size = 10 * zoomscale
977
+ if font_size < 1.0:
978
+ font_size = 1.0
979
+ try:
980
+ font = wx.Font(
981
+ font_size,
982
+ wx.FONTFAMILY_SWISS,
983
+ wx.FONTSTYLE_NORMAL,
984
+ wx.FONTWEIGHT_NORMAL,
985
+ )
986
+ except TypeError:
987
+ font = wx.Font(
988
+ int(font_size),
989
+ wx.FONTFAMILY_SWISS,
990
+ wx.FONTSTYLE_NORMAL,
991
+ wx.FONTWEIGHT_NORMAL,
992
+ )
993
+ gc.SetFont(font, wx.Colour(red=255, green=0, blue=0, alpha=alpha))
994
+ (t_width, t_height) = gc.GetTextExtent(symbol)
995
+ x = x_from + (x_from - (x_from + x_to) / 2) - t_width / 2
996
+ y = y_from + (y_from - (y_from + y_to) / 2) - t_height / 2
997
+ # is corner center then shift it a bit more
998
+ if node.corner == 4:
999
+ x += 0.75 * abs(x_to - x_from)
1000
+ y += 0.75 * abs(y_to - y_from)
1001
+ gc.DrawText(symbol, x, y)
1002
+
1003
+ gc.PopState()
1004
+
1005
+ def draw_point_node(self, node, gc, draw_mode, zoomscale=1.0, alpha=255):
1006
+ """Default draw routine for the laser path element."""
1007
+ if draw_mode & DRAW_MODE_POINTS:
1008
+ return
1009
+ point = node.point
1010
+ gc.PushState()
1011
+ mypen = wx.Pen(wx.BLACK)
1012
+ try:
1013
+ mypen.SetWidth(zoomscale)
1014
+ except TypeError:
1015
+ mypen.SetWidth(int(zoomscale))
1016
+ gc.SetPen(mypen)
1017
+ dif = 5 * zoomscale
1018
+ gc.StrokeLine(point.x - dif, point.y, point.x + dif, point.y)
1019
+ gc.StrokeLine(point.x, point.y - dif, point.x, point.y + dif)
1020
+ gc.PopState()
1021
+
1022
+ def display_label(
1023
+ self, node, gc, draw_mode=0, zoomscale=1.0, alpha=255, color="#ff0000ff"
1024
+ ):
1025
+ if node is None:
1026
+ return
1027
+ if not node.label:
1028
+ return
1029
+ try:
1030
+ bbox = node.bbox_group() if node.type == "group" else node.bbox()
1031
+ # print (f"{node.type}: {bbox}")
1032
+ except AttributeError:
1033
+ # print (f"This node has no bbox: {self.node.type}")
1034
+ return
1035
+ gc.PushState()
1036
+ cx = bbox[0] + 0.5 * (bbox[2] - bbox[0])
1037
+ cy = bbox[1] + 0.25 * (bbox[3] - bbox[1])
1038
+ symbol = node.display_label()
1039
+ font_size = 10 * zoomscale
1040
+ if font_size < 1.0:
1041
+ font_size = 1.0
1042
+ try:
1043
+ font = wx.Font(
1044
+ font_size,
1045
+ wx.FONTFAMILY_SWISS,
1046
+ wx.FONTSTYLE_NORMAL,
1047
+ wx.FONTWEIGHT_NORMAL,
1048
+ )
1049
+ except TypeError:
1050
+ font = wx.Font(
1051
+ int(font_size),
1052
+ wx.FONTFAMILY_SWISS,
1053
+ wx.FONTSTYLE_NORMAL,
1054
+ wx.FONTWEIGHT_NORMAL,
1055
+ )
1056
+ c = Color(color)
1057
+ gc.SetFont(font, wx.Colour(red=c.red, green=c.green, blue=c.blue, alpha=alpha))
1058
+ (t_width, t_height) = gc.GetTextExtent(symbol)
1059
+ x = cx - t_width / 2
1060
+ y = cy - t_height / 2
1061
+ gc.DrawText(symbol, x, y)
1062
+
1063
+ gc.PopState()
1064
+
1065
+ def draw_text_node(self, node, gc, draw_mode=0, zoomscale=1.0, alpha=255):
1066
+ if node is None:
1067
+ return
1068
+ text = node.text
1069
+ if text is None or text == "":
1070
+ return
1071
+
1072
+ try:
1073
+ matrix = node.matrix
1074
+ except AttributeError:
1075
+ matrix = None
1076
+
1077
+ svgfont_to_wx(node)
1078
+ font = node.wxfont
1079
+
1080
+ gc.PushState()
1081
+ if matrix is not None and not matrix.is_identity():
1082
+ gc.ConcatTransform(wx.GraphicsContext.CreateMatrix(gc, ZMatrix(matrix)))
1083
+ #
1084
+ # sw = node.implied_stroke_width
1085
+ # if draw_mode & DRAW_MODE_LINEWIDTH:
1086
+ # # No stroke rendering.
1087
+ # sw = 1000
1088
+ # self._set_penwidth(sw)
1089
+ # self.set_pen(
1090
+ # gc,
1091
+ # node.stroke,
1092
+ # alpha=alpha,
1093
+ # )
1094
+ # self.set_brush(gc, node.fill, alpha=255)
1095
+
1096
+ if node.fill is None or node.fill == "none":
1097
+ fill_color = wx.BLACK
1098
+ else:
1099
+ fill_color = as_wx_color(node.fill)
1100
+ gc.SetFont(font, fill_color)
1101
+
1102
+ if draw_mode & DRAW_MODE_VARIABLES:
1103
+ # Only if flag show the translated values
1104
+ text = self.context.elements.wordlist_translate(
1105
+ text, elemnode=node, increment=False
1106
+ )
1107
+ if node.texttransform is not None:
1108
+ ttf = node.texttransform.lower()
1109
+ if ttf == "capitalize":
1110
+ text = text.capitalize()
1111
+ elif ttf == "uppercase":
1112
+ text = text.upper()
1113
+ if ttf == "lowercase":
1114
+ text = text.lower()
1115
+ xmin, ymin, xmax, ymax = node.bbox(transformed=False)
1116
+ height = ymax - ymin
1117
+ width = xmax - xmin
1118
+ dy = 0
1119
+ dx = 0
1120
+ if node.anchor == "middle":
1121
+ dx -= width / 2
1122
+ elif node.anchor == "end":
1123
+ dx -= width
1124
+ gc.DrawText(text, dx, dy)
1125
+ gc.PopState()
1126
+
1127
+ def draw_image_node(self, node, gc, draw_mode, zoomscale=1.0, alpha=255):
1128
+ gc.PushState()
1129
+
1130
+ cache = None
1131
+ bounds = node.bbox()
1132
+ try:
1133
+ cache = node._cache
1134
+ except AttributeError:
1135
+ pass
1136
+ if cache is None:
1137
+ # We need to establish the cache
1138
+ try:
1139
+ image = node.active_image
1140
+ matrix = node.active_matrix
1141
+ bounds = 0, 0, image.width, image.height
1142
+ if matrix is not None and not matrix.is_identity():
1143
+ gc.ConcatTransform(wx.GraphicsContext.CreateMatrix(gc, ZMatrix(matrix)))
1144
+ except AttributeError:
1145
+ pass
1146
+
1147
+ try:
1148
+ max_allowed = node.max_allowed
1149
+ except AttributeError:
1150
+ max_allowed = 2048
1151
+ try:
1152
+ cache = self.make_thumbnail(
1153
+ image,
1154
+ maximum=max_allowed,
1155
+ alphablack=draw_mode & DRAW_MODE_ALPHABLACK == 0,
1156
+ )
1157
+ node._cache_width, node._cache_height = image.size
1158
+ node._cache = cache
1159
+ except Exception:
1160
+ pass
1161
+
1162
+ min_x, min_y, max_x, max_y = bounds
1163
+ gc.DrawBitmap(cache, min_x, min_y, max_x - min_x, max_y - min_y)
1164
+
1165
+ gc.PopState()
1166
+ if hasattr(node, "message"):
1167
+ txt = node.message
1168
+ if txt is not None:
1169
+ gc.PushState()
1170
+ gc.SetTransform(wx.GraphicsContext.CreateMatrix(gc, ZMatrix(None)))
1171
+ font = wx.Font()
1172
+ font.SetPointSize(20)
1173
+ gc.SetFont(font, wx.BLACK)
1174
+ gc.DrawText(txt, 30, 30)
1175
+ gc.PopState()
1176
+
1177
+ def measure_text(self, node):
1178
+ """
1179
+ Use default measure text routines to calculate height etc.
1180
+
1181
+ Use the real draw of the font to calculate actual size.
1182
+ A 'real' height routine needs to draw the string on an
1183
+ empty canvas and find the first and last dots on a line...
1184
+ We are creating a temporary bitmap and paint on it...
1185
+
1186
+ @param node:
1187
+ @return:
1188
+ """
1189
+ dimension_x = 1000
1190
+ dimension_y = 500
1191
+ scaling = 1
1192
+ bmp = wx.Bitmap(dimension_x, dimension_y, 32)
1193
+ dc = wx.MemoryDC()
1194
+ dc.SelectObject(bmp)
1195
+ dc.SetBackground(wx.BLACK_BRUSH)
1196
+ dc.Clear()
1197
+ gc = wx.GraphicsContext.Create(dc)
1198
+
1199
+ draw_mode = self.context.draw_mode
1200
+ if draw_mode & DRAW_MODE_VARIABLES:
1201
+ # Only if flag show the translated values
1202
+ text = self.context.elements.wordlist_translate(
1203
+ node.text, elemnode=node, increment=False
1204
+ )
1205
+ node.bounds_with_variables_translated = True
1206
+ else:
1207
+ text = node.text
1208
+ node.bounds_with_variables_translated = False
1209
+ if node.texttransform:
1210
+ ttf = node.texttransform.lower()
1211
+ if ttf == "capitalize":
1212
+ text = text.capitalize()
1213
+ elif ttf == "uppercase":
1214
+ text = text.upper()
1215
+ if ttf == "lowercase":
1216
+ text = text.lower()
1217
+ svgfont_to_wx(node)
1218
+ use_font = node.wxfont
1219
+ gc.SetFont(use_font, wx.WHITE)
1220
+ f_width, f_height, f_descent, f_external_leading = gc.GetFullTextExtent(text)
1221
+ needs_revision = False
1222
+ revision_factor = 3
1223
+ if revision_factor * f_width >= dimension_x:
1224
+ dimension_x = revision_factor * f_width
1225
+ needs_revision = True
1226
+ if revision_factor * f_height > dimension_y:
1227
+ dimension_y = revision_factor * f_height
1228
+ needs_revision = True
1229
+ if needs_revision:
1230
+ # We need to create an independent instance of the font
1231
+ # as we may to need to change the font_size temporarily
1232
+ fontdesc = node.wxfont.GetNativeFontInfoDesc()
1233
+ use_font = wx.Font(fontdesc)
1234
+ while True:
1235
+ try:
1236
+ fsize = use_font.GetFractionalPointSize()
1237
+ except AttributeError:
1238
+ fsize = use_font.GetPointSize()
1239
+ # print (f"Revised bounds: {dimension_x} x {dimension_y}, font_size={fsize} (original={fsize_org}")
1240
+ if fsize < 100 or dimension_x < 2000 or dimension_y < 1000:
1241
+ break
1242
+ # We consume an enormous amount of time and memory to create insanely big
1243
+ # temporary canvasses, so we intentionally reduce the resolution and accept
1244
+ # smaller deviations...
1245
+ scaling *= 10
1246
+ fsize /= 10
1247
+ dimension_x /= 10
1248
+ dimension_y /= 10
1249
+ try:
1250
+ use_font.SetFractionalPointSize(fsize)
1251
+ except AttributeError:
1252
+ use_font.SetPointSize(int(fsize))
1253
+
1254
+ gc.Destroy()
1255
+ dc.SelectObject(wx.NullBitmap)
1256
+ dc.Destroy()
1257
+ del dc
1258
+ bmp = wx.Bitmap(int(dimension_x), int(dimension_y), 32)
1259
+ dc = wx.MemoryDC()
1260
+ dc.SelectObject(bmp)
1261
+ dc.SetBackground(wx.BLACK_BRUSH)
1262
+ dc.Clear()
1263
+ gc = wx.GraphicsContext.Create(dc)
1264
+ gc.SetFont(use_font, wx.WHITE)
1265
+
1266
+ gc.DrawText(text, 0, 0)
1267
+ try:
1268
+ img = bmp.ConvertToImage()
1269
+ buf = img.GetData()
1270
+ image = Image.frombuffer(
1271
+ "RGB", tuple(bmp.GetSize()), bytes(buf), "raw", "RGB", 0, 1
1272
+ )
1273
+ node.text_cache = image
1274
+ img_bb = image.getbbox()
1275
+ if img_bb is None:
1276
+ node.raw_bbox = None
1277
+ else:
1278
+ newbb = (
1279
+ scaling * img_bb[0],
1280
+ scaling * img_bb[1],
1281
+ scaling * img_bb[2],
1282
+ scaling * img_bb[3],
1283
+ )
1284
+ node.raw_bbox = newbb
1285
+ except Exception:
1286
+ node.text_cache = None
1287
+ node.raw_bbox = None
1288
+ node.ascent = f_height - f_descent
1289
+ if node.baseline != "hanging":
1290
+ node.matrix.pre_translate(0, -node.ascent)
1291
+ if node.baseline == "middle":
1292
+ node.matrix.pre_translate(0, node.ascent / 2)
1293
+ node.baseline = "hanging"
1294
+ dc.SelectObject(wx.NullBitmap)
1295
+ dc.Destroy()
1296
+ del dc
1297
+
1298
+ def validate_text_nodes(self, nodes, translate_variables):
1299
+ self.context.elements.set_start_time("validate_text_nodes")
1300
+ for item in nodes:
1301
+ if item.type == "elem text" and (
1302
+ item._bounds_dirty
1303
+ or item._paint_bounds_dirty
1304
+ or item.bounds_with_variables_translated != translate_variables
1305
+ ):
1306
+ # We never drew this cleanly; our initial bounds calculations will be off if we don't premeasure
1307
+ self.measure_text(item)
1308
+ item.set_dirty_bounds()
1309
+ dummy = item.bounds
1310
+ self.context.elements.set_end_time("validate_text_nodes")
1311
+
1312
+ def make_raster(
1313
+ self,
1314
+ nodes,
1315
+ bounds,
1316
+ width=None,
1317
+ height=None,
1318
+ bitmap=False,
1319
+ step_x=1,
1320
+ step_y=1,
1321
+ keep_ratio=False,
1322
+ ):
1323
+ """
1324
+ Make Raster turns an iterable of elements and a bounds into an image of the designated size, taking into account
1325
+ the step size. The physical pixels in the image is reduced by the step size then the matrix for the element is
1326
+ scaled up by the same amount. This makes step size work like inverse dpi and correctly sets the image scale to
1327
+ the step scale for 1:1 sizes independent of the scale.
1328
+
1329
+ This function requires both wxPython and Pillow.
1330
+
1331
+ @param nodes: elements to render.
1332
+ @param bounds: bounds of those elements for the viewport.
1333
+ @param width: desired width of the resulting raster
1334
+ @param height: desired height of the resulting raster
1335
+ @param bitmap: bitmap to use rather than provisioning
1336
+ @param step_x: raster step rate, scale rate of the image.
1337
+ @param step_y: raster step rate, scale rate of the image.
1338
+ @param keep_ratio: get a picture with the same height / width
1339
+ ratio as the original
1340
+ @return:
1341
+ """
1342
+ if bounds is None:
1343
+ return None
1344
+ x_min = float("inf")
1345
+ y_min = float("inf")
1346
+ x_max = -float("inf")
1347
+ y_max = -float("inf")
1348
+ if not isinstance(nodes, (tuple, list)):
1349
+ _nodes = [nodes]
1350
+ else:
1351
+ _nodes = nodes
1352
+
1353
+ # if it's a raster we will always translate text variables...
1354
+ variable_translation = True
1355
+ nodecopy = list(_nodes)
1356
+ self.validate_text_nodes(nodecopy, variable_translation)
1357
+
1358
+ for item in _nodes:
1359
+ # bb = item.bounds
1360
+ bb = item.paint_bounds
1361
+ if bb is None:
1362
+ # Fall back to bounds
1363
+ bb = item.bounds
1364
+ if bb is None:
1365
+ continue
1366
+ if bb[0] < x_min:
1367
+ x_min = bb[0]
1368
+ if bb[1] < y_min:
1369
+ y_min = bb[1]
1370
+ if bb[2] > x_max:
1371
+ x_max = bb[2]
1372
+ if bb[3] > y_max:
1373
+ y_max = bb[3]
1374
+ raster_width = max(x_max - x_min, 1)
1375
+ raster_height = max(y_max - y_min, 1)
1376
+ if width is None:
1377
+ width = raster_width / step_x
1378
+ if height is None:
1379
+ height = raster_height / step_y
1380
+ width = max(width, 1)
1381
+ height = max(height, 1)
1382
+ bmp = wx.Bitmap(int(ceil(abs(width))), int(ceil(abs(height))), 32)
1383
+ dc = wx.MemoryDC()
1384
+ dc.SelectObject(bmp)
1385
+ dc.SetBackground(wx.WHITE_BRUSH)
1386
+ dc.Clear()
1387
+
1388
+ matrix = Matrix()
1389
+
1390
+ # Scale affine matrix up by step amount scaled down.
1391
+ try:
1392
+ scale_x = width / raster_width
1393
+ except ZeroDivisionError:
1394
+ scale_x = 1
1395
+
1396
+ try:
1397
+ scale_y = height / raster_height
1398
+ except ZeroDivisionError:
1399
+ scale_y = 1
1400
+ if keep_ratio:
1401
+ scale_x = min(scale_x, scale_y)
1402
+ scale_y = scale_x
1403
+ matrix.post_translate(-x_min, -y_min)
1404
+ matrix.post_scale(scale_x, scale_y)
1405
+ if scale_y < 0:
1406
+ matrix.pre_translate(0, -raster_height)
1407
+ if scale_x < 0:
1408
+ matrix.pre_translate(-raster_width, 0)
1409
+
1410
+ gc = wx.GraphicsContext.Create(dc)
1411
+ gc.dc = dc
1412
+ gc.SetInterpolationQuality(wx.INTERPOLATION_BEST)
1413
+ gc.PushState()
1414
+ if not matrix.is_identity():
1415
+ gc.ConcatTransform(wx.GraphicsContext.CreateMatrix(gc, ZMatrix(matrix)))
1416
+ gc.SetBrush(wx.WHITE_BRUSH)
1417
+ gc.DrawRectangle(x_min - 1, y_min - 1, x_max + 1, y_max + 1)
1418
+ self.render(_nodes, gc, draw_mode=DRAW_MODE_CACHE | DRAW_MODE_VARIABLES, msg="make_raster")
1419
+ img = bmp.ConvertToImage()
1420
+ buf = img.GetData()
1421
+ image = Image.frombuffer(
1422
+ "RGB", tuple(bmp.GetSize()), bytes(buf), "raw", "RGB", 0, 1
1423
+ )
1424
+
1425
+ gc.PopState()
1426
+ dc.SelectObject(wx.NullBitmap)
1427
+ gc.Destroy()
1428
+ del gc.dc
1429
+ del dc
1430
+ return bmp if bitmap else image
1431
+
1432
+ def make_thumbnail(
1433
+ self, pil_data, maximum=None, width=None, height=None, alphablack=True
1434
+ ):
1435
+ """Resizes the given pil image into wx.Bitmap object that fits the constraints."""
1436
+ image_width, image_height = pil_data.size
1437
+ if width is not None and height is None:
1438
+ height = width * image_height / float(image_width)
1439
+ if width is None and height is not None:
1440
+ width = height * image_width / float(image_height)
1441
+ if width is None and height is None:
1442
+ width = image_width
1443
+ height = image_height
1444
+ if maximum is not None and (width > maximum or height > maximum):
1445
+ scale_x = maximum / width
1446
+ scale_y = maximum / height
1447
+ scale = min(scale_x, scale_y)
1448
+ width = int(round(width * scale))
1449
+ height = int(round(height * scale))
1450
+ if image_width != width or image_height != height:
1451
+ pil_data = pil_data.resize((width, height))
1452
+ else:
1453
+ pil_data = pil_data.copy()
1454
+ if not alphablack:
1455
+ return wx.Bitmap.FromBufferRGBA(
1456
+ width, height, pil_data.convert("RGBA").tobytes()
1457
+ )
1458
+ if "transparency" in pil_data.info:
1459
+ pil_data = pil_data.convert("RGBA")
1460
+ try:
1461
+ # If transparent we paste 0 into the pil_data
1462
+ mask = pil_data.getchannel("A").point(lambda e: 255 - e)
1463
+ pil_data.paste(mask, None, mask)
1464
+ except ValueError:
1465
+ pass
1466
+ if pil_data.mode != "L":
1467
+ pil_data = pil_data.convert("L")
1468
+ black = Image.new("RGBA", pil_data.size, "black")
1469
+ black.putalpha(pil_data.point(lambda e: 255 - e))
1470
+ return wx.Bitmap.FromBufferRGBA(width, height, black.tobytes())