meerk40t 0.9.3001__py2.py3-none-any.whl → 0.9.7010__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 (445) 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 +1195 -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 +1844 -1507
  50. meerk40t/core/elements/clipboard.py +229 -219
  51. meerk40t/core/elements/element_treeops.py +4561 -2837
  52. meerk40t/core/elements/element_types.py +125 -105
  53. meerk40t/core/elements/elements.py +4329 -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 +933 -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/trace.py +651 -563
  66. meerk40t/core/elements/tree_commands.py +415 -409
  67. meerk40t/core/elements/undo_redo.py +116 -58
  68. meerk40t/core/elements/wordlist.py +319 -200
  69. meerk40t/core/exceptions.py +9 -9
  70. meerk40t/core/laserjob.py +220 -220
  71. meerk40t/core/logging.py +63 -63
  72. meerk40t/core/node/blobnode.py +83 -86
  73. meerk40t/core/node/bootstrap.py +105 -103
  74. meerk40t/core/node/branch_elems.py +40 -31
  75. meerk40t/core/node/branch_ops.py +45 -38
  76. meerk40t/core/node/branch_regmark.py +48 -41
  77. meerk40t/core/node/cutnode.py +29 -32
  78. meerk40t/core/node/effect_hatch.py +375 -257
  79. meerk40t/core/node/effect_warp.py +398 -0
  80. meerk40t/core/node/effect_wobble.py +441 -309
  81. meerk40t/core/node/elem_ellipse.py +404 -309
  82. meerk40t/core/node/elem_image.py +1082 -801
  83. meerk40t/core/node/elem_line.py +358 -292
  84. meerk40t/core/node/elem_path.py +259 -201
  85. meerk40t/core/node/elem_point.py +129 -102
  86. meerk40t/core/node/elem_polyline.py +310 -246
  87. meerk40t/core/node/elem_rect.py +376 -286
  88. meerk40t/core/node/elem_text.py +445 -418
  89. meerk40t/core/node/filenode.py +59 -40
  90. meerk40t/core/node/groupnode.py +138 -74
  91. meerk40t/core/node/image_processed.py +777 -766
  92. meerk40t/core/node/image_raster.py +156 -113
  93. meerk40t/core/node/layernode.py +31 -31
  94. meerk40t/core/node/mixins.py +135 -107
  95. meerk40t/core/node/node.py +1427 -1304
  96. meerk40t/core/node/nutils.py +117 -114
  97. meerk40t/core/node/op_cut.py +462 -335
  98. meerk40t/core/node/op_dots.py +296 -251
  99. meerk40t/core/node/op_engrave.py +414 -311
  100. meerk40t/core/node/op_image.py +755 -369
  101. meerk40t/core/node/op_raster.py +787 -522
  102. meerk40t/core/node/place_current.py +37 -40
  103. meerk40t/core/node/place_point.py +329 -126
  104. meerk40t/core/node/refnode.py +58 -47
  105. meerk40t/core/node/rootnode.py +225 -219
  106. meerk40t/core/node/util_console.py +48 -48
  107. meerk40t/core/node/util_goto.py +84 -65
  108. meerk40t/core/node/util_home.py +61 -61
  109. meerk40t/core/node/util_input.py +102 -102
  110. meerk40t/core/node/util_output.py +102 -102
  111. meerk40t/core/node/util_wait.py +65 -65
  112. meerk40t/core/parameters.py +709 -707
  113. meerk40t/core/planner.py +875 -785
  114. meerk40t/core/plotplanner.py +656 -652
  115. meerk40t/core/space.py +120 -113
  116. meerk40t/core/spoolers.py +706 -705
  117. meerk40t/core/svg_io.py +1836 -1549
  118. meerk40t/core/treeop.py +534 -445
  119. meerk40t/core/undos.py +278 -124
  120. meerk40t/core/units.py +784 -680
  121. meerk40t/core/view.py +393 -322
  122. meerk40t/core/webhelp.py +62 -62
  123. meerk40t/core/wordlist.py +513 -504
  124. meerk40t/cylinder/cylinder.py +247 -0
  125. meerk40t/cylinder/gui/cylindersettings.py +41 -0
  126. meerk40t/cylinder/gui/gui.py +24 -0
  127. meerk40t/device/__init__.py +1 -1
  128. meerk40t/device/basedevice.py +322 -123
  129. meerk40t/device/devicechoices.py +50 -0
  130. meerk40t/device/dummydevice.py +163 -128
  131. meerk40t/device/gui/defaultactions.py +618 -602
  132. meerk40t/device/gui/effectspanel.py +114 -0
  133. meerk40t/device/gui/formatterpanel.py +253 -290
  134. meerk40t/device/gui/warningpanel.py +337 -260
  135. meerk40t/device/mixins.py +13 -13
  136. meerk40t/dxf/__init__.py +1 -1
  137. meerk40t/dxf/dxf_io.py +766 -554
  138. meerk40t/dxf/plugin.py +47 -35
  139. meerk40t/external_plugins.py +79 -79
  140. meerk40t/external_plugins_build.py +28 -28
  141. meerk40t/extra/cag.py +112 -116
  142. meerk40t/extra/coolant.py +403 -0
  143. meerk40t/extra/encode_detect.py +198 -0
  144. meerk40t/extra/ezd.py +1165 -1165
  145. meerk40t/extra/hershey.py +835 -340
  146. meerk40t/extra/imageactions.py +322 -316
  147. meerk40t/extra/inkscape.py +630 -622
  148. meerk40t/extra/lbrn.py +424 -424
  149. meerk40t/extra/outerworld.py +284 -0
  150. meerk40t/extra/param_functions.py +1542 -1556
  151. meerk40t/extra/potrace.py +257 -253
  152. meerk40t/extra/serial_exchange.py +118 -0
  153. meerk40t/extra/updater.py +602 -453
  154. meerk40t/extra/vectrace.py +147 -146
  155. meerk40t/extra/winsleep.py +83 -83
  156. meerk40t/extra/xcs_reader.py +597 -0
  157. meerk40t/fill/fills.py +781 -335
  158. meerk40t/fill/patternfill.py +1061 -1061
  159. meerk40t/fill/patterns.py +614 -567
  160. meerk40t/grbl/control.py +87 -87
  161. meerk40t/grbl/controller.py +990 -903
  162. meerk40t/grbl/device.py +1081 -768
  163. meerk40t/grbl/driver.py +989 -771
  164. meerk40t/grbl/emulator.py +532 -497
  165. meerk40t/grbl/gcodejob.py +783 -767
  166. meerk40t/grbl/gui/grblconfiguration.py +373 -298
  167. meerk40t/grbl/gui/grblcontroller.py +485 -271
  168. meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
  169. meerk40t/grbl/gui/grbloperationconfig.py +105 -0
  170. meerk40t/grbl/gui/gui.py +147 -116
  171. meerk40t/grbl/interpreter.py +44 -44
  172. meerk40t/grbl/loader.py +22 -22
  173. meerk40t/grbl/mock_connection.py +56 -56
  174. meerk40t/grbl/plugin.py +294 -264
  175. meerk40t/grbl/serial_connection.py +93 -88
  176. meerk40t/grbl/tcp_connection.py +81 -79
  177. meerk40t/grbl/ws_connection.py +112 -0
  178. meerk40t/gui/__init__.py +1 -1
  179. meerk40t/gui/about.py +2042 -296
  180. meerk40t/gui/alignment.py +1644 -1608
  181. meerk40t/gui/autoexec.py +199 -0
  182. meerk40t/gui/basicops.py +791 -670
  183. meerk40t/gui/bufferview.py +77 -71
  184. meerk40t/gui/busy.py +170 -133
  185. meerk40t/gui/choicepropertypanel.py +1673 -1469
  186. meerk40t/gui/consolepanel.py +706 -542
  187. meerk40t/gui/devicepanel.py +687 -581
  188. meerk40t/gui/dialogoptions.py +110 -107
  189. meerk40t/gui/executejob.py +316 -306
  190. meerk40t/gui/fonts.py +90 -90
  191. meerk40t/gui/functionwrapper.py +252 -0
  192. meerk40t/gui/gui_mixins.py +729 -0
  193. meerk40t/gui/guicolors.py +205 -182
  194. meerk40t/gui/help_assets/help_assets.py +218 -201
  195. meerk40t/gui/helper.py +154 -0
  196. meerk40t/gui/hersheymanager.py +1430 -846
  197. meerk40t/gui/icons.py +3422 -2747
  198. meerk40t/gui/imagesplitter.py +555 -508
  199. meerk40t/gui/keymap.py +354 -344
  200. meerk40t/gui/laserpanel.py +892 -806
  201. meerk40t/gui/laserrender.py +1470 -1232
  202. meerk40t/gui/lasertoolpanel.py +805 -793
  203. meerk40t/gui/magnetoptions.py +436 -0
  204. meerk40t/gui/materialmanager.py +2917 -0
  205. meerk40t/gui/materialtest.py +1722 -1694
  206. meerk40t/gui/mkdebug.py +646 -359
  207. meerk40t/gui/mwindow.py +163 -140
  208. meerk40t/gui/navigationpanels.py +2605 -2467
  209. meerk40t/gui/notes.py +143 -142
  210. meerk40t/gui/opassignment.py +414 -410
  211. meerk40t/gui/operation_info.py +310 -299
  212. meerk40t/gui/plugin.py +494 -328
  213. meerk40t/gui/position.py +714 -669
  214. meerk40t/gui/preferences.py +901 -650
  215. meerk40t/gui/propertypanels/attributes.py +1461 -1131
  216. meerk40t/gui/propertypanels/blobproperty.py +117 -114
  217. meerk40t/gui/propertypanels/consoleproperty.py +83 -80
  218. meerk40t/gui/propertypanels/gotoproperty.py +77 -0
  219. meerk40t/gui/propertypanels/groupproperties.py +223 -217
  220. meerk40t/gui/propertypanels/hatchproperty.py +489 -469
  221. meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
  222. meerk40t/gui/propertypanels/inputproperty.py +59 -58
  223. meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
  224. meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
  225. meerk40t/gui/propertypanels/outputproperty.py +59 -58
  226. meerk40t/gui/propertypanels/pathproperty.py +389 -380
  227. meerk40t/gui/propertypanels/placementproperty.py +1214 -383
  228. meerk40t/gui/propertypanels/pointproperty.py +140 -136
  229. meerk40t/gui/propertypanels/propertywindow.py +313 -181
  230. meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
  231. meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
  232. meerk40t/gui/propertypanels/textproperty.py +770 -755
  233. meerk40t/gui/propertypanels/waitproperty.py +56 -55
  234. meerk40t/gui/propertypanels/warpproperty.py +121 -0
  235. meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
  236. meerk40t/gui/ribbon.py +2468 -2210
  237. meerk40t/gui/scene/scene.py +1100 -1051
  238. meerk40t/gui/scene/sceneconst.py +22 -22
  239. meerk40t/gui/scene/scenepanel.py +439 -349
  240. meerk40t/gui/scene/scenespacewidget.py +365 -365
  241. meerk40t/gui/scene/widget.py +518 -505
  242. meerk40t/gui/scenewidgets/affinemover.py +215 -215
  243. meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
  244. meerk40t/gui/scenewidgets/bedwidget.py +120 -97
  245. meerk40t/gui/scenewidgets/elementswidget.py +137 -107
  246. meerk40t/gui/scenewidgets/gridwidget.py +785 -745
  247. meerk40t/gui/scenewidgets/guidewidget.py +765 -765
  248. meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
  249. meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
  250. meerk40t/gui/scenewidgets/nodeselector.py +28 -28
  251. meerk40t/gui/scenewidgets/rectselectwidget.py +589 -346
  252. meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
  253. meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
  254. meerk40t/gui/scenewidgets/selectionwidget.py +2952 -2756
  255. meerk40t/gui/simpleui.py +357 -333
  256. meerk40t/gui/simulation.py +2431 -2094
  257. meerk40t/gui/snapoptions.py +208 -203
  258. meerk40t/gui/spoolerpanel.py +1227 -1180
  259. meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
  260. meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
  261. meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
  262. meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
  263. meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
  264. meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
  265. meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
  266. meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
  267. meerk40t/gui/themes.py +200 -78
  268. meerk40t/gui/tips.py +591 -0
  269. meerk40t/gui/toolwidgets/circlebrush.py +35 -35
  270. meerk40t/gui/toolwidgets/toolcircle.py +248 -242
  271. meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
  272. meerk40t/gui/toolwidgets/tooldraw.py +97 -90
  273. meerk40t/gui/toolwidgets/toolellipse.py +219 -212
  274. meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
  275. meerk40t/gui/toolwidgets/toolline.py +39 -144
  276. meerk40t/gui/toolwidgets/toollinetext.py +79 -236
  277. meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
  278. meerk40t/gui/toolwidgets/toolmeasure.py +160 -216
  279. meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
  280. meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
  281. meerk40t/gui/toolwidgets/toolparameter.py +754 -668
  282. meerk40t/gui/toolwidgets/toolplacement.py +108 -108
  283. meerk40t/gui/toolwidgets/toolpoint.py +68 -59
  284. meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
  285. meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
  286. meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
  287. meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
  288. meerk40t/gui/toolwidgets/toolrect.py +211 -207
  289. meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
  290. meerk40t/gui/toolwidgets/toolribbon.py +598 -113
  291. meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
  292. meerk40t/gui/toolwidgets/tooltext.py +98 -89
  293. meerk40t/gui/toolwidgets/toolvector.py +213 -204
  294. meerk40t/gui/toolwidgets/toolwidget.py +39 -39
  295. meerk40t/gui/usbconnect.py +98 -91
  296. meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
  297. meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
  298. meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
  299. meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
  300. meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
  301. meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
  302. meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
  303. meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
  304. meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
  305. meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
  306. meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
  307. meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
  308. meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
  309. meerk40t/gui/wordlisteditor.py +985 -931
  310. meerk40t/gui/wxmeerk40t.py +1444 -1169
  311. meerk40t/gui/wxmmain.py +5578 -4112
  312. meerk40t/gui/wxmribbon.py +1591 -1076
  313. meerk40t/gui/wxmscene.py +1635 -1453
  314. meerk40t/gui/wxmtree.py +2410 -2089
  315. meerk40t/gui/wxutils.py +1769 -1099
  316. meerk40t/gui/zmatrix.py +102 -102
  317. meerk40t/image/__init__.py +1 -1
  318. meerk40t/image/dither.py +429 -0
  319. meerk40t/image/imagetools.py +2778 -2269
  320. meerk40t/internal_plugins.py +150 -130
  321. meerk40t/kernel/__init__.py +63 -12
  322. meerk40t/kernel/channel.py +259 -212
  323. meerk40t/kernel/context.py +538 -538
  324. meerk40t/kernel/exceptions.py +41 -41
  325. meerk40t/kernel/functions.py +463 -414
  326. meerk40t/kernel/jobs.py +100 -100
  327. meerk40t/kernel/kernel.py +3809 -3571
  328. meerk40t/kernel/lifecycles.py +71 -71
  329. meerk40t/kernel/module.py +49 -49
  330. meerk40t/kernel/service.py +147 -147
  331. meerk40t/kernel/settings.py +383 -343
  332. meerk40t/lihuiyu/controller.py +883 -876
  333. meerk40t/lihuiyu/device.py +1181 -1069
  334. meerk40t/lihuiyu/driver.py +1466 -1372
  335. meerk40t/lihuiyu/gui/gui.py +127 -106
  336. meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
  337. meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
  338. meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
  339. meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
  340. meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
  341. meerk40t/lihuiyu/interpreter.py +53 -53
  342. meerk40t/lihuiyu/laserspeed.py +450 -450
  343. meerk40t/lihuiyu/loader.py +90 -90
  344. meerk40t/lihuiyu/parser.py +404 -404
  345. meerk40t/lihuiyu/plugin.py +101 -102
  346. meerk40t/lihuiyu/tcp_connection.py +111 -109
  347. meerk40t/main.py +231 -165
  348. meerk40t/moshi/builder.py +788 -781
  349. meerk40t/moshi/controller.py +505 -499
  350. meerk40t/moshi/device.py +495 -442
  351. meerk40t/moshi/driver.py +862 -696
  352. meerk40t/moshi/gui/gui.py +78 -76
  353. meerk40t/moshi/gui/moshicontrollergui.py +538 -522
  354. meerk40t/moshi/gui/moshidrivergui.py +87 -75
  355. meerk40t/moshi/plugin.py +43 -43
  356. meerk40t/network/console_server.py +102 -57
  357. meerk40t/network/kernelserver.py +10 -9
  358. meerk40t/network/tcp_server.py +142 -140
  359. meerk40t/network/udp_server.py +103 -77
  360. meerk40t/network/web_server.py +390 -0
  361. meerk40t/newly/controller.py +1158 -1144
  362. meerk40t/newly/device.py +874 -732
  363. meerk40t/newly/driver.py +540 -412
  364. meerk40t/newly/gui/gui.py +219 -188
  365. meerk40t/newly/gui/newlyconfig.py +116 -101
  366. meerk40t/newly/gui/newlycontroller.py +193 -186
  367. meerk40t/newly/gui/operationproperties.py +51 -51
  368. meerk40t/newly/mock_connection.py +82 -82
  369. meerk40t/newly/newly_params.py +56 -56
  370. meerk40t/newly/plugin.py +1214 -1246
  371. meerk40t/newly/usb_connection.py +322 -322
  372. meerk40t/rotary/gui/gui.py +52 -46
  373. meerk40t/rotary/gui/rotarysettings.py +240 -232
  374. meerk40t/rotary/rotary.py +202 -98
  375. meerk40t/ruida/control.py +291 -91
  376. meerk40t/ruida/controller.py +138 -1088
  377. meerk40t/ruida/device.py +672 -231
  378. meerk40t/ruida/driver.py +534 -472
  379. meerk40t/ruida/emulator.py +1494 -1491
  380. meerk40t/ruida/exceptions.py +4 -4
  381. meerk40t/ruida/gui/gui.py +71 -76
  382. meerk40t/ruida/gui/ruidaconfig.py +239 -72
  383. meerk40t/ruida/gui/ruidacontroller.py +187 -184
  384. meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
  385. meerk40t/ruida/loader.py +54 -52
  386. meerk40t/ruida/mock_connection.py +57 -109
  387. meerk40t/ruida/plugin.py +124 -87
  388. meerk40t/ruida/rdjob.py +2084 -945
  389. meerk40t/ruida/serial_connection.py +116 -0
  390. meerk40t/ruida/tcp_connection.py +146 -0
  391. meerk40t/ruida/udp_connection.py +73 -0
  392. meerk40t/svgelements.py +9671 -9669
  393. meerk40t/tools/driver_to_path.py +584 -579
  394. meerk40t/tools/geomstr.py +5583 -4680
  395. meerk40t/tools/jhfparser.py +357 -292
  396. meerk40t/tools/kerftest.py +904 -890
  397. meerk40t/tools/livinghinges.py +1168 -1033
  398. meerk40t/tools/pathtools.py +987 -949
  399. meerk40t/tools/pmatrix.py +234 -0
  400. meerk40t/tools/pointfinder.py +942 -942
  401. meerk40t/tools/polybool.py +940 -940
  402. meerk40t/tools/rasterplotter.py +1660 -547
  403. meerk40t/tools/shxparser.py +989 -901
  404. meerk40t/tools/ttfparser.py +726 -446
  405. meerk40t/tools/zinglplotter.py +595 -593
  406. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/LICENSE +21 -21
  407. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/METADATA +150 -139
  408. meerk40t-0.9.7010.dist-info/RECORD +445 -0
  409. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/WHEEL +1 -1
  410. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/top_level.txt +0 -1
  411. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/zip-safe +1 -1
  412. meerk40t/balormk/elementlightjob.py +0 -159
  413. meerk40t-0.9.3001.dist-info/RECORD +0 -437
  414. test/bootstrap.py +0 -63
  415. test/test_cli.py +0 -12
  416. test/test_core_cutcode.py +0 -418
  417. test/test_core_elements.py +0 -144
  418. test/test_core_plotplanner.py +0 -397
  419. test/test_core_viewports.py +0 -312
  420. test/test_drivers_grbl.py +0 -108
  421. test/test_drivers_lihuiyu.py +0 -443
  422. test/test_drivers_newly.py +0 -113
  423. test/test_element_degenerate_points.py +0 -43
  424. test/test_elements_classify.py +0 -97
  425. test/test_elements_penbox.py +0 -22
  426. test/test_file_svg.py +0 -176
  427. test/test_fill.py +0 -155
  428. test/test_geomstr.py +0 -1523
  429. test/test_geomstr_nodes.py +0 -18
  430. test/test_imagetools_actualize.py +0 -306
  431. test/test_imagetools_wizard.py +0 -258
  432. test/test_kernel.py +0 -200
  433. test/test_laser_speeds.py +0 -3303
  434. test/test_length.py +0 -57
  435. test/test_lifecycle.py +0 -66
  436. test/test_operations.py +0 -251
  437. test/test_operations_hatch.py +0 -57
  438. test/test_ruida.py +0 -19
  439. test/test_spooler.py +0 -22
  440. test/test_tools_rasterplotter.py +0 -29
  441. test/test_wobble.py +0 -133
  442. test/test_zingl.py +0 -124
  443. {test → meerk40t/cylinder}/__init__.py +0 -0
  444. /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
  445. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.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())