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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. meerk40t/__init__.py +1 -1
  2. meerk40t/balormk/balor_params.py +167 -167
  3. meerk40t/balormk/clone_loader.py +457 -457
  4. meerk40t/balormk/controller.py +1566 -1512
  5. meerk40t/balormk/cylindermod.py +64 -0
  6. meerk40t/balormk/device.py +966 -1959
  7. meerk40t/balormk/driver.py +778 -591
  8. meerk40t/balormk/galvo_commands.py +1194 -0
  9. meerk40t/balormk/gui/balorconfig.py +237 -111
  10. meerk40t/balormk/gui/balorcontroller.py +191 -184
  11. meerk40t/balormk/gui/baloroperationproperties.py +116 -115
  12. meerk40t/balormk/gui/corscene.py +845 -0
  13. meerk40t/balormk/gui/gui.py +179 -147
  14. meerk40t/balormk/livelightjob.py +466 -382
  15. meerk40t/balormk/mock_connection.py +131 -109
  16. meerk40t/balormk/plugin.py +133 -135
  17. meerk40t/balormk/usb_connection.py +306 -301
  18. meerk40t/camera/__init__.py +1 -1
  19. meerk40t/camera/camera.py +514 -397
  20. meerk40t/camera/gui/camerapanel.py +1241 -1095
  21. meerk40t/camera/gui/gui.py +58 -58
  22. meerk40t/camera/plugin.py +441 -399
  23. meerk40t/ch341/__init__.py +27 -27
  24. meerk40t/ch341/ch341device.py +628 -628
  25. meerk40t/ch341/libusb.py +595 -589
  26. meerk40t/ch341/mock.py +171 -171
  27. meerk40t/ch341/windriver.py +157 -157
  28. meerk40t/constants.py +13 -0
  29. meerk40t/core/__init__.py +1 -1
  30. meerk40t/core/bindalias.py +550 -539
  31. meerk40t/core/core.py +47 -47
  32. meerk40t/core/cutcode/cubiccut.py +73 -73
  33. meerk40t/core/cutcode/cutcode.py +315 -312
  34. meerk40t/core/cutcode/cutgroup.py +141 -137
  35. meerk40t/core/cutcode/cutobject.py +192 -185
  36. meerk40t/core/cutcode/dwellcut.py +37 -37
  37. meerk40t/core/cutcode/gotocut.py +29 -29
  38. meerk40t/core/cutcode/homecut.py +29 -29
  39. meerk40t/core/cutcode/inputcut.py +34 -34
  40. meerk40t/core/cutcode/linecut.py +33 -33
  41. meerk40t/core/cutcode/outputcut.py +34 -34
  42. meerk40t/core/cutcode/plotcut.py +335 -335
  43. meerk40t/core/cutcode/quadcut.py +61 -61
  44. meerk40t/core/cutcode/rastercut.py +168 -148
  45. meerk40t/core/cutcode/waitcut.py +34 -34
  46. meerk40t/core/cutplan.py +1843 -1316
  47. meerk40t/core/drivers.py +330 -329
  48. meerk40t/core/elements/align.py +801 -669
  49. meerk40t/core/elements/branches.py +1858 -1507
  50. meerk40t/core/elements/clipboard.py +229 -219
  51. meerk40t/core/elements/element_treeops.py +4595 -2837
  52. meerk40t/core/elements/element_types.py +125 -105
  53. meerk40t/core/elements/elements.py +4315 -3617
  54. meerk40t/core/elements/files.py +117 -64
  55. meerk40t/core/elements/geometry.py +473 -224
  56. meerk40t/core/elements/grid.py +467 -316
  57. meerk40t/core/elements/materials.py +158 -94
  58. meerk40t/core/elements/notes.py +50 -38
  59. meerk40t/core/elements/offset_clpr.py +934 -912
  60. meerk40t/core/elements/offset_mk.py +963 -955
  61. meerk40t/core/elements/penbox.py +339 -267
  62. meerk40t/core/elements/placements.py +300 -83
  63. meerk40t/core/elements/render.py +785 -687
  64. meerk40t/core/elements/shapes.py +2618 -2092
  65. meerk40t/core/elements/testcases.py +105 -0
  66. meerk40t/core/elements/trace.py +651 -563
  67. meerk40t/core/elements/tree_commands.py +415 -409
  68. meerk40t/core/elements/undo_redo.py +116 -58
  69. meerk40t/core/elements/wordlist.py +319 -200
  70. meerk40t/core/exceptions.py +9 -9
  71. meerk40t/core/laserjob.py +220 -220
  72. meerk40t/core/logging.py +63 -63
  73. meerk40t/core/node/blobnode.py +83 -86
  74. meerk40t/core/node/bootstrap.py +105 -103
  75. meerk40t/core/node/branch_elems.py +40 -31
  76. meerk40t/core/node/branch_ops.py +45 -38
  77. meerk40t/core/node/branch_regmark.py +48 -41
  78. meerk40t/core/node/cutnode.py +29 -32
  79. meerk40t/core/node/effect_hatch.py +375 -257
  80. meerk40t/core/node/effect_warp.py +398 -0
  81. meerk40t/core/node/effect_wobble.py +441 -309
  82. meerk40t/core/node/elem_ellipse.py +404 -309
  83. meerk40t/core/node/elem_image.py +1082 -801
  84. meerk40t/core/node/elem_line.py +358 -292
  85. meerk40t/core/node/elem_path.py +259 -201
  86. meerk40t/core/node/elem_point.py +129 -102
  87. meerk40t/core/node/elem_polyline.py +310 -246
  88. meerk40t/core/node/elem_rect.py +376 -286
  89. meerk40t/core/node/elem_text.py +445 -418
  90. meerk40t/core/node/filenode.py +59 -40
  91. meerk40t/core/node/groupnode.py +138 -74
  92. meerk40t/core/node/image_processed.py +777 -766
  93. meerk40t/core/node/image_raster.py +156 -113
  94. meerk40t/core/node/layernode.py +31 -31
  95. meerk40t/core/node/mixins.py +135 -107
  96. meerk40t/core/node/node.py +1427 -1304
  97. meerk40t/core/node/nutils.py +117 -114
  98. meerk40t/core/node/op_cut.py +463 -335
  99. meerk40t/core/node/op_dots.py +296 -251
  100. meerk40t/core/node/op_engrave.py +414 -311
  101. meerk40t/core/node/op_image.py +755 -369
  102. meerk40t/core/node/op_raster.py +787 -522
  103. meerk40t/core/node/place_current.py +37 -40
  104. meerk40t/core/node/place_point.py +329 -126
  105. meerk40t/core/node/refnode.py +58 -47
  106. meerk40t/core/node/rootnode.py +225 -219
  107. meerk40t/core/node/util_console.py +48 -48
  108. meerk40t/core/node/util_goto.py +84 -65
  109. meerk40t/core/node/util_home.py +61 -61
  110. meerk40t/core/node/util_input.py +102 -102
  111. meerk40t/core/node/util_output.py +102 -102
  112. meerk40t/core/node/util_wait.py +65 -65
  113. meerk40t/core/parameters.py +709 -707
  114. meerk40t/core/planner.py +875 -785
  115. meerk40t/core/plotplanner.py +656 -652
  116. meerk40t/core/space.py +120 -113
  117. meerk40t/core/spoolers.py +706 -705
  118. meerk40t/core/svg_io.py +1836 -1549
  119. meerk40t/core/treeop.py +534 -445
  120. meerk40t/core/undos.py +278 -124
  121. meerk40t/core/units.py +784 -680
  122. meerk40t/core/view.py +393 -322
  123. meerk40t/core/webhelp.py +62 -62
  124. meerk40t/core/wordlist.py +513 -504
  125. meerk40t/cylinder/cylinder.py +247 -0
  126. meerk40t/cylinder/gui/cylindersettings.py +41 -0
  127. meerk40t/cylinder/gui/gui.py +24 -0
  128. meerk40t/device/__init__.py +1 -1
  129. meerk40t/device/basedevice.py +322 -123
  130. meerk40t/device/devicechoices.py +50 -0
  131. meerk40t/device/dummydevice.py +163 -128
  132. meerk40t/device/gui/defaultactions.py +618 -602
  133. meerk40t/device/gui/effectspanel.py +114 -0
  134. meerk40t/device/gui/formatterpanel.py +253 -290
  135. meerk40t/device/gui/warningpanel.py +337 -260
  136. meerk40t/device/mixins.py +13 -13
  137. meerk40t/dxf/__init__.py +1 -1
  138. meerk40t/dxf/dxf_io.py +766 -554
  139. meerk40t/dxf/plugin.py +47 -35
  140. meerk40t/external_plugins.py +79 -79
  141. meerk40t/external_plugins_build.py +28 -28
  142. meerk40t/extra/cag.py +112 -116
  143. meerk40t/extra/coolant.py +403 -0
  144. meerk40t/extra/encode_detect.py +204 -0
  145. meerk40t/extra/ezd.py +1165 -1165
  146. meerk40t/extra/hershey.py +834 -340
  147. meerk40t/extra/imageactions.py +322 -316
  148. meerk40t/extra/inkscape.py +628 -622
  149. meerk40t/extra/lbrn.py +424 -424
  150. meerk40t/extra/outerworld.py +283 -0
  151. meerk40t/extra/param_functions.py +1542 -1556
  152. meerk40t/extra/potrace.py +257 -253
  153. meerk40t/extra/serial_exchange.py +118 -0
  154. meerk40t/extra/updater.py +602 -453
  155. meerk40t/extra/vectrace.py +147 -146
  156. meerk40t/extra/winsleep.py +83 -83
  157. meerk40t/extra/xcs_reader.py +597 -0
  158. meerk40t/fill/fills.py +781 -335
  159. meerk40t/fill/patternfill.py +1061 -1061
  160. meerk40t/fill/patterns.py +614 -567
  161. meerk40t/grbl/control.py +87 -87
  162. meerk40t/grbl/controller.py +990 -903
  163. meerk40t/grbl/device.py +1084 -768
  164. meerk40t/grbl/driver.py +989 -771
  165. meerk40t/grbl/emulator.py +532 -497
  166. meerk40t/grbl/gcodejob.py +783 -767
  167. meerk40t/grbl/gui/grblconfiguration.py +373 -298
  168. meerk40t/grbl/gui/grblcontroller.py +485 -271
  169. meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
  170. meerk40t/grbl/gui/grbloperationconfig.py +105 -0
  171. meerk40t/grbl/gui/gui.py +147 -116
  172. meerk40t/grbl/interpreter.py +44 -44
  173. meerk40t/grbl/loader.py +22 -22
  174. meerk40t/grbl/mock_connection.py +56 -56
  175. meerk40t/grbl/plugin.py +294 -264
  176. meerk40t/grbl/serial_connection.py +93 -88
  177. meerk40t/grbl/tcp_connection.py +81 -79
  178. meerk40t/grbl/ws_connection.py +112 -0
  179. meerk40t/gui/__init__.py +1 -1
  180. meerk40t/gui/about.py +2042 -296
  181. meerk40t/gui/alignment.py +1644 -1608
  182. meerk40t/gui/autoexec.py +199 -0
  183. meerk40t/gui/basicops.py +791 -670
  184. meerk40t/gui/bufferview.py +77 -71
  185. meerk40t/gui/busy.py +232 -133
  186. meerk40t/gui/choicepropertypanel.py +1662 -1469
  187. meerk40t/gui/consolepanel.py +706 -542
  188. meerk40t/gui/devicepanel.py +687 -581
  189. meerk40t/gui/dialogoptions.py +110 -107
  190. meerk40t/gui/executejob.py +316 -306
  191. meerk40t/gui/fonts.py +90 -90
  192. meerk40t/gui/functionwrapper.py +252 -0
  193. meerk40t/gui/gui_mixins.py +729 -0
  194. meerk40t/gui/guicolors.py +205 -182
  195. meerk40t/gui/help_assets/help_assets.py +218 -201
  196. meerk40t/gui/helper.py +154 -0
  197. meerk40t/gui/hersheymanager.py +1440 -846
  198. meerk40t/gui/icons.py +3422 -2747
  199. meerk40t/gui/imagesplitter.py +555 -508
  200. meerk40t/gui/keymap.py +354 -344
  201. meerk40t/gui/laserpanel.py +897 -806
  202. meerk40t/gui/laserrender.py +1470 -1232
  203. meerk40t/gui/lasertoolpanel.py +805 -793
  204. meerk40t/gui/magnetoptions.py +436 -0
  205. meerk40t/gui/materialmanager.py +2944 -0
  206. meerk40t/gui/materialtest.py +1722 -1694
  207. meerk40t/gui/mkdebug.py +646 -359
  208. meerk40t/gui/mwindow.py +163 -140
  209. meerk40t/gui/navigationpanels.py +2605 -2467
  210. meerk40t/gui/notes.py +143 -142
  211. meerk40t/gui/opassignment.py +414 -410
  212. meerk40t/gui/operation_info.py +310 -299
  213. meerk40t/gui/plugin.py +500 -328
  214. meerk40t/gui/position.py +714 -669
  215. meerk40t/gui/preferences.py +901 -650
  216. meerk40t/gui/propertypanels/attributes.py +1461 -1131
  217. meerk40t/gui/propertypanels/blobproperty.py +117 -114
  218. meerk40t/gui/propertypanels/consoleproperty.py +83 -80
  219. meerk40t/gui/propertypanels/gotoproperty.py +77 -0
  220. meerk40t/gui/propertypanels/groupproperties.py +223 -217
  221. meerk40t/gui/propertypanels/hatchproperty.py +489 -469
  222. meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
  223. meerk40t/gui/propertypanels/inputproperty.py +59 -58
  224. meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
  225. meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
  226. meerk40t/gui/propertypanels/outputproperty.py +59 -58
  227. meerk40t/gui/propertypanels/pathproperty.py +389 -380
  228. meerk40t/gui/propertypanels/placementproperty.py +1214 -383
  229. meerk40t/gui/propertypanels/pointproperty.py +140 -136
  230. meerk40t/gui/propertypanels/propertywindow.py +313 -181
  231. meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
  232. meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
  233. meerk40t/gui/propertypanels/textproperty.py +770 -755
  234. meerk40t/gui/propertypanels/waitproperty.py +56 -55
  235. meerk40t/gui/propertypanels/warpproperty.py +121 -0
  236. meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
  237. meerk40t/gui/ribbon.py +2471 -2210
  238. meerk40t/gui/scene/scene.py +1100 -1051
  239. meerk40t/gui/scene/sceneconst.py +22 -22
  240. meerk40t/gui/scene/scenepanel.py +439 -349
  241. meerk40t/gui/scene/scenespacewidget.py +365 -365
  242. meerk40t/gui/scene/widget.py +518 -505
  243. meerk40t/gui/scenewidgets/affinemover.py +215 -215
  244. meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
  245. meerk40t/gui/scenewidgets/bedwidget.py +120 -97
  246. meerk40t/gui/scenewidgets/elementswidget.py +137 -107
  247. meerk40t/gui/scenewidgets/gridwidget.py +785 -745
  248. meerk40t/gui/scenewidgets/guidewidget.py +765 -765
  249. meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
  250. meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
  251. meerk40t/gui/scenewidgets/nodeselector.py +28 -28
  252. meerk40t/gui/scenewidgets/rectselectwidget.py +592 -346
  253. meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
  254. meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
  255. meerk40t/gui/scenewidgets/selectionwidget.py +2958 -2756
  256. meerk40t/gui/simpleui.py +362 -333
  257. meerk40t/gui/simulation.py +2451 -2094
  258. meerk40t/gui/snapoptions.py +208 -203
  259. meerk40t/gui/spoolerpanel.py +1227 -1180
  260. meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
  261. meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
  262. meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
  263. meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
  264. meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
  265. meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
  266. meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
  267. meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
  268. meerk40t/gui/themes.py +200 -78
  269. meerk40t/gui/tips.py +590 -0
  270. meerk40t/gui/toolwidgets/circlebrush.py +35 -35
  271. meerk40t/gui/toolwidgets/toolcircle.py +248 -242
  272. meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
  273. meerk40t/gui/toolwidgets/tooldraw.py +97 -90
  274. meerk40t/gui/toolwidgets/toolellipse.py +219 -212
  275. meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
  276. meerk40t/gui/toolwidgets/toolline.py +39 -144
  277. meerk40t/gui/toolwidgets/toollinetext.py +79 -236
  278. meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
  279. meerk40t/gui/toolwidgets/toolmeasure.py +163 -216
  280. meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
  281. meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
  282. meerk40t/gui/toolwidgets/toolparameter.py +754 -668
  283. meerk40t/gui/toolwidgets/toolplacement.py +108 -108
  284. meerk40t/gui/toolwidgets/toolpoint.py +68 -59
  285. meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
  286. meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
  287. meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
  288. meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
  289. meerk40t/gui/toolwidgets/toolrect.py +211 -207
  290. meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
  291. meerk40t/gui/toolwidgets/toolribbon.py +598 -113
  292. meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
  293. meerk40t/gui/toolwidgets/tooltext.py +98 -89
  294. meerk40t/gui/toolwidgets/toolvector.py +213 -204
  295. meerk40t/gui/toolwidgets/toolwidget.py +39 -39
  296. meerk40t/gui/usbconnect.py +98 -91
  297. meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
  298. meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
  299. meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
  300. meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
  301. meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
  302. meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
  303. meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
  304. meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
  305. meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
  306. meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
  307. meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
  308. meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
  309. meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
  310. meerk40t/gui/wordlisteditor.py +985 -931
  311. meerk40t/gui/wxmeerk40t.py +1447 -1169
  312. meerk40t/gui/wxmmain.py +5644 -4112
  313. meerk40t/gui/wxmribbon.py +1591 -1076
  314. meerk40t/gui/wxmscene.py +1631 -1453
  315. meerk40t/gui/wxmtree.py +2416 -2089
  316. meerk40t/gui/wxutils.py +1769 -1099
  317. meerk40t/gui/zmatrix.py +102 -102
  318. meerk40t/image/__init__.py +1 -1
  319. meerk40t/image/dither.py +429 -0
  320. meerk40t/image/imagetools.py +2793 -2269
  321. meerk40t/internal_plugins.py +150 -130
  322. meerk40t/kernel/__init__.py +63 -12
  323. meerk40t/kernel/channel.py +259 -212
  324. meerk40t/kernel/context.py +538 -538
  325. meerk40t/kernel/exceptions.py +41 -41
  326. meerk40t/kernel/functions.py +463 -414
  327. meerk40t/kernel/jobs.py +100 -100
  328. meerk40t/kernel/kernel.py +3828 -3571
  329. meerk40t/kernel/lifecycles.py +71 -71
  330. meerk40t/kernel/module.py +49 -49
  331. meerk40t/kernel/service.py +147 -147
  332. meerk40t/kernel/settings.py +383 -343
  333. meerk40t/lihuiyu/controller.py +883 -876
  334. meerk40t/lihuiyu/device.py +1181 -1069
  335. meerk40t/lihuiyu/driver.py +1466 -1372
  336. meerk40t/lihuiyu/gui/gui.py +127 -106
  337. meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
  338. meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
  339. meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
  340. meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
  341. meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
  342. meerk40t/lihuiyu/interpreter.py +53 -53
  343. meerk40t/lihuiyu/laserspeed.py +450 -450
  344. meerk40t/lihuiyu/loader.py +90 -90
  345. meerk40t/lihuiyu/parser.py +404 -404
  346. meerk40t/lihuiyu/plugin.py +101 -102
  347. meerk40t/lihuiyu/tcp_connection.py +111 -109
  348. meerk40t/main.py +231 -165
  349. meerk40t/moshi/builder.py +788 -781
  350. meerk40t/moshi/controller.py +505 -499
  351. meerk40t/moshi/device.py +495 -442
  352. meerk40t/moshi/driver.py +862 -696
  353. meerk40t/moshi/gui/gui.py +78 -76
  354. meerk40t/moshi/gui/moshicontrollergui.py +538 -522
  355. meerk40t/moshi/gui/moshidrivergui.py +87 -75
  356. meerk40t/moshi/plugin.py +43 -43
  357. meerk40t/network/console_server.py +140 -57
  358. meerk40t/network/kernelserver.py +10 -9
  359. meerk40t/network/tcp_server.py +142 -140
  360. meerk40t/network/udp_server.py +103 -77
  361. meerk40t/network/web_server.py +404 -0
  362. meerk40t/newly/controller.py +1158 -1144
  363. meerk40t/newly/device.py +874 -732
  364. meerk40t/newly/driver.py +540 -412
  365. meerk40t/newly/gui/gui.py +219 -188
  366. meerk40t/newly/gui/newlyconfig.py +116 -101
  367. meerk40t/newly/gui/newlycontroller.py +193 -186
  368. meerk40t/newly/gui/operationproperties.py +51 -51
  369. meerk40t/newly/mock_connection.py +82 -82
  370. meerk40t/newly/newly_params.py +56 -56
  371. meerk40t/newly/plugin.py +1214 -1246
  372. meerk40t/newly/usb_connection.py +322 -322
  373. meerk40t/rotary/gui/gui.py +52 -46
  374. meerk40t/rotary/gui/rotarysettings.py +240 -232
  375. meerk40t/rotary/rotary.py +202 -98
  376. meerk40t/ruida/control.py +291 -91
  377. meerk40t/ruida/controller.py +138 -1088
  378. meerk40t/ruida/device.py +676 -231
  379. meerk40t/ruida/driver.py +534 -472
  380. meerk40t/ruida/emulator.py +1494 -1491
  381. meerk40t/ruida/exceptions.py +4 -4
  382. meerk40t/ruida/gui/gui.py +71 -76
  383. meerk40t/ruida/gui/ruidaconfig.py +239 -72
  384. meerk40t/ruida/gui/ruidacontroller.py +187 -184
  385. meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
  386. meerk40t/ruida/loader.py +54 -52
  387. meerk40t/ruida/mock_connection.py +57 -109
  388. meerk40t/ruida/plugin.py +124 -87
  389. meerk40t/ruida/rdjob.py +2084 -945
  390. meerk40t/ruida/serial_connection.py +116 -0
  391. meerk40t/ruida/tcp_connection.py +146 -0
  392. meerk40t/ruida/udp_connection.py +73 -0
  393. meerk40t/svgelements.py +9671 -9669
  394. meerk40t/tools/driver_to_path.py +584 -579
  395. meerk40t/tools/geomstr.py +5583 -4680
  396. meerk40t/tools/jhfparser.py +357 -292
  397. meerk40t/tools/kerftest.py +904 -890
  398. meerk40t/tools/livinghinges.py +1168 -1033
  399. meerk40t/tools/pathtools.py +987 -949
  400. meerk40t/tools/pmatrix.py +234 -0
  401. meerk40t/tools/pointfinder.py +942 -942
  402. meerk40t/tools/polybool.py +941 -940
  403. meerk40t/tools/rasterplotter.py +1660 -547
  404. meerk40t/tools/shxparser.py +1047 -901
  405. meerk40t/tools/ttfparser.py +726 -446
  406. meerk40t/tools/zinglplotter.py +595 -593
  407. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/LICENSE +21 -21
  408. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/METADATA +150 -139
  409. meerk40t-0.9.7020.dist-info/RECORD +446 -0
  410. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/WHEEL +1 -1
  411. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/top_level.txt +0 -1
  412. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/zip-safe +1 -1
  413. meerk40t/balormk/elementlightjob.py +0 -159
  414. meerk40t-0.9.3001.dist-info/RECORD +0 -437
  415. test/bootstrap.py +0 -63
  416. test/test_cli.py +0 -12
  417. test/test_core_cutcode.py +0 -418
  418. test/test_core_elements.py +0 -144
  419. test/test_core_plotplanner.py +0 -397
  420. test/test_core_viewports.py +0 -312
  421. test/test_drivers_grbl.py +0 -108
  422. test/test_drivers_lihuiyu.py +0 -443
  423. test/test_drivers_newly.py +0 -113
  424. test/test_element_degenerate_points.py +0 -43
  425. test/test_elements_classify.py +0 -97
  426. test/test_elements_penbox.py +0 -22
  427. test/test_file_svg.py +0 -176
  428. test/test_fill.py +0 -155
  429. test/test_geomstr.py +0 -1523
  430. test/test_geomstr_nodes.py +0 -18
  431. test/test_imagetools_actualize.py +0 -306
  432. test/test_imagetools_wizard.py +0 -258
  433. test/test_kernel.py +0 -200
  434. test/test_laser_speeds.py +0 -3303
  435. test/test_length.py +0 -57
  436. test/test_lifecycle.py +0 -66
  437. test/test_operations.py +0 -251
  438. test/test_operations_hatch.py +0 -57
  439. test/test_ruida.py +0 -19
  440. test/test_spooler.py +0 -22
  441. test/test_tools_rasterplotter.py +0 -29
  442. test/test_wobble.py +0 -133
  443. test/test_zingl.py +0 -124
  444. {test → meerk40t/cylinder}/__init__.py +0 -0
  445. /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
  446. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/entry_points.txt +0 -0
@@ -1,846 +1,1440 @@
1
- import os
2
- import platform
3
- from glob import glob
4
- from math import isinf
5
-
6
- import wx
7
-
8
- from meerk40t.core.units import UNITS_PER_INCH, Length
9
- from meerk40t.extra.hershey import (
10
- create_linetext_node,
11
- fonts_registered,
12
- update_linetext,
13
- validate_node,
14
- )
15
- from meerk40t.gui.icons import STD_ICON_SIZE, get_default_icon_size, icons8_choose_font
16
- from meerk40t.gui.mwindow import MWindow
17
- from meerk40t.gui.wxutils import StaticBoxSizer, dip_size
18
- from meerk40t.kernel import get_safe_path
19
-
20
- _ = wx.GetTranslation
21
-
22
-
23
- def create_preview_image(context, fontfile):
24
- simplefont = os.path.basename(fontfile)
25
- base, ext = os.path.splitext(fontfile)
26
- bmpfile = base + ".png"
27
- pattern = "The quick brown fox..."
28
- try:
29
- node = create_linetext_node(
30
- context, 0, 0, pattern, font=simplefont, font_size=Length("12pt")
31
- )
32
- except:
33
- # We may encounter an IndexError, a ValueError or an error thrown by struct
34
- # The latter cannot be named? So a global except...
35
- return False
36
- if node is None:
37
- return False
38
- if node.bounds is None:
39
- return False
40
- make_raster = context.elements.lookup("render-op/make_raster")
41
- if make_raster is None:
42
- return False
43
- xmin, ymin, xmax, ymax = node.bounds
44
- if isinf(xmin):
45
- # No bounds for selected elements
46
- return False
47
- width = xmax - xmin
48
- height = ymax - ymin
49
- dpi = 150
50
- dots_per_units = dpi / UNITS_PER_INCH
51
- new_width = width * dots_per_units
52
- new_height = height * dots_per_units
53
- new_height = max(new_height, 1)
54
- new_width = max(new_width, 1)
55
- try:
56
- bitmap = make_raster(
57
- [node],
58
- bounds=node.bounds,
59
- width=new_width,
60
- height=new_height,
61
- bitmap=True,
62
- )
63
- except:
64
- # Invalid path or whatever...
65
- return False
66
- try:
67
- bitmap.SaveFile(bmpfile, wx.BITMAP_TYPE_PNG)
68
- except (OSError, RuntimeError, PermissionError, FileNotFoundError):
69
- return False
70
- return True
71
-
72
-
73
- def load_create_preview_file(context, fontfile):
74
- bitmap = None
75
- base, ext = os.path.splitext(fontfile)
76
- bmpfile = base + ".png"
77
- if not os.path.exists(bmpfile):
78
- __ = create_preview_image(context, fontfile)
79
- if os.path.exists(bmpfile):
80
- bitmap = wx.Bitmap()
81
- bitmap.LoadFile(bmpfile, wx.BITMAP_TYPE_PNG)
82
- return bitmap
83
-
84
-
85
- def fontdirectory(context):
86
- fontdir = ""
87
- safe_dir = os.path.realpath(get_safe_path(context.kernel.name))
88
- context.setting(str, "font_directory", safe_dir)
89
- fontdir = context.font_directory
90
- return fontdir
91
-
92
-
93
- def remove_fontfile(fontfile):
94
- if os.path.exists(fontfile):
95
- try:
96
- os.remove(fontfile)
97
- base, ext = os.path.splitext(fontfile)
98
- bmpfile = base + ".png"
99
- if os.path.exists(bmpfile):
100
- os.remove(bmpfile)
101
- except (OSError, RuntimeError, PermissionError, FileNotFoundError):
102
- pass
103
-
104
-
105
- class LineTextPropertyPanel(wx.Panel):
106
- """
107
- Panel for post-creation text property editing
108
- """
109
-
110
- def __init__(
111
- self,
112
- *args,
113
- context=None,
114
- node=None,
115
- **kwds,
116
- ):
117
- # begin wxGlade: LayerSettingPanel.__init__
118
- kwds["style"] = kwds.get("style", 0)
119
- wx.Panel.__init__(self, *args, **kwds)
120
- self.context = context
121
- self.node = node
122
- self.fonts = []
123
-
124
- main_sizer = StaticBoxSizer(self, wx.ID_ANY, _("Vector-Text"), wx.VERTICAL)
125
-
126
- sizer_text = StaticBoxSizer(self, wx.ID_ANY, _("Content"), wx.HORIZONTAL)
127
- self.text_text = wx.TextCtrl(self, wx.ID_ANY, "")
128
- sizer_text.Add(self.text_text, 1, wx.EXPAND, 0)
129
-
130
- self.btn_bigger = wx.Button(self, wx.ID_ANY, "++")
131
- self.btn_bigger.SetToolTip(_("Increase the font-size"))
132
- sizer_text.Add(self.btn_bigger, 0, wx.EXPAND, 0)
133
-
134
- self.btn_smaller = wx.Button(self, wx.ID_ANY, "--")
135
- self.btn_smaller.SetToolTip(_("Decrease the font-size"))
136
- sizer_text.Add(self.btn_smaller, 0, wx.EXPAND, 0)
137
-
138
- sizer_fonts = StaticBoxSizer(
139
- self, wx.ID_ANY, _("Fonts (double-click to use)"), wx.VERTICAL
140
- )
141
-
142
- self.list_fonts = wx.ListBox(self, wx.ID_ANY)
143
- self.list_fonts.SetMinSize(dip_size(self, -1, 140))
144
- self.list_fonts.SetToolTip(
145
- _("Select to preview the font, double-click to apply it")
146
- )
147
- sizer_fonts.Add(self.list_fonts, 0, wx.EXPAND, 0)
148
-
149
- self.bmp_preview = wx.StaticBitmap(self, wx.ID_ANY)
150
- self.bmp_preview.SetMinSize(dip_size(self, -1, 70))
151
- sizer_fonts.Add(self.bmp_preview, 0, wx.EXPAND, 0)
152
-
153
- main_sizer.Add(sizer_text, 0, wx.EXPAND, 0)
154
- main_sizer.Add(sizer_fonts, 0, wx.EXPAND, 0)
155
- self.SetSizer(main_sizer)
156
- self.Layout()
157
- self.btn_bigger.Bind(wx.EVT_BUTTON, self.on_button_bigger)
158
- self.btn_smaller.Bind(wx.EVT_BUTTON, self.on_button_smaller)
159
- self.text_text.Bind(wx.EVT_TEXT, self.on_text_change)
160
- self.list_fonts.Bind(wx.EVT_LISTBOX, self.on_list_font)
161
- self.list_fonts.Bind(wx.EVT_LISTBOX_DCLICK, self.on_list_font_dclick)
162
- self.set_widgets(self.node)
163
-
164
- def pane_hide(self):
165
- pass
166
-
167
- def pane_show(self):
168
- pass
169
-
170
- def accepts(self, node):
171
- if (
172
- hasattr(node, "mkfont")
173
- and hasattr(node, "mkfontsize")
174
- and hasattr(node, "mktext")
175
- ):
176
- # Let's take the opportunity to check for incorrect types and fix them...
177
- validate_node(node)
178
- return True
179
- else:
180
- return False
181
-
182
- def set_widgets(self, node):
183
- self.node = node
184
- # print(f"set_widget for {self.attribute} to {str(node)}")
185
- if self.node is None or not self.accepts(node):
186
- self.Hide()
187
- return
188
- fontdir = fontdirectory(self.context)
189
- self.load_directory(fontdir)
190
- self.text_text.SetValue(str(node.mktext))
191
- self.Show()
192
-
193
- def load_directory(self, fontdir):
194
- self.fonts = []
195
- self.list_fonts.Clear()
196
- if os.path.exists(fontdir):
197
- self.context.font_directory = fontdir
198
- fontinfo = fonts_registered()
199
- for extension in fontinfo:
200
- ext = "*." + extension
201
- for p in glob(os.path.join(fontdir, ext.lower())):
202
- fn = os.path.basename(p)
203
- if fn not in self.fonts:
204
- self.fonts.append(fn)
205
- for p in glob(os.path.join(fontdir, ext.upper())):
206
- fn = os.path.basename(p)
207
- if fn not in self.fonts:
208
- self.fonts.append(fn)
209
- self.list_fonts.SetItems(self.fonts)
210
- # index = -1
211
- # lookfor = getattr(self.context, "sxh_preferred", "")
212
-
213
- def update_node(self):
214
- vtext = self.text_text.GetValue()
215
- update_linetext(self.context, self.node, vtext)
216
- self.context.signal("element_property_reload", self.node)
217
- self.context.signal("refresh_scene", "Scene")
218
-
219
- def on_button_bigger(self, event):
220
- if self.node is None:
221
- return
222
- self.node.mkfontsize *= 1.2
223
- self.update_node()
224
-
225
- def on_button_smaller(self, event):
226
- if self.node is None:
227
- return
228
- self.node.mkfontsize /= 1.2
229
- self.update_node()
230
-
231
- def on_text_change(self, event):
232
- self.update_node()
233
-
234
- def on_list_font_dclick(self, event):
235
- if self.node is None:
236
- return
237
- index = self.list_fonts.GetSelection()
238
- if index >= 0:
239
- fontname = self.fonts[index]
240
- self.node.mkfont = fontname
241
- self.update_node()
242
-
243
- def on_list_font(self, event):
244
- if self.list_fonts.GetSelection() >= 0:
245
- fontdir = fontdirectory(self.context)
246
- font_file = self.fonts[self.list_fonts.GetSelection()]
247
- full_font_file = os.path.join(fontdir, font_file)
248
- bmp = load_create_preview_file(self.context, full_font_file)
249
- # if bmp is not None:
250
- # bmap_bundle = wx.BitmapBundle().FromBitmap(bmp)
251
- # else:
252
- # bmap_bundle = wx.BitmapBundle()
253
- # self.bmp_preview.SetBitmap(bmap_bundle)
254
- if bmp is None:
255
- bmp = wx.NullBitmap
256
- self.bmp_preview.SetBitmap(bmp)
257
-
258
-
259
- class PanelFontSelect(wx.Panel):
260
- """
261
- Panel to select font during line text creation
262
- """
263
-
264
- def __init__(self, *args, context=None, **kwds):
265
- # begin wxGlade: clsLasertools.__init__
266
- kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
267
- wx.Panel.__init__(self, *args, **kwds)
268
- self.context = context
269
-
270
- mainsizer = wx.BoxSizer(wx.VERTICAL)
271
-
272
- self.all_fonts = []
273
- self.fonts = []
274
- self.font_checks = {}
275
-
276
- fontinfo = fonts_registered()
277
- sizer_checker = wx.BoxSizer(wx.HORIZONTAL)
278
- for extension in fontinfo:
279
- info = fontinfo[extension]
280
- checker = wx.CheckBox(self, wx.ID_ANY, info[0])
281
- checker.SetValue(True)
282
- checker.Bind(wx.EVT_CHECKBOX, self.on_checker(extension))
283
- checker.SetToolTip(
284
- _("Show/Hide all fonts of type {info[0]}").format(info=info)
285
- )
286
- self.font_checks[extension] = [checker, True]
287
- sizer_checker.Add(checker, 0, 0, wx.ALIGN_CENTER_VERTICAL)
288
-
289
- sizer_fonts = StaticBoxSizer(
290
- self, wx.ID_ANY, _("Fonts (double-click to use)"), wx.VERTICAL
291
- )
292
- mainsizer.Add(sizer_fonts, 1, wx.EXPAND, 0)
293
-
294
- self.list_fonts = wx.ListBox(self, wx.ID_ANY)
295
- self.list_fonts.SetToolTip(
296
- _("Select to preview the font, double-click to apply it")
297
- )
298
- sizer_fonts.Add(self.list_fonts, 1, wx.EXPAND, 0)
299
- sizer_fonts.Add(sizer_checker, 0, wx.EXPAND, 0)
300
-
301
- self.bmp_preview = wx.StaticBitmap(self, wx.ID_ANY)
302
- self.bmp_preview.SetMinSize(dip_size(self, -1, 70))
303
- sizer_fonts.Add(self.bmp_preview, 0, wx.EXPAND, 0)
304
-
305
- sizer_buttons = wx.BoxSizer(wx.HORIZONTAL)
306
- sizer_fonts.Add(sizer_buttons, 0, wx.EXPAND, 0)
307
-
308
- self.btn_bigger = wx.Button(self, wx.ID_ANY, "++")
309
- self.btn_bigger.SetToolTip(_("Increase the font-size"))
310
- sizer_buttons.Add(self.btn_bigger, 0, wx.EXPAND, 0)
311
-
312
- self.btn_smaller = wx.Button(self, wx.ID_ANY, "--")
313
- self.btn_smaller.SetToolTip(_("Decrease the font-size"))
314
- sizer_buttons.Add(self.btn_smaller, 0, wx.EXPAND, 0)
315
-
316
- lbl_spacer = wx.StaticText(self, wx.ID_ANY, "")
317
- sizer_buttons.Add(lbl_spacer, 1, 0, 0)
318
-
319
- self.SetSizer(mainsizer)
320
-
321
- self.Layout()
322
-
323
- self.Bind(wx.EVT_LISTBOX, self.on_list_font, self.list_fonts)
324
- self.Bind(wx.EVT_LISTBOX_DCLICK, self.on_list_font_dclick, self.list_fonts)
325
- self.Bind(wx.EVT_BUTTON, self.on_btn_bigger, self.btn_bigger)
326
- self.Bind(wx.EVT_BUTTON, self.on_btn_smaller, self.btn_smaller)
327
-
328
- # end wxGlade
329
- fontdir = fontdirectory(self.context)
330
- self.load_directory(fontdir)
331
-
332
- def load_directory(self, fontdir):
333
- self.all_fonts = []
334
- self.list_fonts.Clear()
335
- if os.path.exists(fontdir):
336
- self.context.font_directory = fontdir
337
- fontinfo = fonts_registered()
338
- for extension in fontinfo:
339
- ext = "*." + extension
340
- for p in glob(os.path.join(fontdir, ext.lower())):
341
- fn = os.path.basename(p)
342
- if fn not in self.all_fonts:
343
- self.all_fonts.append(fn)
344
- for p in glob(os.path.join(fontdir, ext.upper())):
345
- fn = os.path.basename(p)
346
- if fn not in self.all_fonts:
347
- self.all_fonts.append(fn)
348
- self.populate_list_box()
349
- # index = -1
350
- # lookfor = getattr(self.context, "sxh_preferred", "")
351
-
352
- def populate_list_box(self):
353
- self.fonts = []
354
- for entry in self.all_fonts:
355
- parts = os.path.splitext(entry)
356
- if len(parts) > 1:
357
- extension = parts[1][1:].lower()
358
- if extension in self.font_checks:
359
- if not self.font_checks[extension][1]:
360
- entry = None
361
- if entry is not None:
362
- self.fonts.append(entry)
363
- self.list_fonts.SetItems(self.fonts)
364
-
365
- def on_checker(self, extension):
366
- def handler(event):
367
- self.font_checks[extension][1] = not self.font_checks[extension][1]
368
- # Reload List
369
- self.populate_list_box()
370
-
371
- return handler
372
-
373
- def on_btn_bigger(self, event):
374
- self.context.signal("linetext", "bigger")
375
-
376
- def on_btn_smaller(self, event):
377
- self.context.signal("linetext", "smaller")
378
-
379
- def on_list_font_dclick(self, event):
380
- index = self.list_fonts.GetSelection()
381
- if index >= 0:
382
- fontname = self.fonts[index]
383
- self.context.signal("linetext", "font", fontname)
384
-
385
- def on_list_font(self, event):
386
- if self.list_fonts.GetSelection() >= 0:
387
- font_file = self.fonts[self.list_fonts.GetSelection()]
388
- full_font_file = os.path.join(self.context.font_directory, font_file)
389
- bmp = load_create_preview_file(self.context, full_font_file)
390
- # if bmp is not None:
391
- # bmap_bundle = wx.BitmapBundle().FromBitmap(bmp)
392
- # else:
393
- # bmap_bundle = wx.BitmapBundle()
394
- # self.bmp_preview.SetBitmap(bmap_bundle)
395
- if bmp is None:
396
- bmp = wx.NullBitmap
397
- self.bmp_preview.SetBitmap(bmp)
398
-
399
-
400
- class HersheyFontSelector(MWindow):
401
- """
402
- Wrapper Window Class for font selection panel
403
- """
404
-
405
- def __init__(self, *args, **kwds):
406
- super().__init__(450, 550, submenu="", *args, **kwds)
407
- self.panel = PanelFontSelect(self, wx.ID_ANY, context=self.context)
408
- _icon = wx.NullIcon
409
- _icon.CopyFromBitmap(
410
- icons8_choose_font.GetBitmap(resize=0.5 * get_default_icon_size())
411
- )
412
- # _icon.CopyFromBitmap(icons8_computer_support.GetBitmap())
413
- self.SetIcon(_icon)
414
- self.SetTitle(_("Font-Selection"))
415
-
416
- def window_open(self):
417
- pass
418
-
419
- def window_close(self):
420
- pass
421
-
422
- def delegates(self):
423
- yield self.panel
424
-
425
- @staticmethod
426
- def submenu():
427
- # Suppress = True
428
- return "", "Font-Selector", True
429
-
430
-
431
- class PanelFontManager(wx.Panel):
432
- """
433
- Vector Font Manager
434
- """
435
-
436
- def __init__(self, *args, context=None, **kwds):
437
- # begin wxGlade: clsLasertools.__init__
438
- kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
439
- wx.Panel.__init__(self, *args, **kwds)
440
- self.context = context
441
-
442
- mainsizer = wx.BoxSizer(wx.VERTICAL)
443
-
444
- self.fonts = []
445
-
446
- self.text_info = wx.TextCtrl(
447
- self,
448
- wx.ID_ANY,
449
- _(
450
- "MeerK40t can use True-Type-Fonts, Hershey-Fonts or Autocad-86 shape fonts designed to be rendered purely with vectors.\n"
451
- + "They can be scaled, burned like any other vector shape and are therefore very versatile.\n"
452
- + "See more: https://en.wikipedia.org/wiki/Hershey_fonts "
453
- ),
454
- style=wx.BORDER_NONE | wx.TE_MULTILINE | wx.TE_READONLY,
455
- )
456
-
457
- self.text_info.SetMinSize(dip_size(self, -1, 90))
458
- self.text_info.SetBackgroundColour(self.GetBackgroundColour())
459
- sizer_info = StaticBoxSizer(self, wx.ID_ANY, _("Information"), wx.HORIZONTAL)
460
- mainsizer.Add(sizer_info, 0, wx.EXPAND, 0)
461
- sizer_info.Add(self.text_info, 1, wx.EXPAND, 0)
462
-
463
- sizer_directory = StaticBoxSizer(
464
- self, wx.ID_ANY, _("Font-Directory"), wx.HORIZONTAL
465
- )
466
- mainsizer.Add(sizer_directory, 0, wx.EXPAND, 0)
467
-
468
- self.text_fontdir = wx.TextCtrl(self, wx.ID_ANY, "")
469
- sizer_directory.Add(self.text_fontdir, 1, wx.EXPAND, 0)
470
-
471
- self.btn_dirselect = wx.Button(self, wx.ID_ANY, "...")
472
- sizer_directory.Add(self.btn_dirselect, 0, wx.EXPAND, 0)
473
-
474
- sizer_fonts = StaticBoxSizer(self, wx.ID_ANY, _("Fonts"), wx.VERTICAL)
475
- mainsizer.Add(sizer_fonts, 1, wx.EXPAND, 0)
476
-
477
- self.list_fonts = wx.ListBox(self, wx.ID_ANY)
478
- sizer_fonts.Add(self.list_fonts, 1, wx.EXPAND, 0)
479
-
480
- self.bmp_preview = wx.StaticBitmap(self, wx.ID_ANY)
481
- self.bmp_preview.SetMinSize(dip_size(self, -1, 70))
482
- sizer_fonts.Add(self.bmp_preview, 0, wx.EXPAND, 0)
483
-
484
- sizer_buttons = wx.BoxSizer(wx.HORIZONTAL)
485
- sizer_fonts.Add(sizer_buttons, 0, wx.EXPAND, 0)
486
-
487
- self.btn_add = wx.Button(self, wx.ID_ANY, _("Import"))
488
- sizer_buttons.Add(self.btn_add, 0, wx.EXPAND, 0)
489
-
490
- self.btn_delete = wx.Button(self, wx.ID_ANY, _("Delete"))
491
- sizer_buttons.Add(self.btn_delete, 0, wx.EXPAND, 0)
492
-
493
- lbl_spacer = wx.StaticText(self, wx.ID_ANY, "")
494
- sizer_buttons.Add(lbl_spacer, 1, 0, 0)
495
-
496
- self.webresources = [
497
- "https://github.com/kamalmostafa/hershey-fonts/tree/master/hershey-fonts",
498
- "http://iki.fi/sol/hershey/index.html",
499
- "https://www.mepwork.com/2017/11/autocad-shx-fonts.html",
500
- ]
501
- choices = [
502
- _("Goto a font-source..."),
503
- _("Hershey Fonts - #1"),
504
- _("Hershey Fonts - #2"),
505
- _("Autocad-SHX-Fonts"),
506
- ]
507
- system = platform.system()
508
- if system == "Windows":
509
- choices.append(_("Windows-Font-Directory"))
510
- self.webresources.append(os.path.join(os.environ["WINDIR"], "fonts"))
511
- self.combo_webget = wx.ComboBox(
512
- self,
513
- wx.ID_ANY,
514
- choices=choices,
515
- style=wx.CB_DROPDOWN | wx.CB_READONLY,
516
- )
517
- self.combo_webget.SetSelection(0)
518
- sizer_buttons.Add(self.combo_webget, 0, wx.EXPAND, 0)
519
-
520
- self.SetSizer(mainsizer)
521
-
522
- self.Layout()
523
-
524
- self.Bind(wx.EVT_TEXT, self.on_text_directory, self.text_fontdir)
525
- self.Bind(wx.EVT_BUTTON, self.on_btn_directory, self.btn_dirselect)
526
- self.Bind(wx.EVT_LISTBOX, self.on_list_font, self.list_fonts)
527
- self.Bind(wx.EVT_LISTBOX_DCLICK, self.on_list_font_dclick, self.list_fonts)
528
- self.Bind(wx.EVT_BUTTON, self.on_btn_import, self.btn_add)
529
- self.Bind(wx.EVT_BUTTON, self.on_btn_delete, self.btn_delete)
530
- self.Bind(wx.EVT_COMBOBOX, self.on_combo_webget, self.combo_webget)
531
- # end wxGlade
532
- fontdir = fontdirectory(self.context)
533
- self.text_fontdir.SetValue(fontdir)
534
-
535
- def on_text_directory(self, event):
536
- fontdir = self.text_fontdir.GetValue()
537
- self.fonts = []
538
- self.list_fonts.Clear()
539
- if os.path.exists(fontdir):
540
- self.context.font_directory = fontdir
541
- fontinfo = fonts_registered()
542
- for extension in fontinfo:
543
- ext = "*." + extension
544
- for p in glob(os.path.join(fontdir, ext.lower())):
545
- fn = os.path.basename(p)
546
- if fn not in self.fonts:
547
- self.fonts.append(fn)
548
- for p in glob(os.path.join(fontdir, ext.upper())):
549
- fn = os.path.basename(p)
550
- if fn not in self.fonts:
551
- self.fonts.append(fn)
552
- self.list_fonts.SetItems(self.fonts)
553
- # Let the world know we have fonts
554
- self.context.signal("icons")
555
-
556
- def on_btn_directory(self, event):
557
- fontdir = self.text_fontdir.GetValue()
558
- dlg = wx.DirDialog(
559
- None,
560
- _("Choose font directory"),
561
- fontdir,
562
- style=wx.DD_DEFAULT_STYLE
563
- # | wx.DD_DIR_MUST_EXIST
564
- )
565
- if dlg.ShowModal() == wx.ID_OK:
566
- self.text_fontdir.SetValue(dlg.GetPath())
567
- # Only destroy a dialog after you're done with it.
568
- dlg.Destroy()
569
-
570
- def on_list_font_dclick(self, event):
571
- if self.list_fonts.GetSelection() >= 0:
572
- font_file = self.fonts[self.list_fonts.GetSelection()]
573
- self.context.setting(str, "shx_preferred", None)
574
- self.context.shx_preferred = font_file
575
-
576
- # full_font_file = os.path.join(self.context.font_directory, font_file)
577
- # print(f"Fontfile: {font_file}, full: {full_font_file}")
578
-
579
- def on_list_font(self, event):
580
- if self.list_fonts.GetSelection() >= 0:
581
- font_file = self.fonts[self.list_fonts.GetSelection()]
582
- full_font_file = os.path.join(self.context.font_directory, font_file)
583
- bmp = load_create_preview_file(self.context, full_font_file)
584
- # if bmp is not None:
585
- # bmap_bundle = wx.BitmapBundle().FromBitmap(bmp)
586
- # else:
587
- # bmap_bundle = wx.BitmapBundle()
588
- # self.bmp_preview.SetBitmap(bmap_bundle)
589
- if bmp is None:
590
- bmp = wx.NullBitmap
591
- self.bmp_preview.SetBitmap(bmp)
592
-
593
- def on_btn_import(self, event, defaultdirectory=None, defaultextension=None):
594
- fontinfo = fonts_registered()
595
- wildcard = "Vector-Fonts"
596
- idx = 0
597
- filterindex = 0
598
- # 1st put all into one wildcard-pattern
599
- for extension in fontinfo:
600
- ext = "*." + extension
601
- if idx == 0:
602
- wildcard += "|"
603
- else:
604
- wildcard += ";"
605
- wildcard += ext.lower() + ";" + ext.upper()
606
- idx += 1
607
- # 2nd add all individual wildcard-patterns
608
- for idx, extension in enumerate(fontinfo):
609
- if (
610
- defaultextension is not None
611
- and defaultextension.lower() == extension.lower()
612
- ):
613
- filterindex = idx + 1
614
- ext = "*." + extension
615
- info = fontinfo[extension]
616
- wildcard += f"|{info[0]}-Fonts|{ext.lower()};{ext.upper()}"
617
- wildcard += "|All files|*.*"
618
- if defaultdirectory is None:
619
- defdir = ""
620
- else:
621
- defdir = defaultdirectory
622
- # print (os.listdir(os.path.join(os.environ['WINDIR'],'fonts')))
623
- dlg = wx.FileDialog(
624
- self,
625
- message=_(
626
- "Select a font-file to be imported into the the font-directory {fontdir}"
627
- ).format(fontdir=self.context.font_directory),
628
- defaultDir=defdir,
629
- defaultFile="",
630
- wildcard=wildcard,
631
- style=wx.FD_OPEN
632
- | wx.FD_FILE_MUST_EXIST
633
- | wx.FD_MULTIPLE
634
- | wx.FD_PREVIEW
635
- | wx.FD_SHOW_HIDDEN,
636
- )
637
- try:
638
- # Might not be present in early wxpython versions
639
- dlg.SetFilterIndex(filterindex)
640
- except AttributeError:
641
- pass
642
- font_files = None
643
- paths = None
644
- if dlg.ShowModal() == wx.ID_OK:
645
- font_files = dlg.GetPaths()
646
- # Only destroy a dialog after you're done with it.
647
- dlg.Destroy()
648
- stats = [0, 0] # Successful, errors
649
- if font_files is None:
650
- return
651
-
652
- maxidx = len(font_files)
653
- progress_string = _("Fonts imported: {count}")
654
- progress = wx.ProgressDialog(
655
- _("Importing fonts..."),
656
- progress_string.format(count=0),
657
- maximum=maxidx,
658
- parent=None,
659
- style=wx.PD_APP_MODAL | wx.PD_CAN_ABORT,
660
- )
661
- for idx, sourcefile in enumerate(font_files):
662
- basename = os.path.basename(sourcefile)
663
- destfile = os.path.join(self.context.font_directory, basename)
664
- # print (f"Source File: {sourcefile}\nTarget: {destfile}")
665
- try:
666
- with open(sourcefile, "rb") as f, open(destfile, "wb") as g:
667
- while True:
668
- block = f.read(1 * 1024 * 1024) # work by blocks of 1 MB
669
- if not block: # end of file
670
- break
671
- g.write(block)
672
- isokay = create_preview_image(self.context, destfile)
673
- if isokay:
674
- stats[0] += 1
675
- else:
676
- # We delete this file again...
677
- remove_fontfile(destfile)
678
- stats[1] += 1
679
-
680
- keepgoing = progress.Update(
681
- idx + 1, progress_string.format(count=idx + 1)
682
- )
683
- if progress.WasCancelled():
684
- break
685
- except (OSError, RuntimeError, PermissionError, FileNotFoundError):
686
- stats[1] += 1
687
- progress.Destroy()
688
- wx.MessageBox(
689
- _(
690
- "Font-Import completed.\nImported: {ok}\nFailed: {fail}\nTotal: {total}"
691
- ).format(ok=stats[0], fail=stats[1], total=stats[0] + stats[1]),
692
- _("Import completed"),
693
- wx.OK | wx.ICON_INFORMATION,
694
- )
695
- # Reload....
696
- self.on_text_directory(None)
697
-
698
- def on_btn_delete(self, event):
699
- if self.list_fonts.GetSelection() >= 0:
700
- font_file = self.fonts[self.list_fonts.GetSelection()]
701
- full_font_file = os.path.join(self.context.font_directory, font_file)
702
- if (
703
- wx.MessageBox(
704
- _("Do you really want to delete this font: {font}").format(
705
- font=font_file
706
- ),
707
- _("Confirm"),
708
- wx.YES_NO | wx.CANCEL | wx.ICON_WARNING,
709
- )
710
- == wx.YES
711
- ):
712
- remove_fontfile(full_font_file)
713
- # Reload dir...
714
- self.on_text_directory(None)
715
-
716
- def on_combo_webget(self, event):
717
- idx = self.combo_webget.GetSelection() - 1
718
- if idx >= 0:
719
- url = self.webresources[idx]
720
- if url.startswith("http"):
721
- if (
722
- wx.MessageBox(
723
- _(
724
- "You will be led now to a source in the web, where you can download free fonts.\n"
725
- + "Please respect individual property rights!\nDestination: {url}\n"
726
- ).format(url=url)
727
- + _(
728
- "Unpack the downloaded archive after the download and select the extracted files with help of the 'Import'-Button."
729
- ),
730
- _("Confirm"),
731
- wx.YES_NO | wx.CANCEL | wx.ICON_INFORMATION,
732
- )
733
- == wx.YES
734
- ):
735
- import webbrowser
736
-
737
- webbrowser.open(url, new=0, autoraise=True)
738
- else:
739
- # This is a local directory with existing font-files,
740
- # e.g. the Windows-Font-Directory
741
- self.import_files(url, "ttf")
742
-
743
- def import_files(self, import_directory, extension):
744
- source_files = os.listdir(import_directory)
745
- font_files = []
746
- for entry in source_files:
747
- if entry.lower().endswith(extension):
748
- font_files.append(os.path.join(import_directory, entry))
749
- stats = [0, 0] # Successful, errors
750
- if len(font_files) == 0:
751
- return
752
-
753
- maxidx = len(font_files)
754
- progress_string = _("Fonts imported: {count}")
755
- progress = wx.ProgressDialog(
756
- _("Importing fonts..."),
757
- progress_string.format(count=0),
758
- maximum=maxidx,
759
- parent=None,
760
- style=wx.PD_APP_MODAL | wx.PD_CAN_ABORT,
761
- )
762
- for idx, sourcefile in enumerate(font_files):
763
- basename = os.path.basename(sourcefile)
764
- destfile = os.path.join(self.context.font_directory, basename)
765
- if os.path.exists(destfile):
766
- continue
767
- # print (f"Source File: {sourcefile}\nTarget: {destfile}")
768
- try:
769
- with open(sourcefile, "rb") as f, open(destfile, "wb") as g:
770
- while True:
771
- block = f.read(1 * 1024 * 1024) # work by blocks of 1 MB
772
- if not block: # end of file
773
- break
774
- g.write(block)
775
- isokay = create_preview_image(self.context, destfile)
776
- if isokay:
777
- stats[0] += 1
778
- else:
779
- # We delete this file again...
780
- remove_fontfile(destfile)
781
- stats[1] += 1
782
-
783
- keepgoing = progress.Update(
784
- idx + 1, progress_string.format(count=idx + 1)
785
- )
786
- if progress.WasCancelled():
787
- break
788
- except (OSError, RuntimeError, PermissionError, FileNotFoundError):
789
- stats[1] += 1
790
- progress.Destroy()
791
- wx.MessageBox(
792
- _(
793
- "Font-Import completed.\nImported: {ok}\nFailed: {fail}\nTotal: {total}"
794
- ).format(ok=stats[0], fail=stats[1], total=stats[0] + stats[1]),
795
- _("Import completed"),
796
- wx.OK | wx.ICON_INFORMATION,
797
- )
798
- # Reload....
799
- self.on_text_directory(None)
800
-
801
-
802
- # end of class FontManager
803
-
804
-
805
- class HersheyFontManager(MWindow):
806
- """
807
- Wrapper Window Class for Vector Font Manager
808
- """
809
-
810
- def __init__(self, *args, **kwds):
811
- super().__init__(551, 234, submenu="", *args, **kwds)
812
- self.panel = PanelFontManager(self, wx.ID_ANY, context=self.context)
813
- _icon = wx.NullIcon
814
- _icon.CopyFromBitmap(icons8_choose_font.GetBitmap())
815
- # _icon.CopyFromBitmap(icons8_computer_support.GetBitmap())
816
- self.SetIcon(_icon)
817
- self.SetTitle(_("Font-Manager"))
818
-
819
- def window_open(self):
820
- pass
821
-
822
- def window_close(self):
823
- pass
824
-
825
- def delegates(self):
826
- yield self.panel
827
-
828
- @staticmethod
829
- def submenu():
830
- # suppress in tool-menu
831
- return "", "Font-Manager", True
832
-
833
-
834
- def register_hershey_stuff(kernel):
835
- kernel.root.register("path_attributes/linetext", LineTextPropertyPanel)
836
- buttonsize = int(STD_ICON_SIZE)
837
- kernel.register(
838
- "button/config/HersheyFontManager",
839
- {
840
- "label": _("Font-Manager"),
841
- "icon": icons8_choose_font,
842
- "tip": _("Open the vector-font management window."),
843
- "action": lambda v: kernel.console("window toggle HersheyFontManager\n"),
844
- "size": buttonsize,
845
- },
846
- )
1
+ import os
2
+ import platform
3
+ import wx
4
+
5
+ from meerk40t.core.units import Length
6
+ from meerk40t.gui.choicepropertypanel import ChoicePropertyPanel
7
+ from meerk40t.gui.icons import (
8
+ STD_ICON_SIZE,
9
+ get_default_icon_size,
10
+ icon_kerning_bigger,
11
+ icon_kerning_smaller,
12
+ icon_linegap_bigger,
13
+ icon_linegap_smaller,
14
+ icon_textalign_center,
15
+ icon_textalign_left,
16
+ icon_textalign_right,
17
+ icon_textsize_down,
18
+ icon_textsize_up,
19
+ icons8_choose_font,
20
+ )
21
+ from meerk40t.gui.mwindow import MWindow
22
+ from meerk40t.gui.wxutils import (
23
+ StaticBoxSizer,
24
+ dip_size,
25
+ wxButton,
26
+ wxCheckBox,
27
+ wxComboBox,
28
+ wxListBox,
29
+ wxListCtrl,
30
+ wxStaticBitmap,
31
+ wxStaticText,
32
+ wxToggleButton,
33
+ TextCtrl,
34
+ )
35
+ from meerk40t.kernel.kernel import signal_listener
36
+ from meerk40t.tools.geomstr import TYPE_ARC, TYPE_CUBIC, TYPE_LINE, TYPE_QUAD, Geomstr
37
+
38
+ _ = wx.GetTranslation
39
+
40
+
41
+ def remove_fontfile(fontfile):
42
+ if os.path.exists(fontfile):
43
+ try:
44
+ os.remove(fontfile)
45
+ base, ext = os.path.splitext(fontfile)
46
+ bmpfile = base + ".png"
47
+ if os.path.exists(bmpfile):
48
+ os.remove(bmpfile)
49
+ except (OSError, RuntimeError, PermissionError, FileNotFoundError):
50
+ pass
51
+
52
+
53
+ class FontGlyphPicker(wx.Dialog):
54
+ """
55
+ Dialog to pick a glyph from the existing set of characters in a font
56
+ """
57
+
58
+ def __init__(self, *args, context=None, font=None, **kwds):
59
+ # begin wxGlade: clsLasertools.__init__
60
+ kwds["style"] = (
61
+ kwds.get("style", 0) | wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
62
+ )
63
+ wx.Dialog.__init__(self, *args, **kwds)
64
+ self.context = context
65
+ self.context.themes.set_window_colors(self)
66
+ self.font = font
67
+ self.icon_size = 32
68
+ mainsizer = wx.BoxSizer(wx.VERTICAL)
69
+ self.list_glyphs = wxListCtrl(
70
+ self,
71
+ wx.ID_ANY,
72
+ style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES | wx.LC_SINGLE_SEL,
73
+ context=self.context, list_name="list_glyphpicker"
74
+ )
75
+ self.list_glyphs.AppendColumn("UC", format=wx.LIST_FORMAT_LEFT, width=75)
76
+ self.list_glyphs.AppendColumn("ASCII", format=wx.LIST_FORMAT_LEFT, width=75)
77
+ self.list_glyphs.AppendColumn("Char", format=wx.LIST_FORMAT_LEFT, width=75)
78
+ self.list_glyphs.AppendColumn("Debug", format=wx.LIST_FORMAT_LEFT, width=125)
79
+ self.images = wx.ImageList()
80
+ self.images.Create(width=self.icon_size, height=self.icon_size)
81
+ self.list_glyphs.AssignImageList(self.images, wx.IMAGE_LIST_SMALL)
82
+ self.txt_result = TextCtrl(self, wx.ID_ANY)
83
+ mainsizer.Add(self.list_glyphs, 1, wx.EXPAND, 0)
84
+ mainsizer.Add(self.txt_result, 0, wx.EXPAND, 0)
85
+
86
+ self.btn_ok = wxButton(self, wx.ID_OK, _("OK"))
87
+ self.btn_cancel = wxButton(self, wx.ID_CANCEL, _("Cancel"))
88
+ box_sizer = wx.BoxSizer(wx.HORIZONTAL)
89
+ box_sizer.Add(self.btn_ok, 0, 0, 0)
90
+ box_sizer.Add(self.btn_cancel, 0, 0, 0)
91
+ mainsizer.Add(box_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL, 0)
92
+
93
+ self.list_glyphs.Bind(wx.EVT_LEFT_DCLICK, self.on_dbl_click)
94
+ self.SetSizer(mainsizer)
95
+ self.list_glyphs.load_column_widths()
96
+ self.Layout()
97
+ # end wxGlade
98
+ self.load_font()
99
+
100
+ def load_font(self):
101
+ def geomstr_to_gcpath(gc, path):
102
+ """
103
+ Takes a Geomstr path and converts it to a GraphicsContext.Graphics path
104
+
105
+ This also creates a point list of the relevant nodes and creates a ._cache_edit value to be used by node
106
+ editing view.
107
+ """
108
+ p = gc.CreatePath()
109
+ pts = list()
110
+ for subpath in path.as_subpaths():
111
+ if len(subpath) == 0:
112
+ continue
113
+ end = None
114
+ for e in subpath.segments:
115
+ seg_type = int(e[2].real)
116
+ start = e[0]
117
+ if end != start:
118
+ # Start point does not equal previous end point.
119
+ p.MoveToPoint(start.real, start.imag)
120
+ c0 = e[1]
121
+ c1 = e[3]
122
+ end = e[4]
123
+
124
+ if seg_type == TYPE_LINE:
125
+ p.AddLineToPoint(end.real, end.imag)
126
+ pts.append(start)
127
+ pts.append(end)
128
+ elif seg_type == TYPE_QUAD:
129
+ p.AddQuadCurveToPoint(c0.real, c0.imag, end.real, end.imag)
130
+ pts.append(c0)
131
+ pts.append(start)
132
+ pts.append(end)
133
+ elif seg_type == TYPE_ARC:
134
+ radius = Geomstr.arc_radius(None, line=e)
135
+ center = Geomstr.arc_center(None, line=e)
136
+ start_t = Geomstr.angle(None, center, start)
137
+ end_t = Geomstr.angle(None, center, end)
138
+ p.AddArc(
139
+ center.real,
140
+ center.imag,
141
+ radius,
142
+ start_t,
143
+ end_t,
144
+ clockwise="ccw"
145
+ != Geomstr.orientation(None, start, c0, end),
146
+ )
147
+ pts.append(c0)
148
+ pts.append(start)
149
+ pts.append(end)
150
+ elif seg_type == TYPE_CUBIC:
151
+ p.AddCurveToPoint(
152
+ c0.real, c0.imag, c1.real, c1.imag, end.real, end.imag
153
+ )
154
+ pts.append(c0)
155
+ pts.append(c1)
156
+ pts.append(start)
157
+ pts.append(end)
158
+ else:
159
+ print(f"Unknown seg_type: {seg_type}")
160
+ if subpath.first_point == end:
161
+ p.CloseSubpath()
162
+ return p
163
+
164
+ def prepare_bitmap(geom, final_icon_width, final_icon_height, as_stroke=False):
165
+ edge = 1
166
+ strokewidth = 1
167
+ wincol = self.context.themes.get("win_bg")
168
+ strcol = self.context.themes.get("win_fg")
169
+
170
+ spen = wx.Pen()
171
+ sbrush = wx.Brush()
172
+ spen.SetColour(strcol)
173
+ sbrush.SetColour(strcol)
174
+ spen.SetWidth(strokewidth)
175
+ spen.SetCap(wx.CAP_ROUND)
176
+ spen.SetJoin(wx.JOIN_ROUND)
177
+
178
+ bmp = wx.Bitmap.FromRGBA(
179
+ final_icon_width,
180
+ final_icon_height,
181
+ wincol.red,
182
+ wincol.blue,
183
+ wincol.green,
184
+ 0,
185
+ )
186
+ dc = wx.MemoryDC()
187
+ dc.SelectObject(bmp)
188
+ # dc.SetBackground(self._background)
189
+ # dc.SetBackground(wx.RED_BRUSH)
190
+ # dc.Clear()
191
+ gc = wx.GraphicsContext.Create(dc)
192
+ gc.dc = dc
193
+
194
+ gp = geomstr_to_gcpath(gc, geom)
195
+ m_x, m_y, p_w, p_h = gp.Box
196
+ min_x = m_x
197
+ min_y = m_y
198
+ max_x = m_x + p_w
199
+ max_y = m_y + p_h
200
+
201
+ path_width = max_x - min_x
202
+ path_height = max_y - min_y
203
+
204
+ path_width += 2 * edge
205
+ path_height += 2 * edge
206
+
207
+ stroke_buffer = strokewidth
208
+ path_width += 2 * stroke_buffer
209
+ path_height += 2 * stroke_buffer
210
+
211
+ scale_x = final_icon_width / path_width
212
+ scale_y = final_icon_height / path_height
213
+
214
+ scale = min(scale_x, scale_y)
215
+ width_scaled = int(round(path_width * scale))
216
+ height_scaled = int(round(path_height * scale))
217
+
218
+ # print (f"W: {final_icon_width} vs {width_scaled}, {final_icon_height} vs {height_scaled}")
219
+ keep_ratio = True
220
+
221
+ if keep_ratio:
222
+ scale_x = min(scale_x, scale_y)
223
+ scale_y = scale_x
224
+
225
+ from meerk40t.gui.zmatrix import ZMatrix
226
+ from meerk40t.svgelements import Matrix
227
+
228
+ matrix = Matrix()
229
+ matrix.post_translate(
230
+ -min_x
231
+ + edge
232
+ + stroke_buffer
233
+ + (final_icon_width - width_scaled) / 2 / scale_x,
234
+ -min_y
235
+ + edge
236
+ + stroke_buffer
237
+ + (final_icon_height - height_scaled) / 2 / scale_x,
238
+ )
239
+ matrix.post_scale(scale_x, scale_y)
240
+ if scale_y < 0:
241
+ matrix.pre_translate(0, -height_scaled)
242
+ if scale_x < 0:
243
+ matrix.pre_translate(-width_scaled, 0)
244
+
245
+ gc = wx.GraphicsContext.Create(dc)
246
+ gc.dc = dc
247
+ gc.SetInterpolationQuality(wx.INTERPOLATION_BEST)
248
+ gc.PushState()
249
+ if not matrix.is_identity():
250
+ gc.ConcatTransform(wx.GraphicsContext.CreateMatrix(gc, ZMatrix(matrix)))
251
+ if as_stroke:
252
+ gc.SetPen(spen)
253
+ gc.StrokePath(gp)
254
+ else:
255
+ gc.SetBrush(sbrush)
256
+ gc.FillPath(gp, fillStyle=wx.WINDING_RULE)
257
+ dc.SelectObject(wx.NullBitmap)
258
+ gc.Destroy()
259
+ del gc.dc
260
+ del dc
261
+ return bmp
262
+
263
+ def getbitmap(geom, icon_size, as_stroke=False):
264
+ final_icon_height = int(icon_size)
265
+ final_icon_width = int(icon_size)
266
+ if final_icon_height <= 0:
267
+ final_icon_height = 1
268
+ if final_icon_width <= 0:
269
+ final_icon_width = 1
270
+ bmp = prepare_bitmap(
271
+ geom, final_icon_width, final_icon_height, as_stroke=as_stroke
272
+ )
273
+ return bmp
274
+
275
+ self.list_glyphs.DeleteAllItems()
276
+ self.images.RemoveAll()
277
+ from meerk40t.extra.hershey import FontPath
278
+
279
+ fontfile = self.context.fonts.full_name(self.font)
280
+
281
+ cfont = self.context.fonts.cached_fontclass(fontfile)
282
+ if cfont is None:
283
+ return
284
+ as_stroke = getattr(cfont, "STROKE_BASED", False)
285
+ for c in cfont.glyphs:
286
+ if isinstance(c, str):
287
+ if len(c) > 1:
288
+ # print (f"Strange: {c}, use {idx} instead")
289
+ continue
290
+ if ord(c) == 65535:
291
+ continue
292
+ cstr = str(c)
293
+ elif isinstance(c, int):
294
+ if c == 65535:
295
+ continue
296
+ cstr = chr(c)
297
+ else:
298
+ continue
299
+ hexa = cstr.encode("utf-8")
300
+ item = self.list_glyphs.InsertItem(self.list_glyphs.ItemCount, hexa)
301
+ self.list_glyphs.SetItem(item, 1, str(ord(cstr)))
302
+ self.list_glyphs.SetItem(item, 2, cstr)
303
+ path = FontPath(False)
304
+ try:
305
+ cfont.render(
306
+ path,
307
+ cstr,
308
+ True,
309
+ 12.0,
310
+ 1.0,
311
+ 1.1,
312
+ "left",
313
+ )
314
+ # path contains now the geometry...
315
+ okay = True
316
+ except Exception as e:
317
+ self.list_glyphs.SetItem(item, 3, str(e))
318
+ okay = False
319
+ # path contains now the geometry...
320
+ if okay:
321
+ geo = path.geometry
322
+ # print (f"Length {geo.index} after rendering: {ord(c)} / '{hexa}'")
323
+ bmp = getbitmap(geo, self.icon_size, as_stroke=as_stroke)
324
+ if bmp is not None:
325
+ image_index = self.images.Add(bmp)
326
+ self.list_glyphs.SetItemImage(item, image_index)
327
+ else:
328
+ self.list_glyphs.SetItem(item, 3, "Could not create bitmap")
329
+ # for idx in range(self.images.GetImageCount()):
330
+ # bmp = self.images.GetBitmap(idx)
331
+ # bmp.SaveFile(f"C:\\temp\\bmp_{idx}.png", type=wx.BITMAP_TYPE_PNG)
332
+
333
+ def on_dbl_click(self, event):
334
+ # Get the ascii code
335
+ x, y = event.GetPosition()
336
+ row_id, flags = self.list_glyphs.HitTest((x, y))
337
+ if row_id < 0:
338
+ return
339
+ listitem = self.list_glyphs.GetItem(row_id, 1)
340
+ data = listitem.GetText()
341
+ try:
342
+ code = int(data)
343
+ except ValueError:
344
+ return
345
+ content = self.txt_result.GetValue() + chr(code)
346
+ self.txt_result.ChangeValue(content)
347
+
348
+ def result(self):
349
+ return self.txt_result.GetValue()
350
+
351
+
352
+ class LineTextPropertyPanel(wx.Panel):
353
+ """
354
+ Panel for post-creation text property editing
355
+ """
356
+
357
+ def __init__(
358
+ self,
359
+ *args,
360
+ context=None,
361
+ node=None,
362
+ **kwds,
363
+ ):
364
+ # begin wxGlade: LayerSettingPanel.__init__
365
+ kwds["style"] = kwds.get("style", 0)
366
+ wx.Panel.__init__(self, *args, **kwds)
367
+ self.context = context
368
+ self.context.themes.set_window_colors(self)
369
+ self.context.setting(float, "last_font_size", float(Length("20px")))
370
+ self.context.setting(str, "last_font", "")
371
+
372
+ self.node = node
373
+ self.fonts = []
374
+
375
+ # We neeed this to avoid a crash under Linux when textselection is called too quickly
376
+ self._islinux = platform.system() == "Linux"
377
+
378
+ main_sizer = StaticBoxSizer(self, wx.ID_ANY, _("Vector-Text"), wx.VERTICAL)
379
+
380
+ sizer_text = StaticBoxSizer(self, wx.ID_ANY, _("Content"), wx.HORIZONTAL)
381
+ self.text_text = TextCtrl(
382
+ self, wx.ID_ANY, "", style=wx.TE_PROCESS_ENTER | wx.TE_MULTILINE
383
+ )
384
+ sizer_text.Add(self.text_text, 1, wx.EXPAND, 0)
385
+
386
+ text_options = StaticBoxSizer(self, wx.ID_ANY, "", wx.HORIZONTAL)
387
+
388
+ iconsize = dip_size(self, 25, 25)[0]
389
+
390
+ align_options = (_("Left"), _("Center"), _("Right"))
391
+ align_icons = (icon_textalign_left, icon_textalign_center, icon_textalign_right)
392
+ self.rb_align = []
393
+ ttip_main = _("Textalignment for multi-lines")
394
+ for ttip_sub, icon in zip(align_options, align_icons):
395
+ btn = wxToggleButton(self, wx.ID_ANY)
396
+ btn.SetToolTip(f"{ttip_main}: {ttip_sub}")
397
+ btn.SetValue(False)
398
+ btn.SetBitmap(icon.GetBitmap(resize=iconsize))
399
+ self.rb_align.append(btn)
400
+ text_options.Add(btn, 0, wx.ALIGN_CENTER_VERTICAL, 0)
401
+
402
+ text_options.AddSpacer(25)
403
+
404
+ self.btn_bigger = wxButton(self, wx.ID_ANY)
405
+ self.btn_bigger.SetToolTip(_("Increase the font-size"))
406
+ self.btn_bigger.SetBitmap(icon_textsize_up.GetBitmap(resize=iconsize))
407
+ text_options.Add(self.btn_bigger, 0, wx.ALIGN_CENTER_VERTICAL, 0)
408
+
409
+ self.btn_smaller = wxButton(self, wx.ID_ANY)
410
+ self.btn_smaller.SetToolTip(_("Decrease the font-size"))
411
+ self.btn_smaller.SetBitmap(icon_textsize_down.GetBitmap(resize=iconsize))
412
+ text_options.Add(self.btn_smaller, 0, wx.ALIGN_CENTER_VERTICAL, 0)
413
+
414
+ text_options.AddSpacer(25)
415
+
416
+ msg = (
417
+ "\n"
418
+ + _("- Hold shift/ctrl-Key down for bigger change")
419
+ + "\n"
420
+ + _("- Right click will reset value to default")
421
+ )
422
+
423
+ self.btn_bigger_spacing = wxButton(self, wx.ID_ANY)
424
+ self.btn_bigger_spacing.SetToolTip(_("Increase the character-gap") + msg)
425
+ self.btn_bigger_spacing.SetBitmap(
426
+ icon_kerning_bigger.GetBitmap(resize=iconsize)
427
+ )
428
+ text_options.Add(self.btn_bigger_spacing, 0, wx.ALIGN_CENTER_VERTICAL, 0)
429
+
430
+ self.btn_smaller_spacing = wxButton(self, wx.ID_ANY)
431
+ self.btn_smaller_spacing.SetToolTip(_("Decrease the character-gap") + msg)
432
+ self.btn_smaller_spacing.SetBitmap(
433
+ icon_kerning_smaller.GetBitmap(resize=iconsize)
434
+ )
435
+ text_options.Add(self.btn_smaller_spacing, 0, wx.ALIGN_CENTER_VERTICAL, 0)
436
+
437
+ text_options.AddSpacer(25)
438
+
439
+ self.btn_attrib_lineplus = wxButton(self, id=wx.ID_ANY)
440
+ self.btn_attrib_lineminus = wxButton(self, id=wx.ID_ANY)
441
+ text_options.Add(self.btn_attrib_lineplus, 0, wx.ALIGN_CENTER_VERTICAL, 0)
442
+ text_options.Add(self.btn_attrib_lineminus, 0, wx.ALIGN_CENTER_VERTICAL, 0)
443
+
444
+ self.btn_attrib_lineplus.SetToolTip(_("Increase line distance") + msg)
445
+ self.btn_attrib_lineminus.SetToolTip(_("Reduce line distance") + msg)
446
+ self.btn_attrib_lineplus.SetBitmap(
447
+ icon_linegap_bigger.GetBitmap(resize=iconsize)
448
+ )
449
+ self.btn_attrib_lineminus.SetBitmap(
450
+ icon_linegap_smaller.GetBitmap(resize=iconsize)
451
+ )
452
+ self.check_weld = wxCheckBox(self, wx.ID_ANY, "")
453
+ self.check_weld.SetToolTip(_("Weld overlapping characters together?"))
454
+ text_options.AddSpacer(25)
455
+ text_options.Add(self.check_weld, 0, wx.ALIGN_CENTER_VERTICAL, 0)
456
+
457
+ for btn in (
458
+ self.rb_align[0],
459
+ self.rb_align[1],
460
+ self.rb_align[2],
461
+ self.btn_bigger,
462
+ self.btn_smaller,
463
+ self.btn_bigger_spacing,
464
+ self.btn_smaller_spacing,
465
+ self.btn_attrib_lineminus,
466
+ self.btn_attrib_lineplus,
467
+ ):
468
+ btn.SetMinSize(dip_size(self, 35, 35))
469
+
470
+ sizer_fonts = StaticBoxSizer(
471
+ self, wx.ID_ANY, _("Fonts (double-click to use)"), wx.VERTICAL
472
+ )
473
+
474
+ self.list_fonts = wxListBox(self, wx.ID_ANY)
475
+ self.list_fonts.SetMinSize(dip_size(self, -1, 140))
476
+ self.list_fonts.SetToolTip(
477
+ _("Select to preview the font, double-click to apply it")
478
+ )
479
+ sizer_fonts.Add(self.list_fonts, 0, wx.EXPAND, 0)
480
+
481
+ self.bmp_preview = wxStaticBitmap(self, wx.ID_ANY)
482
+ self.bmp_preview.SetMinSize(dip_size(self, -1, 50))
483
+ sizer_fonts.Add(self.bmp_preview, 0, wx.EXPAND, 0)
484
+
485
+ main_sizer.Add(sizer_text, 0, wx.EXPAND, 0)
486
+ main_sizer.Add(text_options, 0, wx.EXPAND, 0)
487
+ main_sizer.Add(sizer_fonts, 0, wx.EXPAND, 0)
488
+ self.SetSizer(main_sizer)
489
+ self.Layout()
490
+ self.btn_bigger.Bind(wx.EVT_BUTTON, self.on_button_bigger)
491
+ self.btn_smaller.Bind(wx.EVT_BUTTON, self.on_button_smaller)
492
+
493
+ self.btn_bigger_spacing.Bind(wx.EVT_BUTTON, self.on_button_bigger_spacing)
494
+ self.btn_smaller_spacing.Bind(wx.EVT_BUTTON, self.on_button_smaller_spacing)
495
+ self.btn_bigger_spacing.Bind(wx.EVT_RIGHT_DOWN, self.on_button_reset_spacing)
496
+ self.btn_smaller_spacing.Bind(wx.EVT_RIGHT_DOWN, self.on_button_reset_spacing)
497
+ self.Bind(wx.EVT_BUTTON, self.on_linegap_bigger, self.btn_attrib_lineplus)
498
+ self.Bind(wx.EVT_BUTTON, self.on_linegap_smaller, self.btn_attrib_lineminus)
499
+ self.btn_attrib_lineplus.Bind(wx.EVT_RIGHT_DOWN, self.on_linegap_reset)
500
+ self.btn_attrib_lineminus.Bind(wx.EVT_RIGHT_DOWN, self.on_linegap_reset)
501
+ self.check_weld.Bind(wx.EVT_CHECKBOX, self.on_weld)
502
+ for btn in self.rb_align:
503
+ btn.Bind(wx.EVT_TOGGLEBUTTON, self.on_radio_box)
504
+ self.text_text.Bind(wx.EVT_TEXT, self.on_text_change)
505
+ self.text_text.Bind(wx.EVT_RIGHT_DOWN, self.on_context_menu)
506
+ self.list_fonts.Bind(wx.EVT_LISTBOX, self.on_list_font)
507
+ self.list_fonts.Bind(wx.EVT_LISTBOX_DCLICK, self.on_list_font_dclick)
508
+
509
+ self.set_widgets(self.node)
510
+
511
+ def pane_hide(self):
512
+ pass
513
+
514
+ def pane_show(self):
515
+ pass
516
+
517
+ def accepts(self, node):
518
+ if (
519
+ hasattr(node, "mkfont")
520
+ and hasattr(node, "mkfontsize")
521
+ and hasattr(node, "mktext")
522
+ ):
523
+ # Let's take the opportunity to check for incorrect types and fix them...
524
+ self.context.fonts.validate_node(node)
525
+ return True
526
+ else:
527
+ return False
528
+
529
+ def set_widgets(self, node):
530
+ self.node = node
531
+ # print(f"set_widget for {self.attribute} to {str(node)}")
532
+ if self.node is None or not self.accepts(node):
533
+ self.Hide()
534
+ return
535
+ if not hasattr(self.node, "mkfontspacing") or self.node.mkfontspacing is None:
536
+ self.node.mkfontspacing = 1.0
537
+ if not hasattr(self.node, "mklinegap") or self.node.mklinegap is None:
538
+ self.node.mklinegap = 1.1
539
+ if not hasattr(self.node, "mkfontweld") or self.node.mkfontweld is None:
540
+ self.node.mkfontweld = False
541
+ self.check_weld.SetValue(self.node.mkfontweld)
542
+ if not hasattr(self.node, "mkalign") or self.node.mkalign is None:
543
+ self.node.mkalign = "start"
544
+ vals = ("start", "middle", "end")
545
+ try:
546
+ idx = vals.index(self.node.mkalign)
547
+ except IndexError:
548
+ idx = 0
549
+ for b_idx, btn in enumerate(self.rb_align):
550
+ btn.SetValue(bool(idx == b_idx))
551
+
552
+ self.load_directory()
553
+ self.text_text.ChangeValue(str(node.mktext))
554
+ self.Show()
555
+
556
+ def load_directory(self):
557
+ self.list_fonts.Clear()
558
+ self.fonts = self.context.fonts.available_fonts()
559
+ font_desc = [e[1] for e in self.fonts]
560
+ self.list_fonts.SetItems(font_desc)
561
+ # index = -1
562
+ # lookfor = getattr(self.context, "sxh_preferred", "")
563
+
564
+ def update_node(self):
565
+ vtext = self.text_text.GetValue()
566
+ self.context.fonts.update_linetext(self.node, vtext)
567
+ self.context.signal("element_property_reload", self.node)
568
+ self.context.signal("refresh_scene", "Scene")
569
+ self.context.last_font = self.node.mkfont
570
+ self.context.last_font_size = self.node.mkfontsize
571
+
572
+ def on_linegap_reset(self, event):
573
+ if self.node is None:
574
+ return
575
+ self.node.mklinegap = 1.1
576
+ self.update_node()
577
+
578
+ def on_linegap_bigger(self, event):
579
+ if self.node is None:
580
+ return
581
+ gap = 0.01
582
+ if wx.GetKeyState(wx.WXK_SHIFT):
583
+ gap = 0.1
584
+ if wx.GetKeyState(wx.WXK_CONTROL):
585
+ gap = 0.25
586
+ if self.node.mklinegap is None:
587
+ self.node.mklinegap = 1.1
588
+ else:
589
+ self.node.mklinegap += gap
590
+ self.update_node()
591
+
592
+ def on_linegap_smaller(self, event):
593
+ if self.node is None:
594
+ return
595
+ gap = 0.01
596
+ if wx.GetKeyState(wx.WXK_SHIFT):
597
+ gap = 0.1
598
+ if wx.GetKeyState(wx.WXK_CONTROL):
599
+ gap = 0.25
600
+ if self.node.mklinegap is None:
601
+ self.node.mklinegap = 1.1
602
+ else:
603
+ self.node.mklinegap -= gap
604
+ if self.node.mklinegap < 0:
605
+ self.node.mklinegap = 0
606
+ self.update_node()
607
+
608
+ def on_radio_box(self, event):
609
+ evt_btn = event.GetEventObject()
610
+ for idx, btn in enumerate(self.rb_align):
611
+ if btn is evt_btn:
612
+ new_anchor = idx
613
+ btn.SetValue(True)
614
+ else:
615
+ btn.SetValue(False)
616
+ if new_anchor == 0:
617
+ self.node.mkalign = "start"
618
+ elif new_anchor == 1:
619
+ self.node.mkalign = "middle"
620
+ elif new_anchor == 2:
621
+ self.node.mkalign = "end"
622
+ self.update_node()
623
+
624
+ def on_weld(self, event):
625
+ if self.node is None:
626
+ return
627
+ self.node.mkfontweld = self.check_weld.GetValue()
628
+ self.update_node()
629
+
630
+ def on_button_bigger(self, event):
631
+ if self.node is None:
632
+ return
633
+ self.node.mkfontsize *= 1.2
634
+ self.update_node()
635
+
636
+ def on_button_smaller(self, event):
637
+ if self.node is None:
638
+ return
639
+ self.node.mkfontsize /= 1.2
640
+ self.update_node()
641
+
642
+ def on_button_reset_spacing(self, event):
643
+ # print ("Reset")
644
+ self.node.mkfontspacing = 1.0
645
+ self.update_node()
646
+
647
+ def on_button_bigger_spacing(self, event):
648
+ if self.node is None:
649
+ return
650
+ gap = 0.01
651
+ if wx.GetKeyState(wx.WXK_SHIFT):
652
+ gap = 0.1
653
+ if wx.GetKeyState(wx.WXK_CONTROL):
654
+ gap = 0.25
655
+ self.node.mkfontspacing += gap
656
+ self.update_node()
657
+
658
+ def on_button_smaller_spacing(self, event):
659
+ if self.node is None:
660
+ return
661
+ gap = 0.01
662
+ if wx.GetKeyState(wx.WXK_SHIFT):
663
+ gap = 0.1
664
+ if wx.GetKeyState(wx.WXK_CONTROL):
665
+ gap = 0.25
666
+ self.node.mkfontspacing -= gap
667
+ self.update_node()
668
+
669
+ def on_text_change(self, event):
670
+ self.update_node()
671
+
672
+ def on_list_font_dclick(self, event):
673
+ if self.node is None:
674
+ return
675
+ index = self.list_fonts.GetSelection()
676
+ if index >= 0:
677
+ fontinfo = self.fonts[index]
678
+ fontname = os.path.basename(fontinfo[0])
679
+ self.node.mkfont = fontname
680
+ self.update_node()
681
+
682
+ def on_list_font(self, event):
683
+ if self.list_fonts.GetSelection() >= 0:
684
+ font_info = self.fonts[self.list_fonts.GetSelection()]
685
+ full_font_file = font_info[0]
686
+ bmp = self.context.fonts.preview_file(full_font_file)
687
+ # if bmp is not None:
688
+ # bmap_bundle = wx.BitmapBundle().FromBitmap(bmp)
689
+ # else:
690
+ # bmap_bundle = wx.BitmapBundle()
691
+ # self.bmp_preview.SetBitmap(bmap_bundle)
692
+ if bmp is None:
693
+ bmp = wx.NullBitmap
694
+ self.bmp_preview.SetBitmap(bmp)
695
+
696
+ def on_context_menu(self, event):
697
+ def on_paste(event):
698
+ self.text_text.Paste()
699
+
700
+ def on_glyph(event):
701
+ mydlg = FontGlyphPicker(
702
+ None, id=wx.ID_ANY, context=self.context, font=self.node.mkfont
703
+ )
704
+ glyphs = None
705
+ if mydlg.ShowModal() == wx.ID_OK:
706
+ # This returns a string of characters that need to be inserted
707
+ glyphs = mydlg.result()
708
+ mydlg.Destroy()
709
+ if glyphs is None or glyphs == "":
710
+ return
711
+ text = self.text_text.GetValue()
712
+ pos = self.text_text.GetInsertionPoint()
713
+ if pos == self.text_text.GetLastPosition():
714
+ before = text
715
+ after = ""
716
+ else:
717
+ before = self.text_text.GetValue()[:pos]
718
+ after = self.text_text.GetValue()[pos:]
719
+ self.text_text.SetValue(before + glyphs + after)
720
+
721
+ menu = wx.Menu()
722
+ item = menu.Append(wx.ID_ANY, _("Paste"), "", wx.ITEM_NORMAL)
723
+ self.Bind(wx.EVT_MENU, on_paste, item)
724
+ item = menu.Append(wx.ID_ANY, _("Insert symbol"), "", wx.ITEM_NORMAL)
725
+ self.Bind(wx.EVT_MENU, on_glyph, item)
726
+ self.PopupMenu(menu)
727
+ menu.Destroy()
728
+
729
+
730
+ def signal(self, signalstr, myargs):
731
+ if signalstr == "textselect" and self.IsShown():
732
+ # This can crash for completely unknown reasons under Linux!
733
+ # Hypothesis: you can't focus / SelectStuff if the control is not yet shown.
734
+ if self._islinux:
735
+ return
736
+ self.text_text.SelectAll()
737
+ self.text_text.SetFocus()
738
+
739
+
740
+ class PanelFontSelect(wx.Panel):
741
+ """
742
+ Panel to select font during line text creation
743
+ """
744
+
745
+ def __init__(self, *args, context=None, **kwds):
746
+ # begin wxGlade: clsLasertools.__init__
747
+ kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
748
+ wx.Panel.__init__(self, *args, **kwds)
749
+ self.context = context
750
+ self.context.themes.set_window_colors(self)
751
+
752
+ mainsizer = wx.BoxSizer(wx.VERTICAL)
753
+
754
+ self.all_fonts = []
755
+ self.fonts = []
756
+ self.font_checks = {}
757
+
758
+ fontinfo = self.context.fonts.fonts_registered
759
+ sizer_checker = wx.BoxSizer(wx.HORIZONTAL)
760
+ for extension in fontinfo:
761
+ info = fontinfo[extension]
762
+ checker = wxCheckBox(self, wx.ID_ANY, info[0])
763
+ checker.SetValue(True)
764
+ checker.Bind(wx.EVT_CHECKBOX, self.on_checker(extension))
765
+ checker.SetToolTip(
766
+ _("Show/Hide all fonts of type {info[0]}").format(info=info)
767
+ )
768
+ self.font_checks[extension] = [checker, True]
769
+ sizer_checker.Add(checker, 0, 0, wx.ALIGN_CENTER_VERTICAL)
770
+
771
+ sizer_fonts = StaticBoxSizer(
772
+ self, wx.ID_ANY, _("Fonts (double-click to use)"), wx.VERTICAL
773
+ )
774
+ mainsizer.Add(sizer_fonts, 1, wx.EXPAND, 0)
775
+
776
+ self.list_fonts = wxListBox(self, wx.ID_ANY)
777
+ self.list_fonts.SetToolTip(
778
+ _("Select to preview the font, double-click to apply it")
779
+ )
780
+ sizer_fonts.Add(self.list_fonts, 1, wx.EXPAND, 0)
781
+ sizer_fonts.Add(sizer_checker, 0, wx.EXPAND, 0)
782
+
783
+ self.bmp_preview = wxStaticBitmap(self, wx.ID_ANY)
784
+ self.bmp_preview.SetMinSize(dip_size(self, -1, 70))
785
+ sizer_fonts.Add(self.bmp_preview, 0, wx.EXPAND, 0)
786
+
787
+ sizer_buttons = wx.BoxSizer(wx.HORIZONTAL)
788
+ sizer_fonts.Add(sizer_buttons, 0, wx.EXPAND, 0)
789
+
790
+ picsize = dip_size(self, 32, 32)
791
+ icon_size = picsize[0]
792
+ bsize = icon_size * self.context.root.bitmap_correction_scale
793
+
794
+ self.btn_bigger = wxButton(self, wx.ID_ANY)
795
+ self.btn_bigger.SetBitmap(icon_textsize_up.GetBitmap(resize=bsize))
796
+ self.btn_bigger.SetToolTip(_("Increase the font-size"))
797
+ sizer_buttons.Add(self.btn_bigger, 0, wx.EXPAND, 0)
798
+
799
+ self.btn_smaller = wxButton(self, wx.ID_ANY)
800
+ self.btn_smaller.SetBitmap(icon_textsize_down.GetBitmap(resize=bsize))
801
+ self.btn_smaller.SetToolTip(_("Decrease the font-size"))
802
+ sizer_buttons.Add(self.btn_smaller, 0, wx.EXPAND, 0)
803
+
804
+ sizer_buttons.AddSpacer(25)
805
+
806
+ self.btn_align_left = wxButton(self, wx.ID_ANY)
807
+ self.btn_align_left.SetBitmap(icon_textalign_left.GetBitmap(resize=bsize))
808
+ self.btn_align_left.SetToolTip(_("Align text on the left side"))
809
+ sizer_buttons.Add(self.btn_align_left, 0, wx.EXPAND, 0)
810
+
811
+ self.btn_align_center = wxButton(self, wx.ID_ANY)
812
+ self.btn_align_center.SetBitmap(
813
+ icon_textalign_center.GetBitmap(resize=bsize)
814
+ )
815
+ self.btn_align_center.SetToolTip(_("Align text around the center"))
816
+ sizer_buttons.Add(self.btn_align_center, 0, wx.EXPAND, 0)
817
+
818
+ self.btn_align_right = wxButton(self, wx.ID_ANY)
819
+ self.btn_align_right.SetBitmap(icon_textalign_right.GetBitmap(resize=bsize))
820
+ self.btn_align_right.SetToolTip(_("Align text on the right side"))
821
+ sizer_buttons.Add(self.btn_align_right, 0, wx.EXPAND, 0)
822
+
823
+ for btn in (
824
+ self.btn_align_center,
825
+ self.btn_align_left,
826
+ self.btn_align_right,
827
+ self.btn_bigger,
828
+ self.btn_smaller,
829
+ ):
830
+ btn.SetMaxSize(dip_size(self, icon_size + 4, -1))
831
+
832
+ lbl_spacer = wxStaticText(self, wx.ID_ANY, "")
833
+ sizer_buttons.Add(lbl_spacer, 1, 0, 0)
834
+
835
+ self.SetSizer(mainsizer)
836
+
837
+ self.Layout()
838
+
839
+ self.Bind(wx.EVT_LISTBOX, self.on_list_font, self.list_fonts)
840
+ self.Bind(wx.EVT_LISTBOX_DCLICK, self.on_list_font_dclick, self.list_fonts)
841
+ self.Bind(wx.EVT_BUTTON, self.on_btn_bigger, self.btn_bigger)
842
+ self.Bind(wx.EVT_BUTTON, self.on_btn_smaller, self.btn_smaller)
843
+
844
+ self.Bind(wx.EVT_BUTTON, self.on_align("start"), self.btn_align_left)
845
+ self.Bind(wx.EVT_BUTTON, self.on_align("middle"), self.btn_align_center)
846
+ self.Bind(wx.EVT_BUTTON, self.on_align("end"), self.btn_align_right)
847
+
848
+ # end wxGlade
849
+ self.load_directory()
850
+
851
+ def load_directory(self):
852
+ self.all_fonts = self.context.fonts.available_fonts()
853
+ self.list_fonts.Clear()
854
+ self.populate_list_box()
855
+
856
+ def populate_list_box(self):
857
+ self.fonts.clear()
858
+ font_desc = []
859
+ for entry in self.all_fonts:
860
+ # 0 basename, 1 full_path, 2 facename
861
+ parts = os.path.splitext(entry[0])
862
+ if len(parts) > 1:
863
+ extension = parts[1][1:].lower()
864
+ if extension in self.font_checks:
865
+ if not self.font_checks[extension][1]:
866
+ entry = None
867
+ if entry is not None:
868
+ self.fonts.append(entry[0])
869
+ font_desc.append(entry[1])
870
+
871
+ self.list_fonts.SetItems(font_desc)
872
+
873
+ def on_checker(self, extension):
874
+ def handler(event):
875
+ self.font_checks[extension][1] = not self.font_checks[extension][1]
876
+ # Reload List
877
+ self.populate_list_box()
878
+
879
+ return handler
880
+
881
+ def on_btn_bigger(self, event):
882
+ self.context.signal("linetext", "bigger")
883
+
884
+ def on_btn_smaller(self, event):
885
+ self.context.signal("linetext", "smaller")
886
+
887
+ def on_align(self, alignment):
888
+ def handler(event):
889
+ self.context.signal("linetext", "align", local_alignment)
890
+
891
+ local_alignment = alignment
892
+ return handler
893
+
894
+ def on_list_font_dclick(self, event):
895
+ index = self.list_fonts.GetSelection()
896
+ if index >= 0:
897
+ fontname = self.fonts[index]
898
+ self.context.signal("linetext", "font", fontname)
899
+
900
+ def on_list_font(self, event):
901
+ if self.list_fonts.GetSelection() >= 0:
902
+ full_font_file = self.fonts[self.list_fonts.GetSelection()]
903
+ bmp = self.context.fonts.preview_file(full_font_file)
904
+ # if bmp is not None:
905
+ # bmap_bundle = wx.BitmapBundle().FromBitmap(bmp)
906
+ # else:
907
+ # bmap_bundle = wx.BitmapBundle()
908
+ # self.bmp_preview.SetBitmap(bmap_bundle)
909
+ if bmp is None:
910
+ bmp = wx.NullBitmap
911
+ self.bmp_preview.SetBitmap(bmp)
912
+
913
+
914
+ class HersheyFontSelector(MWindow):
915
+ """
916
+ Wrapper Window Class for font selection panel
917
+ """
918
+
919
+ def __init__(self, *args, **kwds):
920
+ super().__init__(450, 550, submenu="", *args, **kwds)
921
+ self.panel = PanelFontSelect(self, wx.ID_ANY, context=self.context)
922
+ self.sizer.Add(self.panel, 1, wx.EXPAND, 0)
923
+ _icon = wx.NullIcon
924
+ _icon.CopyFromBitmap(
925
+ icons8_choose_font.GetBitmap(resize=0.5 * get_default_icon_size(self.context))
926
+ )
927
+ # _icon.CopyFromBitmap(icons8_computer_support.GetBitmap())
928
+ self.SetIcon(_icon)
929
+ self.SetTitle(_("Font-Selection"))
930
+ self.restore_aspect()
931
+
932
+ def window_open(self):
933
+ pass
934
+
935
+ def window_close(self):
936
+ # We don't need an automatic opening
937
+ self.window_context.open_on_start = False
938
+
939
+ def delegates(self):
940
+ yield self.panel
941
+
942
+ @staticmethod
943
+ def submenu():
944
+ # Suppress = True
945
+ return "", "Font-Selector", True
946
+
947
+ @staticmethod
948
+ def helptext():
949
+ return _("Pick a font to use for vector text")
950
+
951
+ @signal_listener("tool_changed")
952
+ def on_tool_changed(self, origin, newtool=None, *args):
953
+ # Signal provides a tuple with (togglegroup, id)
954
+ needs_close = True
955
+ if newtool is not None:
956
+ if isinstance(newtool, (list, tuple)):
957
+ group = newtool[0].lower() if newtool[0] is not None else ""
958
+ identifier = newtool[1].lower() if newtool[1] is not None else ""
959
+ else:
960
+ group = newtool
961
+ identifier = ""
962
+ needs_close = identifier != "linetext"
963
+ if needs_close:
964
+ self.Close()
965
+
966
+
967
+ class PanelFontManager(wx.Panel):
968
+ """
969
+ Vector Font Manager
970
+ """
971
+
972
+ def __init__(self, *args, context=None, **kwds):
973
+ # begin wxGlade: clsLasertools.__init__
974
+ kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
975
+ wx.Panel.__init__(self, *args, **kwds)
976
+ self.context = context
977
+ self.context.themes.set_window_colors(self)
978
+ self.SetHelpText("vectortext")
979
+
980
+ mainsizer = wx.BoxSizer(wx.VERTICAL)
981
+
982
+ self.font_infos = []
983
+
984
+ self.text_info = TextCtrl(
985
+ self,
986
+ wx.ID_ANY,
987
+ _(
988
+ "MeerK40t can use True-Type-Fonts, Hershey-Fonts or Autocad-86 shape fonts designed to be rendered purely with vectors.\n"
989
+ + "They can be scaled, burned like any other vector shape and are therefore very versatile.\n"
990
+ + "See more: https://en.wikipedia.org/wiki/Hershey_fonts "
991
+ ),
992
+ style=wx.BORDER_NONE | wx.TE_MULTILINE | wx.TE_READONLY,
993
+ )
994
+
995
+ self.text_info.SetMinSize(dip_size(self, -1, 90))
996
+ self.text_info.SetBackgroundColour(self.GetBackgroundColour())
997
+ sizer_info = StaticBoxSizer(self, wx.ID_ANY, _("Information"), wx.HORIZONTAL)
998
+ mainsizer.Add(sizer_info, 0, wx.EXPAND, 0)
999
+ sizer_info.Add(self.text_info, 1, wx.EXPAND, 0)
1000
+
1001
+ sizer_directory = StaticBoxSizer(
1002
+ self, wx.ID_ANY, _("Font-Work-Directory"), wx.HORIZONTAL
1003
+ )
1004
+ mainsizer.Add(sizer_directory, 0, wx.EXPAND, 0)
1005
+
1006
+ self.text_fontdir = TextCtrl(self, wx.ID_ANY, "")
1007
+ sizer_directory.Add(self.text_fontdir, 1, wx.EXPAND, 0)
1008
+ self.text_fontdir.SetToolTip(
1009
+ _(
1010
+ "Additional directory for userdefined fonts (also used to store some cache files)"
1011
+ )
1012
+ )
1013
+
1014
+ self.btn_dirselect = wxButton(self, wx.ID_ANY, "...")
1015
+ sizer_directory.Add(self.btn_dirselect, 0, wx.EXPAND, 0)
1016
+
1017
+ choices = []
1018
+ prechoices = context.lookup("choices/preferences")
1019
+ for info in prechoices:
1020
+ if info["attr"] == "system_font_directories":
1021
+ cinfo = dict(info)
1022
+ cinfo["page"] = ""
1023
+ choices.append(cinfo)
1024
+ break
1025
+
1026
+ self.sysdirs = ChoicePropertyPanel(
1027
+ self, wx.ID_ANY, context=self.context, choices=choices, scrolling=False
1028
+ )
1029
+ mainsizer.Add(self.sysdirs, 0, wx.EXPAND, 0)
1030
+ sizer_fonts = StaticBoxSizer(self, wx.ID_ANY, _("Fonts"), wx.VERTICAL)
1031
+ mainsizer.Add(sizer_fonts, 1, wx.EXPAND, 0)
1032
+
1033
+ self.list_fonts = wxListBox(self, wx.ID_ANY)
1034
+ sizer_fonts.Add(self.list_fonts, 1, wx.EXPAND, 0)
1035
+
1036
+ self.bmp_preview = wxStaticBitmap(self, wx.ID_ANY)
1037
+ self.bmp_preview.SetMinSize(dip_size(self, -1, 70))
1038
+ sizer_fonts.Add(self.bmp_preview, 0, wx.EXPAND, 0)
1039
+
1040
+ sizer_buttons = wx.BoxSizer(wx.HORIZONTAL)
1041
+ sizer_fonts.Add(sizer_buttons, 0, wx.EXPAND, 0)
1042
+
1043
+ self.btn_add = wxButton(self, wx.ID_ANY, _("Import"))
1044
+ sizer_buttons.Add(self.btn_add, 0, wx.EXPAND, 0)
1045
+
1046
+ self.btn_delete = wxButton(self, wx.ID_ANY, _("Delete"))
1047
+ sizer_buttons.Add(self.btn_delete, 0, wx.EXPAND, 0)
1048
+
1049
+ lbl_spacer = wxStaticText(self, wx.ID_ANY, "")
1050
+ sizer_buttons.Add(lbl_spacer, 1, 0, 0)
1051
+
1052
+ self.btn_refresh = wxButton(self, wx.ID_ANY, _("Refresh"))
1053
+ sizer_buttons.Add(self.btn_refresh, 0, wx.EXPAND, 0)
1054
+
1055
+ self.webresources = [
1056
+ "https://github.com/kamalmostafa/hershey-fonts/tree/master/hershey-fonts",
1057
+ "http://iki.fi/sol/hershey/index.html",
1058
+ "https://www.mepwork.com/2017/11/autocad-shx-fonts.html",
1059
+ ]
1060
+ choices = [
1061
+ _("Goto a font-source..."),
1062
+ _("Hershey Fonts - #1"),
1063
+ _("Hershey Fonts - #2"),
1064
+ _("Autocad-SHX-Fonts"),
1065
+ ]
1066
+ self.combo_webget = wxComboBox(
1067
+ self,
1068
+ wx.ID_ANY,
1069
+ choices=choices,
1070
+ style=wx.CB_DROPDOWN | wx.CB_READONLY,
1071
+ )
1072
+ self.combo_webget.SetSelection(0)
1073
+ sizer_buttons.Add(self.combo_webget, 0, wx.EXPAND, 0)
1074
+
1075
+ self.SetSizer(mainsizer)
1076
+ self.Layout()
1077
+ mainsizer.Fit(self)
1078
+
1079
+ self.Bind(wx.EVT_TEXT, self.on_text_directory, self.text_fontdir)
1080
+ self.Bind(wx.EVT_BUTTON, self.on_btn_directory, self.btn_dirselect)
1081
+ self.Bind(wx.EVT_LISTBOX, self.on_list_font, self.list_fonts)
1082
+ self.Bind(wx.EVT_LISTBOX_DCLICK, self.on_list_font_dclick, self.list_fonts)
1083
+ self.Bind(wx.EVT_BUTTON, self.on_btn_import, self.btn_add)
1084
+ self.Bind(wx.EVT_BUTTON, self.on_btn_delete, self.btn_delete)
1085
+ self.Bind(wx.EVT_BUTTON, self.on_btn_refresh, self.btn_refresh)
1086
+ self.Bind(wx.EVT_COMBOBOX, self.on_combo_webget, self.combo_webget)
1087
+ self.list_fonts.Bind(wx.EVT_MOTION, self.on_list_hover)
1088
+ # end wxGlade
1089
+ fontdir = self.context.fonts.font_directory
1090
+ self.text_fontdir.SetValue(fontdir)
1091
+
1092
+ def on_text_directory(self, event):
1093
+ fontdir = self.text_fontdir.GetValue()
1094
+ self.font_infos.clear()
1095
+ font_desc = []
1096
+ self.list_fonts.Clear()
1097
+ if os.path.exists(fontdir):
1098
+ self.context.fonts.font_directory = fontdir
1099
+ self.text_fontdir.SetBackgroundColour(
1100
+ wx.SystemSettings().GetColour(wx.SYS_COLOUR_WINDOW)
1101
+ )
1102
+ self.text_fontdir.SetToolTip(
1103
+ _(
1104
+ "Additional directory for userdefined fonts (also used to store some cache files)"
1105
+ )
1106
+ )
1107
+ else:
1108
+ self.text_fontdir.SetBackgroundColour(
1109
+ wx.SystemSettings().GetColour(wx.SYS_COLOUR_HIGHLIGHT)
1110
+ )
1111
+ self.text_fontdir.SetToolTip(
1112
+ _("Invalid directory! Will not be used, please provide a valid path.")
1113
+ )
1114
+ return
1115
+ # resp = wx.MessageBox(_("This is an invalid directory, do you want to use the default directory?"),_("Invalid directory"), style=wx.YES_NO|wx.ICON_WARNING)
1116
+ # if resp==wx.YES:
1117
+ # fontdir = self.context.fonts.font_directory
1118
+ # self.text_fontdir.SetValue(fontdir)
1119
+ # else:
1120
+ # return
1121
+ self.font_infos = self.context.fonts.available_fonts()
1122
+
1123
+ for info in self.font_infos:
1124
+ font_desc.append(info[1])
1125
+ self.list_fonts.SetItems(font_desc)
1126
+ # Let the world know we have fonts
1127
+ self.context.signal("icons")
1128
+
1129
+ def on_btn_directory(self, event):
1130
+ fontdir = self.text_fontdir.GetValue()
1131
+ dlg = wx.DirDialog(
1132
+ None,
1133
+ _("Choose font directory"),
1134
+ fontdir,
1135
+ style=wx.DD_DEFAULT_STYLE
1136
+ # | wx.DD_DIR_MUST_EXIST
1137
+ )
1138
+ if dlg.ShowModal() == wx.ID_OK:
1139
+ self.text_fontdir.SetValue(dlg.GetPath())
1140
+ # Only destroy a dialog after you're done with it.
1141
+ dlg.Destroy()
1142
+
1143
+ def on_list_font_dclick(self, event):
1144
+ if self.list_fonts.GetSelection() >= 0:
1145
+ font_file = self.font_infos[self.list_fonts.GetSelection()][0]
1146
+ self.context.setting(str, "last_font", None)
1147
+ self.context.last_font = font_file
1148
+
1149
+ def on_list_font(self, event):
1150
+ if self.list_fonts.GetSelection() >= 0:
1151
+ info = self.font_infos[self.list_fonts.GetSelection()]
1152
+ full_font_file = info[0]
1153
+ is_system = info[4]
1154
+ self.btn_delete.Enable(not is_system)
1155
+ bmp = self.context.fonts.preview_file(full_font_file)
1156
+ # if bmp is not None:
1157
+ # bmap_bundle = wx.BitmapBundle().FromBitmap(bmp)
1158
+ # else:
1159
+ # bmap_bundle = wx.BitmapBundle()
1160
+ # self.bmp_preview.SetBitmap(bmap_bundle)
1161
+ if bmp is None:
1162
+ bmp = wx.NullBitmap
1163
+ self.bmp_preview.SetBitmap(bmp)
1164
+
1165
+ def on_list_hover(self, event):
1166
+ event.Skip()
1167
+ pt = event.GetPosition()
1168
+ item = self.list_fonts.HitTest(pt)
1169
+ ttip = _("List of available fonts")
1170
+ if item >= 0:
1171
+ try:
1172
+ info = self.font_infos[item]
1173
+ ttip = f"{info[1]}\nFamily: {info[2]}\nSubfamily: {info[3]}\n{info[0]}"
1174
+ except IndexError:
1175
+ pass
1176
+ self.list_fonts.SetToolTip(ttip)
1177
+
1178
+ def on_btn_import(self, event, defaultdirectory=None, defaultextension=None):
1179
+ fontinfo = self.context.fonts.fonts_registered
1180
+ wildcard = "Vector-Fonts"
1181
+ idx = 0
1182
+ filterindex = 0
1183
+ # 1st put all into one wildcard-pattern
1184
+ for extension in fontinfo:
1185
+ ext = "*." + extension
1186
+ if idx == 0:
1187
+ wildcard += "|"
1188
+ else:
1189
+ wildcard += ";"
1190
+ wildcard += ext.lower() + ";" + ext.upper()
1191
+ idx += 1
1192
+ # 2nd add all individual wildcard-patterns
1193
+ for idx, extension in enumerate(fontinfo):
1194
+ if (
1195
+ defaultextension is not None
1196
+ and defaultextension.lower() == extension.lower()
1197
+ ):
1198
+ filterindex = idx + 1
1199
+ ext = "*." + extension
1200
+ info = fontinfo[extension]
1201
+ wildcard += f"|{info[0]}-Fonts|{ext.lower()};{ext.upper()}"
1202
+ wildcard += "|" + _("All files") + "|*.*"
1203
+ if defaultdirectory is None:
1204
+ defdir = ""
1205
+ else:
1206
+ defdir = defaultdirectory
1207
+ # print (os.listdir(os.path.join(os.environ['WINDIR'],'fonts')))
1208
+ dlg = wx.FileDialog(
1209
+ self,
1210
+ message=_(
1211
+ "Select a font-file to be imported into the the font-directory {fontdir}"
1212
+ ).format(fontdir=self.context.fonts.font_directory),
1213
+ defaultDir=defdir,
1214
+ defaultFile="",
1215
+ wildcard=wildcard,
1216
+ style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.FD_MULTIPLE | wx.FD_PREVIEW
1217
+ # | wx.FD_SHOW_HIDDEN,
1218
+ )
1219
+ try:
1220
+ # Might not be present in early wxpython versions
1221
+ dlg.SetFilterIndex(filterindex)
1222
+ except AttributeError:
1223
+ pass
1224
+ font_files = None
1225
+ if dlg.ShowModal() == wx.ID_OK:
1226
+ font_files = dlg.GetPaths()
1227
+ # Only destroy a dialog after you're done with it.
1228
+ dlg.Destroy()
1229
+ stats = [0, 0] # Successful, errors
1230
+ if font_files is None:
1231
+ return
1232
+
1233
+ maxidx = len(font_files)
1234
+ progress_string = _("Fonts imported: {count}")
1235
+ progress = wx.ProgressDialog(
1236
+ _("Importing fonts..."),
1237
+ progress_string.format(count=0),
1238
+ maximum=maxidx,
1239
+ parent=None,
1240
+ style=wx.PD_APP_MODAL | wx.PD_CAN_ABORT,
1241
+ )
1242
+ for idx, sourcefile in enumerate(font_files):
1243
+ basename = os.path.basename(sourcefile)
1244
+ destfile = os.path.join(self.context.fonts.font_directory, basename)
1245
+ # print (f"Source File: {sourcefile}\nTarget: {destfile}")
1246
+ try:
1247
+ with open(sourcefile, "rb") as f, open(destfile, "wb") as g:
1248
+ while True:
1249
+ block = f.read(1 * 1024 * 1024) # work by blocks of 1 MB
1250
+ if not block: # end of file
1251
+ break
1252
+ g.write(block)
1253
+ bmp = self.context.fonts.preview_file(destfile)
1254
+ if bmp is not None:
1255
+ stats[0] += 1
1256
+ else:
1257
+ # We delete this file again...
1258
+ remove_fontfile(destfile)
1259
+ stats[1] += 1
1260
+
1261
+ progress.Update(idx + 1, progress_string.format(count=idx + 1))
1262
+ if progress.WasCancelled():
1263
+ break
1264
+ except (OSError, RuntimeError, PermissionError, FileNotFoundError):
1265
+ stats[1] += 1
1266
+ progress.Destroy()
1267
+ wx.MessageBox(
1268
+ _(
1269
+ "Font-Import completed.\nImported: {ok}\nFailed: {fail}\nTotal: {total}"
1270
+ ).format(ok=stats[0], fail=stats[1], total=stats[0] + stats[1]),
1271
+ _("Import completed"),
1272
+ wx.OK | wx.ICON_INFORMATION,
1273
+ )
1274
+ # Reload....
1275
+ self.on_text_directory(None)
1276
+
1277
+ def on_btn_refresh(self, event):
1278
+ self.context.fonts.reset_cache()
1279
+ self.on_text_directory(None)
1280
+
1281
+ def on_btn_delete(self, event):
1282
+ if self.list_fonts.GetSelection() >= 0:
1283
+ info = self.font_infos[self.list_fonts.GetSelection()]
1284
+ full_font_file = info[0]
1285
+ font_file = os.path.basename(full_font_file)
1286
+ if self.context.fonts.is_system_font(full_font_file):
1287
+ return
1288
+ if (
1289
+ wx.MessageBox(
1290
+ _("Do you really want to delete this font: {font}").format(
1291
+ font=font_file
1292
+ ),
1293
+ _("Confirm"),
1294
+ wx.YES_NO | wx.CANCEL | wx.ICON_WARNING,
1295
+ )
1296
+ == wx.YES
1297
+ ):
1298
+ remove_fontfile(full_font_file)
1299
+ # Reload dir...
1300
+ self.on_text_directory(None)
1301
+
1302
+ def on_combo_webget(self, event):
1303
+ idx = self.combo_webget.GetSelection() - 1
1304
+ if idx >= 0:
1305
+ url = self.webresources[idx]
1306
+ if url.startswith("http"):
1307
+ if (
1308
+ wx.MessageBox(
1309
+ _(
1310
+ "You will be led now to a source in the web, where you can download free fonts.\n"
1311
+ + "Please respect individual property rights!\nDestination: {url}\n"
1312
+ ).format(url=url)
1313
+ + _(
1314
+ "Unpack the downloaded archive after the download and select the extracted files with help of the 'Import'-Button."
1315
+ ),
1316
+ _("Confirm"),
1317
+ wx.YES_NO | wx.CANCEL | wx.ICON_INFORMATION,
1318
+ )
1319
+ == wx.YES
1320
+ ):
1321
+ import webbrowser
1322
+
1323
+ webbrowser.open(url, new=0, autoraise=True)
1324
+ else:
1325
+ # This is a local directory with existing font-files,
1326
+ # e.g. the Windows-Font-Directory
1327
+ self.import_files(url, "ttf")
1328
+
1329
+ def import_files(self, import_directory, extension):
1330
+ source_files = os.listdir(import_directory)
1331
+ font_files = []
1332
+ for entry in source_files:
1333
+ if entry.lower().endswith(extension):
1334
+ font_files.append(os.path.join(import_directory, entry))
1335
+ stats = [0, 0] # Successful, errors
1336
+ if len(font_files) == 0:
1337
+ return
1338
+
1339
+ maxidx = len(font_files)
1340
+ progress_string = _("Fonts imported: {count}")
1341
+ progress = wx.ProgressDialog(
1342
+ _("Importing fonts..."),
1343
+ progress_string.format(count=0),
1344
+ maximum=maxidx,
1345
+ parent=None,
1346
+ style=wx.PD_APP_MODAL | wx.PD_CAN_ABORT,
1347
+ )
1348
+ for idx, sourcefile in enumerate(font_files):
1349
+ basename = os.path.basename(sourcefile)
1350
+ destfile = os.path.join(self.context.fonts.font_directory, basename)
1351
+ if os.path.exists(destfile):
1352
+ continue
1353
+ # print (f"Source File: {sourcefile}\nTarget: {destfile}")
1354
+ try:
1355
+ with open(sourcefile, "rb") as f, open(destfile, "wb") as g:
1356
+ while True:
1357
+ block = f.read(1 * 1024 * 1024) # work by blocks of 1 MB
1358
+ if not block: # end of file
1359
+ break
1360
+ g.write(block)
1361
+ bmp = self.context.fonts.preview_file(destfile)
1362
+ if bmp is not None:
1363
+ stats[0] += 1
1364
+ else:
1365
+ # We delete this file again...
1366
+ remove_fontfile(destfile)
1367
+ stats[1] += 1
1368
+
1369
+ progress.Update(idx + 1, progress_string.format(count=idx + 1))
1370
+ if progress.WasCancelled():
1371
+ break
1372
+ except (OSError, RuntimeError, PermissionError, FileNotFoundError):
1373
+ stats[1] += 1
1374
+ progress.Destroy()
1375
+ wx.MessageBox(
1376
+ _(
1377
+ "Font-Import completed.\nImported: {ok}\nFailed: {fail}\nTotal: {total}"
1378
+ ).format(ok=stats[0], fail=stats[1], total=stats[0] + stats[1]),
1379
+ _("Import completed"),
1380
+ wx.OK | wx.ICON_INFORMATION,
1381
+ )
1382
+ # Reload....
1383
+ self.on_text_directory(None)
1384
+
1385
+ def pane_hide(self):
1386
+ self.sysdirs.pane_hide()
1387
+
1388
+ # end of class FontManager
1389
+
1390
+
1391
+ class HersheyFontManager(MWindow):
1392
+ """
1393
+ Wrapper Window Class for Vector Font Manager
1394
+ """
1395
+
1396
+ def __init__(self, *args, **kwds):
1397
+ super().__init__(551, 234, submenu="", *args, **kwds)
1398
+ self.panel = PanelFontManager(self, wx.ID_ANY, context=self.context)
1399
+ self.sizer.Add(self.panel, 1, wx.EXPAND, 0)
1400
+ _icon = wx.NullIcon
1401
+ _icon.CopyFromBitmap(icons8_choose_font.GetBitmap())
1402
+ # _icon.CopyFromBitmap(icons8_computer_support.GetBitmap())
1403
+ self.SetIcon(_icon)
1404
+ self.SetTitle(_("Font-Manager"))
1405
+ self.Layout()
1406
+ self.restore_aspect()
1407
+
1408
+ def window_open(self):
1409
+ pass
1410
+
1411
+ def window_close(self):
1412
+ pass
1413
+
1414
+ def delegates(self):
1415
+ yield self.panel
1416
+
1417
+ @staticmethod
1418
+ def submenu():
1419
+ # suppress in tool-menu
1420
+ return "", "Font-Manager", True
1421
+
1422
+ @staticmethod
1423
+ def helptext():
1424
+ return _("Manage the fonts available to MeerK40t")
1425
+
1426
+
1427
+ def register_hershey_stuff(kernel):
1428
+ kernel.root.register("path_attributes/linetext", LineTextPropertyPanel)
1429
+ buttonsize = int(STD_ICON_SIZE)
1430
+ kernel.register(
1431
+ "button/config/HersheyFontManager",
1432
+ {
1433
+ "label": _("Font-Manager"),
1434
+ "icon": icons8_choose_font,
1435
+ "tip": _("Open the vector-font management window."),
1436
+ "help": "vectortext",
1437
+ "action": lambda v: kernel.console("window toggle HersheyFontManager\n"),
1438
+ "size": buttonsize,
1439
+ },
1440
+ )