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

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