meerk40t 0.9.3001__py2.py3-none-any.whl → 0.9.7020__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- meerk40t/__init__.py +1 -1
- meerk40t/balormk/balor_params.py +167 -167
- meerk40t/balormk/clone_loader.py +457 -457
- meerk40t/balormk/controller.py +1566 -1512
- meerk40t/balormk/cylindermod.py +64 -0
- meerk40t/balormk/device.py +966 -1959
- meerk40t/balormk/driver.py +778 -591
- meerk40t/balormk/galvo_commands.py +1194 -0
- meerk40t/balormk/gui/balorconfig.py +237 -111
- meerk40t/balormk/gui/balorcontroller.py +191 -184
- meerk40t/balormk/gui/baloroperationproperties.py +116 -115
- meerk40t/balormk/gui/corscene.py +845 -0
- meerk40t/balormk/gui/gui.py +179 -147
- meerk40t/balormk/livelightjob.py +466 -382
- meerk40t/balormk/mock_connection.py +131 -109
- meerk40t/balormk/plugin.py +133 -135
- meerk40t/balormk/usb_connection.py +306 -301
- meerk40t/camera/__init__.py +1 -1
- meerk40t/camera/camera.py +514 -397
- meerk40t/camera/gui/camerapanel.py +1241 -1095
- meerk40t/camera/gui/gui.py +58 -58
- meerk40t/camera/plugin.py +441 -399
- meerk40t/ch341/__init__.py +27 -27
- meerk40t/ch341/ch341device.py +628 -628
- meerk40t/ch341/libusb.py +595 -589
- meerk40t/ch341/mock.py +171 -171
- meerk40t/ch341/windriver.py +157 -157
- meerk40t/constants.py +13 -0
- meerk40t/core/__init__.py +1 -1
- meerk40t/core/bindalias.py +550 -539
- meerk40t/core/core.py +47 -47
- meerk40t/core/cutcode/cubiccut.py +73 -73
- meerk40t/core/cutcode/cutcode.py +315 -312
- meerk40t/core/cutcode/cutgroup.py +141 -137
- meerk40t/core/cutcode/cutobject.py +192 -185
- meerk40t/core/cutcode/dwellcut.py +37 -37
- meerk40t/core/cutcode/gotocut.py +29 -29
- meerk40t/core/cutcode/homecut.py +29 -29
- meerk40t/core/cutcode/inputcut.py +34 -34
- meerk40t/core/cutcode/linecut.py +33 -33
- meerk40t/core/cutcode/outputcut.py +34 -34
- meerk40t/core/cutcode/plotcut.py +335 -335
- meerk40t/core/cutcode/quadcut.py +61 -61
- meerk40t/core/cutcode/rastercut.py +168 -148
- meerk40t/core/cutcode/waitcut.py +34 -34
- meerk40t/core/cutplan.py +1843 -1316
- meerk40t/core/drivers.py +330 -329
- meerk40t/core/elements/align.py +801 -669
- meerk40t/core/elements/branches.py +1858 -1507
- meerk40t/core/elements/clipboard.py +229 -219
- meerk40t/core/elements/element_treeops.py +4595 -2837
- meerk40t/core/elements/element_types.py +125 -105
- meerk40t/core/elements/elements.py +4315 -3617
- meerk40t/core/elements/files.py +117 -64
- meerk40t/core/elements/geometry.py +473 -224
- meerk40t/core/elements/grid.py +467 -316
- meerk40t/core/elements/materials.py +158 -94
- meerk40t/core/elements/notes.py +50 -38
- meerk40t/core/elements/offset_clpr.py +934 -912
- meerk40t/core/elements/offset_mk.py +963 -955
- meerk40t/core/elements/penbox.py +339 -267
- meerk40t/core/elements/placements.py +300 -83
- meerk40t/core/elements/render.py +785 -687
- meerk40t/core/elements/shapes.py +2618 -2092
- meerk40t/core/elements/testcases.py +105 -0
- meerk40t/core/elements/trace.py +651 -563
- meerk40t/core/elements/tree_commands.py +415 -409
- meerk40t/core/elements/undo_redo.py +116 -58
- meerk40t/core/elements/wordlist.py +319 -200
- meerk40t/core/exceptions.py +9 -9
- meerk40t/core/laserjob.py +220 -220
- meerk40t/core/logging.py +63 -63
- meerk40t/core/node/blobnode.py +83 -86
- meerk40t/core/node/bootstrap.py +105 -103
- meerk40t/core/node/branch_elems.py +40 -31
- meerk40t/core/node/branch_ops.py +45 -38
- meerk40t/core/node/branch_regmark.py +48 -41
- meerk40t/core/node/cutnode.py +29 -32
- meerk40t/core/node/effect_hatch.py +375 -257
- meerk40t/core/node/effect_warp.py +398 -0
- meerk40t/core/node/effect_wobble.py +441 -309
- meerk40t/core/node/elem_ellipse.py +404 -309
- meerk40t/core/node/elem_image.py +1082 -801
- meerk40t/core/node/elem_line.py +358 -292
- meerk40t/core/node/elem_path.py +259 -201
- meerk40t/core/node/elem_point.py +129 -102
- meerk40t/core/node/elem_polyline.py +310 -246
- meerk40t/core/node/elem_rect.py +376 -286
- meerk40t/core/node/elem_text.py +445 -418
- meerk40t/core/node/filenode.py +59 -40
- meerk40t/core/node/groupnode.py +138 -74
- meerk40t/core/node/image_processed.py +777 -766
- meerk40t/core/node/image_raster.py +156 -113
- meerk40t/core/node/layernode.py +31 -31
- meerk40t/core/node/mixins.py +135 -107
- meerk40t/core/node/node.py +1427 -1304
- meerk40t/core/node/nutils.py +117 -114
- meerk40t/core/node/op_cut.py +463 -335
- meerk40t/core/node/op_dots.py +296 -251
- meerk40t/core/node/op_engrave.py +414 -311
- meerk40t/core/node/op_image.py +755 -369
- meerk40t/core/node/op_raster.py +787 -522
- meerk40t/core/node/place_current.py +37 -40
- meerk40t/core/node/place_point.py +329 -126
- meerk40t/core/node/refnode.py +58 -47
- meerk40t/core/node/rootnode.py +225 -219
- meerk40t/core/node/util_console.py +48 -48
- meerk40t/core/node/util_goto.py +84 -65
- meerk40t/core/node/util_home.py +61 -61
- meerk40t/core/node/util_input.py +102 -102
- meerk40t/core/node/util_output.py +102 -102
- meerk40t/core/node/util_wait.py +65 -65
- meerk40t/core/parameters.py +709 -707
- meerk40t/core/planner.py +875 -785
- meerk40t/core/plotplanner.py +656 -652
- meerk40t/core/space.py +120 -113
- meerk40t/core/spoolers.py +706 -705
- meerk40t/core/svg_io.py +1836 -1549
- meerk40t/core/treeop.py +534 -445
- meerk40t/core/undos.py +278 -124
- meerk40t/core/units.py +784 -680
- meerk40t/core/view.py +393 -322
- meerk40t/core/webhelp.py +62 -62
- meerk40t/core/wordlist.py +513 -504
- meerk40t/cylinder/cylinder.py +247 -0
- meerk40t/cylinder/gui/cylindersettings.py +41 -0
- meerk40t/cylinder/gui/gui.py +24 -0
- meerk40t/device/__init__.py +1 -1
- meerk40t/device/basedevice.py +322 -123
- meerk40t/device/devicechoices.py +50 -0
- meerk40t/device/dummydevice.py +163 -128
- meerk40t/device/gui/defaultactions.py +618 -602
- meerk40t/device/gui/effectspanel.py +114 -0
- meerk40t/device/gui/formatterpanel.py +253 -290
- meerk40t/device/gui/warningpanel.py +337 -260
- meerk40t/device/mixins.py +13 -13
- meerk40t/dxf/__init__.py +1 -1
- meerk40t/dxf/dxf_io.py +766 -554
- meerk40t/dxf/plugin.py +47 -35
- meerk40t/external_plugins.py +79 -79
- meerk40t/external_plugins_build.py +28 -28
- meerk40t/extra/cag.py +112 -116
- meerk40t/extra/coolant.py +403 -0
- meerk40t/extra/encode_detect.py +204 -0
- meerk40t/extra/ezd.py +1165 -1165
- meerk40t/extra/hershey.py +834 -340
- meerk40t/extra/imageactions.py +322 -316
- meerk40t/extra/inkscape.py +628 -622
- meerk40t/extra/lbrn.py +424 -424
- meerk40t/extra/outerworld.py +283 -0
- meerk40t/extra/param_functions.py +1542 -1556
- meerk40t/extra/potrace.py +257 -253
- meerk40t/extra/serial_exchange.py +118 -0
- meerk40t/extra/updater.py +602 -453
- meerk40t/extra/vectrace.py +147 -146
- meerk40t/extra/winsleep.py +83 -83
- meerk40t/extra/xcs_reader.py +597 -0
- meerk40t/fill/fills.py +781 -335
- meerk40t/fill/patternfill.py +1061 -1061
- meerk40t/fill/patterns.py +614 -567
- meerk40t/grbl/control.py +87 -87
- meerk40t/grbl/controller.py +990 -903
- meerk40t/grbl/device.py +1084 -768
- meerk40t/grbl/driver.py +989 -771
- meerk40t/grbl/emulator.py +532 -497
- meerk40t/grbl/gcodejob.py +783 -767
- meerk40t/grbl/gui/grblconfiguration.py +373 -298
- meerk40t/grbl/gui/grblcontroller.py +485 -271
- meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
- meerk40t/grbl/gui/grbloperationconfig.py +105 -0
- meerk40t/grbl/gui/gui.py +147 -116
- meerk40t/grbl/interpreter.py +44 -44
- meerk40t/grbl/loader.py +22 -22
- meerk40t/grbl/mock_connection.py +56 -56
- meerk40t/grbl/plugin.py +294 -264
- meerk40t/grbl/serial_connection.py +93 -88
- meerk40t/grbl/tcp_connection.py +81 -79
- meerk40t/grbl/ws_connection.py +112 -0
- meerk40t/gui/__init__.py +1 -1
- meerk40t/gui/about.py +2042 -296
- meerk40t/gui/alignment.py +1644 -1608
- meerk40t/gui/autoexec.py +199 -0
- meerk40t/gui/basicops.py +791 -670
- meerk40t/gui/bufferview.py +77 -71
- meerk40t/gui/busy.py +232 -133
- meerk40t/gui/choicepropertypanel.py +1662 -1469
- meerk40t/gui/consolepanel.py +706 -542
- meerk40t/gui/devicepanel.py +687 -581
- meerk40t/gui/dialogoptions.py +110 -107
- meerk40t/gui/executejob.py +316 -306
- meerk40t/gui/fonts.py +90 -90
- meerk40t/gui/functionwrapper.py +252 -0
- meerk40t/gui/gui_mixins.py +729 -0
- meerk40t/gui/guicolors.py +205 -182
- meerk40t/gui/help_assets/help_assets.py +218 -201
- meerk40t/gui/helper.py +154 -0
- meerk40t/gui/hersheymanager.py +1440 -846
- meerk40t/gui/icons.py +3422 -2747
- meerk40t/gui/imagesplitter.py +555 -508
- meerk40t/gui/keymap.py +354 -344
- meerk40t/gui/laserpanel.py +897 -806
- meerk40t/gui/laserrender.py +1470 -1232
- meerk40t/gui/lasertoolpanel.py +805 -793
- meerk40t/gui/magnetoptions.py +436 -0
- meerk40t/gui/materialmanager.py +2944 -0
- meerk40t/gui/materialtest.py +1722 -1694
- meerk40t/gui/mkdebug.py +646 -359
- meerk40t/gui/mwindow.py +163 -140
- meerk40t/gui/navigationpanels.py +2605 -2467
- meerk40t/gui/notes.py +143 -142
- meerk40t/gui/opassignment.py +414 -410
- meerk40t/gui/operation_info.py +310 -299
- meerk40t/gui/plugin.py +500 -328
- meerk40t/gui/position.py +714 -669
- meerk40t/gui/preferences.py +901 -650
- meerk40t/gui/propertypanels/attributes.py +1461 -1131
- meerk40t/gui/propertypanels/blobproperty.py +117 -114
- meerk40t/gui/propertypanels/consoleproperty.py +83 -80
- meerk40t/gui/propertypanels/gotoproperty.py +77 -0
- meerk40t/gui/propertypanels/groupproperties.py +223 -217
- meerk40t/gui/propertypanels/hatchproperty.py +489 -469
- meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
- meerk40t/gui/propertypanels/inputproperty.py +59 -58
- meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
- meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
- meerk40t/gui/propertypanels/outputproperty.py +59 -58
- meerk40t/gui/propertypanels/pathproperty.py +389 -380
- meerk40t/gui/propertypanels/placementproperty.py +1214 -383
- meerk40t/gui/propertypanels/pointproperty.py +140 -136
- meerk40t/gui/propertypanels/propertywindow.py +313 -181
- meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
- meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
- meerk40t/gui/propertypanels/textproperty.py +770 -755
- meerk40t/gui/propertypanels/waitproperty.py +56 -55
- meerk40t/gui/propertypanels/warpproperty.py +121 -0
- meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
- meerk40t/gui/ribbon.py +2471 -2210
- meerk40t/gui/scene/scene.py +1100 -1051
- meerk40t/gui/scene/sceneconst.py +22 -22
- meerk40t/gui/scene/scenepanel.py +439 -349
- meerk40t/gui/scene/scenespacewidget.py +365 -365
- meerk40t/gui/scene/widget.py +518 -505
- meerk40t/gui/scenewidgets/affinemover.py +215 -215
- meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
- meerk40t/gui/scenewidgets/bedwidget.py +120 -97
- meerk40t/gui/scenewidgets/elementswidget.py +137 -107
- meerk40t/gui/scenewidgets/gridwidget.py +785 -745
- meerk40t/gui/scenewidgets/guidewidget.py +765 -765
- meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
- meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
- meerk40t/gui/scenewidgets/nodeselector.py +28 -28
- meerk40t/gui/scenewidgets/rectselectwidget.py +592 -346
- meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
- meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
- meerk40t/gui/scenewidgets/selectionwidget.py +2958 -2756
- meerk40t/gui/simpleui.py +362 -333
- meerk40t/gui/simulation.py +2451 -2094
- meerk40t/gui/snapoptions.py +208 -203
- meerk40t/gui/spoolerpanel.py +1227 -1180
- meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
- meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
- meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
- meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
- meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
- meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
- meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
- meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
- meerk40t/gui/themes.py +200 -78
- meerk40t/gui/tips.py +590 -0
- meerk40t/gui/toolwidgets/circlebrush.py +35 -35
- meerk40t/gui/toolwidgets/toolcircle.py +248 -242
- meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
- meerk40t/gui/toolwidgets/tooldraw.py +97 -90
- meerk40t/gui/toolwidgets/toolellipse.py +219 -212
- meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
- meerk40t/gui/toolwidgets/toolline.py +39 -144
- meerk40t/gui/toolwidgets/toollinetext.py +79 -236
- meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
- meerk40t/gui/toolwidgets/toolmeasure.py +163 -216
- meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
- meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
- meerk40t/gui/toolwidgets/toolparameter.py +754 -668
- meerk40t/gui/toolwidgets/toolplacement.py +108 -108
- meerk40t/gui/toolwidgets/toolpoint.py +68 -59
- meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
- meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
- meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
- meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
- meerk40t/gui/toolwidgets/toolrect.py +211 -207
- meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
- meerk40t/gui/toolwidgets/toolribbon.py +598 -113
- meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
- meerk40t/gui/toolwidgets/tooltext.py +98 -89
- meerk40t/gui/toolwidgets/toolvector.py +213 -204
- meerk40t/gui/toolwidgets/toolwidget.py +39 -39
- meerk40t/gui/usbconnect.py +98 -91
- meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
- meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
- meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
- meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
- meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
- meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
- meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
- meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
- meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
- meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
- meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
- meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
- meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
- meerk40t/gui/wordlisteditor.py +985 -931
- meerk40t/gui/wxmeerk40t.py +1447 -1169
- meerk40t/gui/wxmmain.py +5644 -4112
- meerk40t/gui/wxmribbon.py +1591 -1076
- meerk40t/gui/wxmscene.py +1631 -1453
- meerk40t/gui/wxmtree.py +2416 -2089
- meerk40t/gui/wxutils.py +1769 -1099
- meerk40t/gui/zmatrix.py +102 -102
- meerk40t/image/__init__.py +1 -1
- meerk40t/image/dither.py +429 -0
- meerk40t/image/imagetools.py +2793 -2269
- meerk40t/internal_plugins.py +150 -130
- meerk40t/kernel/__init__.py +63 -12
- meerk40t/kernel/channel.py +259 -212
- meerk40t/kernel/context.py +538 -538
- meerk40t/kernel/exceptions.py +41 -41
- meerk40t/kernel/functions.py +463 -414
- meerk40t/kernel/jobs.py +100 -100
- meerk40t/kernel/kernel.py +3828 -3571
- meerk40t/kernel/lifecycles.py +71 -71
- meerk40t/kernel/module.py +49 -49
- meerk40t/kernel/service.py +147 -147
- meerk40t/kernel/settings.py +383 -343
- meerk40t/lihuiyu/controller.py +883 -876
- meerk40t/lihuiyu/device.py +1181 -1069
- meerk40t/lihuiyu/driver.py +1466 -1372
- meerk40t/lihuiyu/gui/gui.py +127 -106
- meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
- meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
- meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
- meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
- meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
- meerk40t/lihuiyu/interpreter.py +53 -53
- meerk40t/lihuiyu/laserspeed.py +450 -450
- meerk40t/lihuiyu/loader.py +90 -90
- meerk40t/lihuiyu/parser.py +404 -404
- meerk40t/lihuiyu/plugin.py +101 -102
- meerk40t/lihuiyu/tcp_connection.py +111 -109
- meerk40t/main.py +231 -165
- meerk40t/moshi/builder.py +788 -781
- meerk40t/moshi/controller.py +505 -499
- meerk40t/moshi/device.py +495 -442
- meerk40t/moshi/driver.py +862 -696
- meerk40t/moshi/gui/gui.py +78 -76
- meerk40t/moshi/gui/moshicontrollergui.py +538 -522
- meerk40t/moshi/gui/moshidrivergui.py +87 -75
- meerk40t/moshi/plugin.py +43 -43
- meerk40t/network/console_server.py +140 -57
- meerk40t/network/kernelserver.py +10 -9
- meerk40t/network/tcp_server.py +142 -140
- meerk40t/network/udp_server.py +103 -77
- meerk40t/network/web_server.py +404 -0
- meerk40t/newly/controller.py +1158 -1144
- meerk40t/newly/device.py +874 -732
- meerk40t/newly/driver.py +540 -412
- meerk40t/newly/gui/gui.py +219 -188
- meerk40t/newly/gui/newlyconfig.py +116 -101
- meerk40t/newly/gui/newlycontroller.py +193 -186
- meerk40t/newly/gui/operationproperties.py +51 -51
- meerk40t/newly/mock_connection.py +82 -82
- meerk40t/newly/newly_params.py +56 -56
- meerk40t/newly/plugin.py +1214 -1246
- meerk40t/newly/usb_connection.py +322 -322
- meerk40t/rotary/gui/gui.py +52 -46
- meerk40t/rotary/gui/rotarysettings.py +240 -232
- meerk40t/rotary/rotary.py +202 -98
- meerk40t/ruida/control.py +291 -91
- meerk40t/ruida/controller.py +138 -1088
- meerk40t/ruida/device.py +676 -231
- meerk40t/ruida/driver.py +534 -472
- meerk40t/ruida/emulator.py +1494 -1491
- meerk40t/ruida/exceptions.py +4 -4
- meerk40t/ruida/gui/gui.py +71 -76
- meerk40t/ruida/gui/ruidaconfig.py +239 -72
- meerk40t/ruida/gui/ruidacontroller.py +187 -184
- meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
- meerk40t/ruida/loader.py +54 -52
- meerk40t/ruida/mock_connection.py +57 -109
- meerk40t/ruida/plugin.py +124 -87
- meerk40t/ruida/rdjob.py +2084 -945
- meerk40t/ruida/serial_connection.py +116 -0
- meerk40t/ruida/tcp_connection.py +146 -0
- meerk40t/ruida/udp_connection.py +73 -0
- meerk40t/svgelements.py +9671 -9669
- meerk40t/tools/driver_to_path.py +584 -579
- meerk40t/tools/geomstr.py +5583 -4680
- meerk40t/tools/jhfparser.py +357 -292
- meerk40t/tools/kerftest.py +904 -890
- meerk40t/tools/livinghinges.py +1168 -1033
- meerk40t/tools/pathtools.py +987 -949
- meerk40t/tools/pmatrix.py +234 -0
- meerk40t/tools/pointfinder.py +942 -942
- meerk40t/tools/polybool.py +941 -940
- meerk40t/tools/rasterplotter.py +1660 -547
- meerk40t/tools/shxparser.py +1047 -901
- meerk40t/tools/ttfparser.py +726 -446
- meerk40t/tools/zinglplotter.py +595 -593
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/LICENSE +21 -21
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/METADATA +150 -139
- meerk40t-0.9.7020.dist-info/RECORD +446 -0
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/WHEEL +1 -1
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/top_level.txt +0 -1
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/zip-safe +1 -1
- meerk40t/balormk/elementlightjob.py +0 -159
- meerk40t-0.9.3001.dist-info/RECORD +0 -437
- test/bootstrap.py +0 -63
- test/test_cli.py +0 -12
- test/test_core_cutcode.py +0 -418
- test/test_core_elements.py +0 -144
- test/test_core_plotplanner.py +0 -397
- test/test_core_viewports.py +0 -312
- test/test_drivers_grbl.py +0 -108
- test/test_drivers_lihuiyu.py +0 -443
- test/test_drivers_newly.py +0 -113
- test/test_element_degenerate_points.py +0 -43
- test/test_elements_classify.py +0 -97
- test/test_elements_penbox.py +0 -22
- test/test_file_svg.py +0 -176
- test/test_fill.py +0 -155
- test/test_geomstr.py +0 -1523
- test/test_geomstr_nodes.py +0 -18
- test/test_imagetools_actualize.py +0 -306
- test/test_imagetools_wizard.py +0 -258
- test/test_kernel.py +0 -200
- test/test_laser_speeds.py +0 -3303
- test/test_length.py +0 -57
- test/test_lifecycle.py +0 -66
- test/test_operations.py +0 -251
- test/test_operations_hatch.py +0 -57
- test/test_ruida.py +0 -19
- test/test_spooler.py +0 -22
- test/test_tools_rasterplotter.py +0 -29
- test/test_wobble.py +0 -133
- test/test_zingl.py +0 -124
- {test → meerk40t/cylinder}/__init__.py +0 -0
- /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/entry_points.txt +0 -0
meerk40t/core/node/elem_image.py
CHANGED
@@ -1,801 +1,1082 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
self.
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
self.
|
95
|
-
|
96
|
-
self.
|
97
|
-
|
98
|
-
self.
|
99
|
-
|
100
|
-
if self.operations
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
if
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
self.
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
self.
|
195
|
-
self.
|
196
|
-
self.
|
197
|
-
|
198
|
-
self.
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
return
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
"""
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
)
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
""
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
#
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
except
|
540
|
-
pass
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
"""
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
1
|
+
"""
|
2
|
+
ImageNode is the bootstrapped node type for handling image elements within the application.
|
3
|
+
|
4
|
+
This class manages the properties and behaviors associated with image nodes, including
|
5
|
+
image processing, transformations, and keyhole functionalities. It supports various
|
6
|
+
operations such as cropping, dither effects, and applying raster scripts, while also
|
7
|
+
maintaining the necessary metadata for rendering and manipulation.
|
8
|
+
|
9
|
+
Args:
|
10
|
+
**kwargs: Additional keyword arguments for node initialization.
|
11
|
+
|
12
|
+
Attributes:
|
13
|
+
image: The original image loaded into the node.
|
14
|
+
matrix: The transformation matrix applied to the image.
|
15
|
+
dpi: The resolution of the image in dots per inch.
|
16
|
+
operations: A list of operations to be applied to the image.
|
17
|
+
keyhole_reference: Reference for keyhole operations.
|
18
|
+
active_image: The processed image ready for rendering.
|
19
|
+
active_matrix: The matrix that combines the main matrix with the processed matrix.
|
20
|
+
convex_hull: The convex hull of the non-white pixels in the image.
|
21
|
+
|
22
|
+
Methods:
|
23
|
+
set_keyhole(keyhole_ref, geom=None): Sets the keyhole reference and geometry.
|
24
|
+
process_image(step_x=None, step_y=None, crop=True): Processes the image based on the specified steps and cropping options.
|
25
|
+
update(context): Initiates the image processing thread and updates the image.
|
26
|
+
as_image(): Returns the active image and its bounding box.
|
27
|
+
bbox(transformed=True, with_stroke=False): Returns the bounding box of the image.
|
28
|
+
"""
|
29
|
+
|
30
|
+
import numpy as np
|
31
|
+
import threading
|
32
|
+
import time
|
33
|
+
from copy import copy
|
34
|
+
from math import ceil, floor
|
35
|
+
|
36
|
+
from meerk40t.core.node.node import Node
|
37
|
+
from meerk40t.core.node.mixins import LabelDisplay, Suppressable
|
38
|
+
from meerk40t.core.units import UNITS_PER_INCH, UNITS_PER_MM
|
39
|
+
from meerk40t.image.imagetools import RasterScripts
|
40
|
+
from meerk40t.svgelements import Matrix, Path, Polygon
|
41
|
+
from meerk40t.tools.geomstr import Geomstr
|
42
|
+
|
43
|
+
class ImageNode(Node, LabelDisplay, Suppressable):
|
44
|
+
"""
|
45
|
+
ImageNode is the bootstrapped node type for the 'elem image' type.
|
46
|
+
|
47
|
+
ImageNode contains a main matrix, main image. A processed image and a processed matrix.
|
48
|
+
The processed matrix must be concatenated with the main matrix to be accurate.
|
49
|
+
"""
|
50
|
+
|
51
|
+
def __init__(self, **kwargs):
|
52
|
+
self.image = None
|
53
|
+
self.matrix = None
|
54
|
+
self.overscan = None
|
55
|
+
self.direction = None
|
56
|
+
self.dpi = 500
|
57
|
+
self.operations = []
|
58
|
+
self.invert = None
|
59
|
+
self.dither = True
|
60
|
+
self.dither_type = "Floyd-Steinberg"
|
61
|
+
self.red = 1.0
|
62
|
+
self.green = 1.0
|
63
|
+
self.blue = 1.0
|
64
|
+
self.lightness = 1.0
|
65
|
+
self.view_invert = False
|
66
|
+
self.prevent_crop = False
|
67
|
+
self.is_depthmap = False
|
68
|
+
self.depth_resolution = 256
|
69
|
+
# Keyhole-Variables
|
70
|
+
self._keyhole_reference = None
|
71
|
+
self._keyhole_geometry = None
|
72
|
+
self._keyhole_image = None
|
73
|
+
self._processing = False
|
74
|
+
self._convex_hull = None
|
75
|
+
self._default_units = UNITS_PER_INCH
|
76
|
+
startup = True
|
77
|
+
if "comingfromcopy" in kwargs:
|
78
|
+
startup = False
|
79
|
+
del kwargs["comingfromcopy"]
|
80
|
+
|
81
|
+
self.passthrough = False
|
82
|
+
super().__init__(type="elem image", **kwargs)
|
83
|
+
if "hidden" in kwargs:
|
84
|
+
if isinstance(kwargs["hidden"], str):
|
85
|
+
if kwargs["hidden"].lower() == "true":
|
86
|
+
kwargs["hidden"] = True
|
87
|
+
else:
|
88
|
+
kwargs["hidden"] = False
|
89
|
+
self.hidden = kwargs["hidden"]
|
90
|
+
# kwargs can actually reset quite a lot of the properties to none
|
91
|
+
# so, we need to revert these changes...
|
92
|
+
if self.red is None:
|
93
|
+
self.red = 1.0
|
94
|
+
if self.green is None:
|
95
|
+
self.green = 1.0
|
96
|
+
if self.blue is None:
|
97
|
+
self.blue = 1.0
|
98
|
+
if self.lightness is None:
|
99
|
+
self.lightness = 1.0
|
100
|
+
if self.operations is None:
|
101
|
+
self.operations = list()
|
102
|
+
if self.dither_type is None:
|
103
|
+
self.dither_type = "Floyd-Steinberg"
|
104
|
+
|
105
|
+
self.__formatter = "{element_type} {id} {width}x{height}"
|
106
|
+
if self.matrix is None:
|
107
|
+
self.matrix = Matrix()
|
108
|
+
if hasattr(self, "href"):
|
109
|
+
try:
|
110
|
+
from PIL import Image as PILImage
|
111
|
+
|
112
|
+
if not isinstance(self.href, str):
|
113
|
+
# Error caused by href being int value
|
114
|
+
raise ImportError
|
115
|
+
|
116
|
+
self.image = PILImage.open(self.href)
|
117
|
+
if hasattr(self, "x"):
|
118
|
+
self.matrix.post_translate_x(self.x)
|
119
|
+
delattr(self, "x")
|
120
|
+
if hasattr(self, "y"):
|
121
|
+
self.matrix.post_translate_x(self.y)
|
122
|
+
delattr(self, "y")
|
123
|
+
real_width, real_height = self.image.size
|
124
|
+
declared_width, declared_height = real_width, real_height
|
125
|
+
if hasattr(self, "width"):
|
126
|
+
declared_width = self.width
|
127
|
+
delattr(self, "width")
|
128
|
+
|
129
|
+
if hasattr(self, "height"):
|
130
|
+
declared_height = self.height
|
131
|
+
delattr(self, "height")
|
132
|
+
try:
|
133
|
+
sx = declared_width / real_width
|
134
|
+
sy = declared_height / real_height
|
135
|
+
self.matrix.post_scale(sx, sy)
|
136
|
+
except ZeroDivisionError:
|
137
|
+
pass
|
138
|
+
delattr(self, "href")
|
139
|
+
|
140
|
+
except ImportError:
|
141
|
+
self.image = None
|
142
|
+
|
143
|
+
# Step_x/y is the step factor of the image, the reciprocal of the DPI.
|
144
|
+
self.step_x = None
|
145
|
+
self.step_y = None
|
146
|
+
|
147
|
+
self._needs_update = False
|
148
|
+
self._update_thread = None
|
149
|
+
self._update_lock = threading.Lock()
|
150
|
+
self._processed_image = None
|
151
|
+
self._processed_matrix = None
|
152
|
+
self._actualized_matrix = None
|
153
|
+
self._process_image_failed = False
|
154
|
+
|
155
|
+
self.message = None
|
156
|
+
if (self.operations or self.dither or self.prevent_crop or self.keyhole_reference) and startup:
|
157
|
+
step = self._default_units / self.dpi
|
158
|
+
step_x = step
|
159
|
+
step_y = step
|
160
|
+
self.process_image(step_x, step_y, not self.prevent_crop)
|
161
|
+
|
162
|
+
def __copy__(self):
|
163
|
+
nd = self.node_dict
|
164
|
+
nd["matrix"] = copy(self.matrix)
|
165
|
+
nd["operations"] = copy(self.operations)
|
166
|
+
nd["comingfromcopy"] = True
|
167
|
+
newnode = ImageNode(**nd)
|
168
|
+
if self._processed_image is not None:
|
169
|
+
newnode._processed_image = copy(self._processed_image)
|
170
|
+
newnode._processed_matrix = copy(self._processed_matrix)
|
171
|
+
newnode._actualized_matrix = copy(self._actualized_matrix)
|
172
|
+
g = None if self._keyhole_geometry is None else copy(self._keyhole_geometry)
|
173
|
+
newnode.set_keyhole(self.keyhole_reference, g)
|
174
|
+
return newnode
|
175
|
+
|
176
|
+
def __repr__(self):
|
177
|
+
return f"{self.__class__.__name__}('{self.type}', {str(self.image)}, {str(self._parent)})"
|
178
|
+
|
179
|
+
@property
|
180
|
+
def active_image(self):
|
181
|
+
# This may be called too quick, so the image is still processing.
|
182
|
+
# This would cause an immediate recalculation which would make
|
183
|
+
# things even worse, we wait max 1 second
|
184
|
+
counter = 0
|
185
|
+
while self._processing and counter < 20:
|
186
|
+
time.sleep(0.05)
|
187
|
+
counter += 1
|
188
|
+
if self._processed_image is None:
|
189
|
+
|
190
|
+
step = self._default_units / self.dpi
|
191
|
+
step_x = step
|
192
|
+
step_y = step
|
193
|
+
self.process_image(step_x, step_y, not self.prevent_crop)
|
194
|
+
return self._apply_keyhole()
|
195
|
+
# if self._processed_image is not None:
|
196
|
+
# return self._processed_image
|
197
|
+
# else:
|
198
|
+
# return self.image
|
199
|
+
|
200
|
+
@property
|
201
|
+
def active_matrix(self):
|
202
|
+
if self._processed_matrix is None:
|
203
|
+
return self.matrix
|
204
|
+
return self._processed_matrix * self.matrix
|
205
|
+
|
206
|
+
@property
|
207
|
+
def keyhole_reference(self):
|
208
|
+
return self._keyhole_reference
|
209
|
+
|
210
|
+
@keyhole_reference.setter
|
211
|
+
def keyhole_reference(self, value):
|
212
|
+
self._keyhole_reference = value
|
213
|
+
self._keyhole_geometry = None
|
214
|
+
self._bounds_dirty = True
|
215
|
+
|
216
|
+
def set_keyhole(self, keyhole_ref, geom=None):
|
217
|
+
# This is useful if we do want to set it after loading a file
|
218
|
+
# or when assigning the reference, as this does not need a context
|
219
|
+
# query the complete node tree
|
220
|
+
self._cache = None
|
221
|
+
self.keyhole_reference = keyhole_ref
|
222
|
+
self._keyhole_geometry = geom
|
223
|
+
self._keyhole_image = None
|
224
|
+
|
225
|
+
def convex_hull(self) -> Geomstr:
|
226
|
+
if self._convex_hull is not None:
|
227
|
+
return self._convex_hull
|
228
|
+
t0 = time.perf_counter()
|
229
|
+
image_np = np.array(self.active_image.convert("L"))
|
230
|
+
# print (image_np)
|
231
|
+
# Find non-white pixels
|
232
|
+
# Iterate over each row in the image
|
233
|
+
left_side = []
|
234
|
+
right_side = []
|
235
|
+
for y in range(image_np.shape[0]):
|
236
|
+
row = image_np[y]
|
237
|
+
non_white_indices = np.where(row < 255)[0]
|
238
|
+
|
239
|
+
if non_white_indices.size > 0:
|
240
|
+
leftmost = non_white_indices[0]
|
241
|
+
rightmost = non_white_indices[-1]
|
242
|
+
left_side.append((leftmost, y))
|
243
|
+
right_side.insert(0, (rightmost, y))
|
244
|
+
left_side.extend(right_side)
|
245
|
+
non_white_pixels = left_side
|
246
|
+
t1 = time.perf_counter()
|
247
|
+
# Compute the convex hull
|
248
|
+
"""
|
249
|
+
After the introduction of the quickhull routine in geomstr
|
250
|
+
the ConvexHull routine from scipy provides only limited
|
251
|
+
advantages over our own routine
|
252
|
+
|
253
|
+
pts = None
|
254
|
+
try:
|
255
|
+
# The ConvexHull routine from scipy provides less points
|
256
|
+
# and is faster (plus it has fewer non-understood artifacts)
|
257
|
+
from scipy.spatial import ConvexHull
|
258
|
+
c_points = np.array(non_white_pixels)
|
259
|
+
hull = ConvexHull(c_points)
|
260
|
+
hpts = c_points[hull.vertices]
|
261
|
+
pts = list( ( p[0], p[1] ) for p in hpts)
|
262
|
+
# print (f"scipy Hull has {len(pts)} pts")
|
263
|
+
except ImportError:
|
264
|
+
pass
|
265
|
+
t1b = time.perf_counter()
|
266
|
+
if pts is None:
|
267
|
+
"""
|
268
|
+
pts = list(Geomstr.convex_hull(None, non_white_pixels))
|
269
|
+
if pts:
|
270
|
+
pts.append(pts[0])
|
271
|
+
# print("convex hull done")
|
272
|
+
t2 = time.perf_counter()
|
273
|
+
# print (f"Hull has {len(pts)} pts")
|
274
|
+
self._convex_hull = Geomstr.lines(*pts)
|
275
|
+
# print (f"Hull dimension: {self._convex_hull.bbox()} (for reference: image is {self.active_image.width}x{self.active_image.height} pixels)")
|
276
|
+
self._convex_hull.transform(self.active_matrix)
|
277
|
+
# print (f"Final dimension: {self._convex_hull.bbox()}")
|
278
|
+
t3 = time.perf_counter()
|
279
|
+
# print (f"Time to get pixels: {t1-t0:.3f}s, geomstr: {t2-t1b:.3f}s, scipy: {t1b-t1:.3f}s, total: {t3-t0:.3f}s")
|
280
|
+
return self._convex_hull
|
281
|
+
|
282
|
+
def preprocess(self, context, matrix, plan):
|
283
|
+
"""
|
284
|
+
Preprocess step during the cut planning stages.
|
285
|
+
|
286
|
+
We require a context to calculate the correct step values relative to the device
|
287
|
+
"""
|
288
|
+
|
289
|
+
dev_x, dev_y = context.device.view.dpi_to_steps(1)
|
290
|
+
self._default_units = (dev_x + dev_y) / 2
|
291
|
+
self.step_x, self.step_y = context.device.view.dpi_to_steps(self.dpi)
|
292
|
+
self.matrix *= matrix
|
293
|
+
self.set_dirty_bounds()
|
294
|
+
self.process_image(self.step_x, self.step_y, not self.prevent_crop)
|
295
|
+
|
296
|
+
def as_image(self):
|
297
|
+
return self.active_image, self.bbox()
|
298
|
+
|
299
|
+
def bbox(self, transformed=True, with_stroke=False):
|
300
|
+
image_width, image_height = self.active_image.size
|
301
|
+
matrix = self.active_matrix
|
302
|
+
x0, y0 = matrix.point_in_matrix_space((0, 0))
|
303
|
+
x1, y1 = matrix.point_in_matrix_space((image_width, image_height))
|
304
|
+
x2, y2 = matrix.point_in_matrix_space((0, image_height))
|
305
|
+
x3, y3 = matrix.point_in_matrix_space((image_width, 0))
|
306
|
+
return (
|
307
|
+
min(x0, x1, x2, x3),
|
308
|
+
min(y0, y1, y2, y3),
|
309
|
+
max(x0, x1, x2, x3),
|
310
|
+
max(y0, y1, y2, y3),
|
311
|
+
)
|
312
|
+
|
313
|
+
def default_map(self, default_map=None):
|
314
|
+
default_map = super().default_map(default_map=default_map)
|
315
|
+
default_map.update(self.__dict__)
|
316
|
+
image = self.active_image
|
317
|
+
default_map["width"] = image.width
|
318
|
+
default_map["height"] = image.height
|
319
|
+
default_map["element_type"] = "Image"
|
320
|
+
return default_map
|
321
|
+
|
322
|
+
def can_drop(self, drag_node):
|
323
|
+
# Dragging element into element.
|
324
|
+
return bool(
|
325
|
+
hasattr(drag_node, "as_geometry") or
|
326
|
+
hasattr(drag_node, "as_image") or
|
327
|
+
drag_node.type in ("op image", "op raster", "file", "group")
|
328
|
+
)
|
329
|
+
|
330
|
+
def drop(self, drag_node, modify=True, flag=False):
|
331
|
+
# Dragging element into element.
|
332
|
+
if not self.can_drop(drag_node):
|
333
|
+
return False
|
334
|
+
if hasattr(drag_node, "as_geometry") or hasattr(drag_node, "as_image") or drag_node.type in ("file", "group"):
|
335
|
+
if modify:
|
336
|
+
self.insert_sibling(drag_node)
|
337
|
+
return True
|
338
|
+
elif drag_node.type.startswith("op"):
|
339
|
+
# If we drag an operation to this node,
|
340
|
+
# then we will reverse the game
|
341
|
+
old_references = list(self._references)
|
342
|
+
result = drag_node.drop(self, modify=modify, flag=flag)
|
343
|
+
if result and modify:
|
344
|
+
for ref in old_references:
|
345
|
+
ref.remove_node()
|
346
|
+
return result
|
347
|
+
return False
|
348
|
+
|
349
|
+
def revalidate_points(self):
|
350
|
+
bounds = self.bounds
|
351
|
+
if bounds is None:
|
352
|
+
return
|
353
|
+
if len(self._points) < 9:
|
354
|
+
self._points.extend([None] * (9 - len(self._points)))
|
355
|
+
self._points[0] = [bounds[0], bounds[1], "bounds top_left"]
|
356
|
+
self._points[1] = [bounds[2], bounds[1], "bounds top_right"]
|
357
|
+
self._points[2] = [bounds[0], bounds[3], "bounds bottom_left"]
|
358
|
+
self._points[3] = [bounds[2], bounds[3], "bounds bottom_right"]
|
359
|
+
cx = (bounds[0] + bounds[2]) / 2
|
360
|
+
cy = (bounds[1] + bounds[3]) / 2
|
361
|
+
self._points[4] = [cx, cy, "bounds center_center"]
|
362
|
+
self._points[5] = [cx, bounds[1], "bounds top_center"]
|
363
|
+
self._points[6] = [cx, bounds[3], "bounds bottom_center"]
|
364
|
+
self._points[7] = [bounds[0], cy, "bounds center_left"]
|
365
|
+
self._points[8] = [bounds[2], cy, "bounds center_right"]
|
366
|
+
|
367
|
+
def update_point(self, index, point):
|
368
|
+
return False
|
369
|
+
|
370
|
+
def add_point(self, point, index=None):
|
371
|
+
return False
|
372
|
+
|
373
|
+
def update(self, context):
|
374
|
+
"""
|
375
|
+
Update kicks off the image processing thread, which performs RasterWizard script operations on the image node.
|
376
|
+
|
377
|
+
The text should be displayed in the scene by the renderer. And any additional changes will be processed
|
378
|
+
until the new processed image is completed.
|
379
|
+
|
380
|
+
@param context:
|
381
|
+
@return:
|
382
|
+
"""
|
383
|
+
self._needs_update = True
|
384
|
+
if context is not None:
|
385
|
+
self.message = "Processing..."
|
386
|
+
context.signal("refresh_scene", "Scene")
|
387
|
+
if self._update_thread is None:
|
388
|
+
|
389
|
+
def clear(result):
|
390
|
+
self._needs_update = False
|
391
|
+
self._update_thread = None
|
392
|
+
if context is not None:
|
393
|
+
if self._process_image_failed:
|
394
|
+
self.message = "Process image could not exist in memory."
|
395
|
+
else:
|
396
|
+
self.message = None
|
397
|
+
context.signal("refresh_scene", "Scene")
|
398
|
+
context.signal("image_updated", self)
|
399
|
+
|
400
|
+
def get_keyhole_geometry():
|
401
|
+
self._keyhole_geometry = None
|
402
|
+
self._keyhole_image = None
|
403
|
+
refnode = context.elements.find_node(self.id)
|
404
|
+
if refnode is not None and hasattr(refnode, "as_geometry"):
|
405
|
+
self._keyhole_geometry = refnode.as_geometry()
|
406
|
+
|
407
|
+
self._processed_image = None
|
408
|
+
self._convex_hull = None
|
409
|
+
# self.processed_matrix = None
|
410
|
+
if context is None:
|
411
|
+
# Direct execution
|
412
|
+
self._needs_update = False
|
413
|
+
# Calculate scene step_x, step_y values
|
414
|
+
step = self._default_units / self.dpi
|
415
|
+
step_x = step
|
416
|
+
step_y = step
|
417
|
+
self.process_image(step_x, step_y, not self.prevent_crop)
|
418
|
+
# Unset cache.
|
419
|
+
self._cache = None
|
420
|
+
else:
|
421
|
+
if self._keyhole_reference is not None and self._keyhole_geometry is None:
|
422
|
+
get_keyhole_geometry()
|
423
|
+
|
424
|
+
# We need to have a thread per image, so we need to provide a node specific thread_name!
|
425
|
+
self._update_thread = context.threaded(
|
426
|
+
self._process_image_thread, result=clear, daemon=True, thread_name=f"image_update_{self.id}_{str(time.perf_counter())}"
|
427
|
+
)
|
428
|
+
|
429
|
+
def _process_image_thread(self):
|
430
|
+
"""
|
431
|
+
The function deletes the caches and processes the image until it no longer needs updating.
|
432
|
+
|
433
|
+
@return:
|
434
|
+
"""
|
435
|
+
while self._needs_update:
|
436
|
+
self._needs_update = False
|
437
|
+
# Calculate scene step_x, step_y values
|
438
|
+
step = self._default_units / self.dpi
|
439
|
+
step_x = step
|
440
|
+
step_y = step
|
441
|
+
with self._update_lock:
|
442
|
+
self.process_image(step_x, step_y, not self.prevent_crop)
|
443
|
+
# Unset cache.
|
444
|
+
self._cache = None
|
445
|
+
|
446
|
+
def process_image(self, step_x=None, step_y=None, crop=True):
|
447
|
+
"""
|
448
|
+
SVG matrices are defined as follows.
|
449
|
+
[a c e]
|
450
|
+
[b d f]
|
451
|
+
|
452
|
+
Pil requires a, c, e, b, d, f accordingly.
|
453
|
+
|
454
|
+
As of 0.7.2 this converts the image to "L" as part of the process.
|
455
|
+
|
456
|
+
There is a small amount of slop at the edge of converted images sometimes, so it's essential
|
457
|
+
to mark the image as inverted if black should be treated as empty pixels. The scaled down image
|
458
|
+
not lose the edge pixels since they could be important, but also dim may not be a multiple
|
459
|
+
of step level which requires an introduced empty edge pixel to be added.
|
460
|
+
"""
|
461
|
+
|
462
|
+
from PIL import Image, ImageDraw
|
463
|
+
while self._processing:
|
464
|
+
time.sleep(0.05)
|
465
|
+
|
466
|
+
if step_x is None:
|
467
|
+
step_x = self.step_x
|
468
|
+
if step_y is None:
|
469
|
+
step_y = self.step_y
|
470
|
+
# print (f"process called with step_x={step_x}, step_y={step_y} (node: {self.step_x}, {self.step_y})")
|
471
|
+
try:
|
472
|
+
actualized_matrix, image = self._process_image(step_x, step_y, crop=crop)
|
473
|
+
inverted_main_matrix = Matrix(self.matrix).inverse()
|
474
|
+
self._actualized_matrix = actualized_matrix
|
475
|
+
self._processed_matrix = actualized_matrix * inverted_main_matrix
|
476
|
+
self._processed_image = image
|
477
|
+
self._process_image_failed = False
|
478
|
+
bb = self.bbox()
|
479
|
+
self._bounds = bb
|
480
|
+
self._paint_bounds = bb
|
481
|
+
except Exception as e:
|
482
|
+
# Memory error if creating requires too much memory.
|
483
|
+
# DecompressionBomb if over 272 megapixels.
|
484
|
+
# ValueError if bounds are NaN.
|
485
|
+
# ZeroDivide if inverting the processed matrix cannot happen because image is a line
|
486
|
+
# print (f"Shit, crashed with {e}")
|
487
|
+
self._process_image_failed = True
|
488
|
+
self._processing = False
|
489
|
+
self.updated()
|
490
|
+
|
491
|
+
@property
|
492
|
+
def opaque_image(self):
|
493
|
+
from PIL import Image
|
494
|
+
img = self.image
|
495
|
+
if img is not None and img.mode == "RGBA":
|
496
|
+
r, g, b, a = img.split()
|
497
|
+
background = Image.new("RGB", img.size, "white")
|
498
|
+
background.paste(img, mask=a)
|
499
|
+
img = background
|
500
|
+
return img
|
501
|
+
|
502
|
+
def _convert_image_to_grayscale(self, image):
|
503
|
+
# Convert image to L type.
|
504
|
+
if image.mode == "I":
|
505
|
+
from PIL import Image
|
506
|
+
# Load the 32-bit signed grayscale image
|
507
|
+
img = np.array(image, dtype=np.int32)
|
508
|
+
|
509
|
+
# No need to reshape the image to its original dimensions
|
510
|
+
# img = img.reshape((image.width, image.height))
|
511
|
+
|
512
|
+
# Normalize the image to the range 0-255
|
513
|
+
img_normalized = ((img - img.min()) / (img.max() - img.min()) * 255).astype(np.uint8)
|
514
|
+
|
515
|
+
# Convert the NumPy array to a Pillow Image
|
516
|
+
img_pil = Image.fromarray(img_normalized)
|
517
|
+
image = img_pil.convert("L")
|
518
|
+
elif image.mode != "L":
|
519
|
+
# Precalculate RGB for L conversion.
|
520
|
+
# if self.red is None:
|
521
|
+
# self.red = 1
|
522
|
+
if self.red is None or self.green is None or self.blue is None:
|
523
|
+
r = 1
|
524
|
+
g = 1
|
525
|
+
b = 1
|
526
|
+
else:
|
527
|
+
r = self.red * 0.299
|
528
|
+
g = self.green * 0.587
|
529
|
+
b = self.blue * 0.114
|
530
|
+
v = self.lightness
|
531
|
+
if v == 0:
|
532
|
+
v = 0.000001
|
533
|
+
c = r + g + b
|
534
|
+
try:
|
535
|
+
c /= v
|
536
|
+
r = r / c
|
537
|
+
g = g / c
|
538
|
+
b = b / c
|
539
|
+
except ZeroDivisionError:
|
540
|
+
pass
|
541
|
+
image = image.convert("RGB")
|
542
|
+
image = image.convert("L", matrix=(r, g, b, 1.0))
|
543
|
+
return image
|
544
|
+
|
545
|
+
def _get_transparent_mask(self, image):
|
546
|
+
"""
|
547
|
+
Create Transparency Mask.
|
548
|
+
@param image:
|
549
|
+
@return:
|
550
|
+
"""
|
551
|
+
if image is None:
|
552
|
+
return None
|
553
|
+
if "transparency" in image.info:
|
554
|
+
image = image.convert("RGBA")
|
555
|
+
try:
|
556
|
+
return image.getchannel("A").point(lambda e: 255 - e)
|
557
|
+
except ValueError:
|
558
|
+
return None
|
559
|
+
|
560
|
+
def _apply_mask(self, image, mask, reject_color=None):
|
561
|
+
"""
|
562
|
+
Fill in original image with reject pixels.
|
563
|
+
|
564
|
+
@param image: Image to be masked off.
|
565
|
+
@param mask: Mask to apply to image
|
566
|
+
@param reject_color: Optional specified reject color override. Reject is usually "white" or black if inverted.
|
567
|
+
@return: image with mask pixels filled in with reject pixels
|
568
|
+
"""
|
569
|
+
if not mask:
|
570
|
+
return image
|
571
|
+
if reject_color is None:
|
572
|
+
reject_color = "black" if self.invert else "white"
|
573
|
+
from PIL import Image
|
574
|
+
|
575
|
+
background = image.copy()
|
576
|
+
reject = Image.new("L", image.size, reject_color)
|
577
|
+
background.paste(reject, mask=mask)
|
578
|
+
return background
|
579
|
+
|
580
|
+
def _get_crop_box(self, image):
|
581
|
+
"""
|
582
|
+
Get the bbox cutting off the reject edges. The reject edges depend on the image's invert setting.
|
583
|
+
@param image: Image to get crop box for.
|
584
|
+
@return:
|
585
|
+
"""
|
586
|
+
try:
|
587
|
+
if self.invert:
|
588
|
+
return image.getbbox()
|
589
|
+
else:
|
590
|
+
return image.point(lambda e: 255 - e).getbbox()
|
591
|
+
except ValueError:
|
592
|
+
return None
|
593
|
+
|
594
|
+
def _process_script(self, image):
|
595
|
+
"""
|
596
|
+
Process actual raster script operations. Any required grayscale, inversion, and masking will already have
|
597
|
+
occurred. If there were reject pixels before they will be masked off after this process.
|
598
|
+
|
599
|
+
@param image: image to process with self.operation script.
|
600
|
+
|
601
|
+
@return: processed image
|
602
|
+
"""
|
603
|
+
from PIL import ImageEnhance, ImageFilter, ImageOps
|
604
|
+
|
605
|
+
overall_left = 0
|
606
|
+
overall_top = 0
|
607
|
+
overall_right, overall_bottom = image.size
|
608
|
+
for op in self.operations:
|
609
|
+
name = op["name"]
|
610
|
+
if name == "resample":
|
611
|
+
# This is just a reminder, that while this may still appear in the scripts it is intentionally
|
612
|
+
# ignored (or needs to be revised with the upcoming appearance of passthrough) as it is not
|
613
|
+
# serving the purpose of the past
|
614
|
+
continue
|
615
|
+
if name == "crop":
|
616
|
+
try:
|
617
|
+
# The dimensions of the image could have already be changed,
|
618
|
+
# so we recalculate the edges based on the original image size
|
619
|
+
if op["enable"] and op["bounds"] is not None:
|
620
|
+
crop = op["bounds"]
|
621
|
+
left_gap = int(crop[0])
|
622
|
+
top_gap = int(crop[1])
|
623
|
+
right_gap = self.image.width - int(crop[2])
|
624
|
+
bottom_gap = self.image.height - int(crop[3])
|
625
|
+
|
626
|
+
w, h = image.size
|
627
|
+
left = left_gap
|
628
|
+
upper = top_gap
|
629
|
+
right = image.width - right_gap
|
630
|
+
lower = image.height - bottom_gap
|
631
|
+
|
632
|
+
if left >= w:
|
633
|
+
left = w - 1
|
634
|
+
if upper >= h:
|
635
|
+
upper = h
|
636
|
+
if right <= left:
|
637
|
+
right = left + 1
|
638
|
+
if lower <= upper:
|
639
|
+
lower = upper + 1
|
640
|
+
|
641
|
+
overall_left += left
|
642
|
+
overall_top += upper
|
643
|
+
overall_right -= w - right
|
644
|
+
overall_bottom -= h - lower
|
645
|
+
image = image.crop((left, upper, right, lower))
|
646
|
+
except KeyError:
|
647
|
+
pass
|
648
|
+
elif name == "edge_enhance":
|
649
|
+
try:
|
650
|
+
if op["enable"]:
|
651
|
+
if image.mode == "P":
|
652
|
+
image = image.convert("L")
|
653
|
+
image = image.filter(filter=ImageFilter.EDGE_ENHANCE)
|
654
|
+
except KeyError:
|
655
|
+
pass
|
656
|
+
elif name == "auto_contrast":
|
657
|
+
try:
|
658
|
+
if op["enable"]:
|
659
|
+
if image.mode not in ("RGB", "L"):
|
660
|
+
# Auto-contrast raises NotImplementedError if P
|
661
|
+
# Auto-contrast raises OSError if not RGB, L.
|
662
|
+
image = image.convert("L")
|
663
|
+
image = ImageOps.autocontrast(image, cutoff=op["cutoff"])
|
664
|
+
except KeyError:
|
665
|
+
pass
|
666
|
+
elif name == "tone":
|
667
|
+
try:
|
668
|
+
if op["enable"] and op["values"] is not None and image.mode == "L":
|
669
|
+
image = image.convert("P")
|
670
|
+
tone_values = op["values"]
|
671
|
+
if op["type"] == "spline":
|
672
|
+
spline = ImageNode.spline(tone_values)
|
673
|
+
else:
|
674
|
+
tone_values = [q for q in tone_values if q is not None]
|
675
|
+
spline = ImageNode.line(tone_values)
|
676
|
+
if len(spline) < 256:
|
677
|
+
spline.extend([255] * (256 - len(spline)))
|
678
|
+
if len(spline) > 256:
|
679
|
+
spline = spline[:256]
|
680
|
+
image = image.point(spline)
|
681
|
+
if image.mode != "L":
|
682
|
+
image = image.convert("L")
|
683
|
+
except KeyError:
|
684
|
+
pass
|
685
|
+
elif name == "contrast":
|
686
|
+
try:
|
687
|
+
if op["enable"] and (op["contrast"] is not None and op["brightness"] is not None):
|
688
|
+
contrast = ImageEnhance.Contrast(image)
|
689
|
+
c = (op["contrast"] + 128.0) / 128.0
|
690
|
+
image = contrast.enhance(c)
|
691
|
+
|
692
|
+
brightness = ImageEnhance.Brightness(image)
|
693
|
+
b = (op["brightness"] + 128.0) / 128.0
|
694
|
+
image = brightness.enhance(b)
|
695
|
+
except KeyError:
|
696
|
+
pass
|
697
|
+
elif name == "gamma":
|
698
|
+
try:
|
699
|
+
if op["enable"] and op["factor"] is not None:
|
700
|
+
if image.mode == "L":
|
701
|
+
gamma_factor = float(op["factor"])
|
702
|
+
|
703
|
+
def crimp(px):
|
704
|
+
px = int(round(px))
|
705
|
+
if px < 0:
|
706
|
+
return 0
|
707
|
+
if px > 255:
|
708
|
+
return 255
|
709
|
+
return px
|
710
|
+
|
711
|
+
if gamma_factor == 0:
|
712
|
+
gamma_lut = [0] * 256
|
713
|
+
else:
|
714
|
+
gamma_lut = [
|
715
|
+
crimp(pow(i / 255, (1.0 / gamma_factor)) * 255)
|
716
|
+
for i in range(256)
|
717
|
+
]
|
718
|
+
image = image.point(gamma_lut)
|
719
|
+
if image.mode != "L":
|
720
|
+
image = image.convert("L")
|
721
|
+
except KeyError:
|
722
|
+
pass
|
723
|
+
elif name == "unsharp_mask":
|
724
|
+
try:
|
725
|
+
if (
|
726
|
+
op["enable"]
|
727
|
+
and op["percent"] is not None
|
728
|
+
and op["radius"] is not None
|
729
|
+
and op["threshold"] is not None
|
730
|
+
):
|
731
|
+
unsharp = ImageFilter.UnsharpMask(
|
732
|
+
radius=op["radius"],
|
733
|
+
percent=op["percent"],
|
734
|
+
threshold=op["threshold"],
|
735
|
+
)
|
736
|
+
image = image.filter(unsharp)
|
737
|
+
except (KeyError, ValueError): # Value error if wrong type of image.
|
738
|
+
pass
|
739
|
+
elif name == "halftone":
|
740
|
+
try:
|
741
|
+
if op["enable"]:
|
742
|
+
image = RasterScripts.halftone(
|
743
|
+
image,
|
744
|
+
sample=op["sample"],
|
745
|
+
angle=op["angle"],
|
746
|
+
oversample=op["oversample"],
|
747
|
+
black=op["black"],
|
748
|
+
)
|
749
|
+
except KeyError:
|
750
|
+
pass
|
751
|
+
elif name == "dither":
|
752
|
+
# Set dither
|
753
|
+
try:
|
754
|
+
if op["enable"] and op["type"] is not None:
|
755
|
+
self.dither_type = op["type"]
|
756
|
+
self.dither = True
|
757
|
+
self.is_depthmap = False
|
758
|
+
else:
|
759
|
+
# Takes precedence
|
760
|
+
self.dither = False
|
761
|
+
# image = self._apply_dither(image)
|
762
|
+
except KeyError:
|
763
|
+
pass
|
764
|
+
else:
|
765
|
+
# print(f"Unknown operation in raster-script: {name}")
|
766
|
+
continue
|
767
|
+
return image, (overall_left, overall_top, overall_right, overall_bottom)
|
768
|
+
|
769
|
+
def _apply_dither(self, image):
|
770
|
+
"""
|
771
|
+
Dither image to 1 bit. Floyd-Steinberg is performed by Pillow, other dithers require custom code.
|
772
|
+
|
773
|
+
@param image: grayscale image to dither.
|
774
|
+
@return: 1 bit dithered image
|
775
|
+
"""
|
776
|
+
from meerk40t.image.imagetools import dither
|
777
|
+
|
778
|
+
if self.dither and self.dither_type is not None:
|
779
|
+
if self.dither_type != "Floyd-Steinberg":
|
780
|
+
image = dither(image, self.dither_type)
|
781
|
+
if image.mode != "1":
|
782
|
+
image = image.convert("1")
|
783
|
+
self.is_depthmap = False
|
784
|
+
return image
|
785
|
+
|
786
|
+
def _process_image(self, step_x, step_y, crop=True):
|
787
|
+
"""
|
788
|
+
This core code replaces the older actualize and rasterwizard functionalities. It should convert the image to
|
789
|
+
a post-processed form with resulting post-process matrix.
|
790
|
+
|
791
|
+
@param crop: Should the unneeded edges be cropped as part of this process. The need for the edge is determined
|
792
|
+
by the color and the state of the self.invert attribute.
|
793
|
+
@return:
|
794
|
+
"""
|
795
|
+
from PIL import Image, ImageOps
|
796
|
+
|
797
|
+
try:
|
798
|
+
from PIL.Image import Transform
|
799
|
+
|
800
|
+
AFFINE = Transform.AFFINE
|
801
|
+
except ImportError:
|
802
|
+
AFFINE = Image.AFFINE
|
803
|
+
|
804
|
+
try:
|
805
|
+
from PIL.Image import Resampling
|
806
|
+
|
807
|
+
BICUBIC = Resampling.BICUBIC
|
808
|
+
except ImportError:
|
809
|
+
BICUBIC = Image.BICUBIC
|
810
|
+
self._processing = True
|
811
|
+
image = self.image
|
812
|
+
|
813
|
+
transparent_mask = self._get_transparent_mask(image)
|
814
|
+
opaque = self.opaque_image
|
815
|
+
image = self._convert_image_to_grayscale(opaque)
|
816
|
+
|
817
|
+
image = self._apply_mask(image, transparent_mask)
|
818
|
+
|
819
|
+
# Calculate image box.
|
820
|
+
box = None
|
821
|
+
if crop:
|
822
|
+
box = self._get_crop_box(image)
|
823
|
+
if box is None:
|
824
|
+
# If box is entirely white, bbox caused value error, or crop not set.
|
825
|
+
box = (0, 0, image.width, image.height)
|
826
|
+
orgbox = (box[0], box[1], box[2], box[3])
|
827
|
+
|
828
|
+
transform_matrix = copy(self.matrix) # Prevent Knock-on effect.
|
829
|
+
|
830
|
+
# Find the boundary points of the rotated box edges.
|
831
|
+
boundary_points = [
|
832
|
+
transform_matrix.point_in_matrix_space([box[0], box[1]]), # Top-left
|
833
|
+
transform_matrix.point_in_matrix_space([box[2], box[1]]), # Top-right
|
834
|
+
transform_matrix.point_in_matrix_space([box[0], box[3]]), # Bottom-left
|
835
|
+
transform_matrix.point_in_matrix_space([box[2], box[3]]), # Bottom-right
|
836
|
+
]
|
837
|
+
xs = [e[0] for e in boundary_points]
|
838
|
+
ys = [e[1] for e in boundary_points]
|
839
|
+
|
840
|
+
# bbox here is expanded matrix size of box.
|
841
|
+
step_scale_x = 1 / float(step_x)
|
842
|
+
step_scale_y = 1 / float(step_y)
|
843
|
+
|
844
|
+
bbox = min(xs), min(ys), max(xs), max(ys)
|
845
|
+
|
846
|
+
image_width = ceil(bbox[2] * step_scale_x) - floor(bbox[0] * step_scale_x)
|
847
|
+
image_height = ceil(bbox[3] * step_scale_y) - floor(bbox[1] * step_scale_y)
|
848
|
+
tx = bbox[0]
|
849
|
+
ty = bbox[1]
|
850
|
+
# Caveat: we move the picture backward, so that the non-white
|
851
|
+
# image content aligns at 0 , 0 - but we don't crop the image
|
852
|
+
transform_matrix.post_translate(-tx, -ty)
|
853
|
+
transform_matrix.post_scale(step_scale_x, step_scale_y)
|
854
|
+
if step_y < 0:
|
855
|
+
# If step_y is negative, translate
|
856
|
+
transform_matrix.post_translate(0, image_height)
|
857
|
+
if step_x < 0:
|
858
|
+
# If step_x is negative, translate
|
859
|
+
transform_matrix.post_translate(image_width, 0)
|
860
|
+
|
861
|
+
try:
|
862
|
+
transform_matrix.inverse()
|
863
|
+
except ZeroDivisionError:
|
864
|
+
# malformed matrix, scale=0 or something.
|
865
|
+
transform_matrix.reset()
|
866
|
+
|
867
|
+
# Perform image transform if needed.
|
868
|
+
if (
|
869
|
+
self.matrix.a != step_x
|
870
|
+
or self.matrix.b != 0.0
|
871
|
+
or self.matrix.c != 0.0
|
872
|
+
or self.matrix.d != step_y
|
873
|
+
):
|
874
|
+
# print (f"another transform called while {image.width}x{image.height} - requested: {image_width}x{image_height}")
|
875
|
+
if image_height <= 0:
|
876
|
+
image_height = 1
|
877
|
+
if image_width <= 0:
|
878
|
+
image_width = 1
|
879
|
+
image = image.transform(
|
880
|
+
(image_width, image_height),
|
881
|
+
AFFINE,
|
882
|
+
(
|
883
|
+
transform_matrix.a,
|
884
|
+
transform_matrix.c,
|
885
|
+
transform_matrix.e,
|
886
|
+
transform_matrix.b,
|
887
|
+
transform_matrix.d,
|
888
|
+
transform_matrix.f,
|
889
|
+
),
|
890
|
+
resample=BICUBIC,
|
891
|
+
fillcolor="black" if self.invert else "white",
|
892
|
+
)
|
893
|
+
# print (f"after transform {image.width}x{image.height}")
|
894
|
+
actualized_matrix = Matrix()
|
895
|
+
|
896
|
+
if step_y < 0:
|
897
|
+
# if step_y is negative, translate.
|
898
|
+
actualized_matrix.post_translate(0, -image_height)
|
899
|
+
if step_x < 0:
|
900
|
+
# if step_x is negative, translate.
|
901
|
+
actualized_matrix.post_translate(-image_width, 0)
|
902
|
+
|
903
|
+
# If crop applies, apply crop.
|
904
|
+
if crop:
|
905
|
+
cbox = self._get_crop_box(image)
|
906
|
+
if cbox is not None:
|
907
|
+
width = cbox[2] - cbox[0]
|
908
|
+
height = cbox[3] - cbox[1]
|
909
|
+
if width != image.width or height != image.height:
|
910
|
+
image = image.crop(cbox)
|
911
|
+
# TODO:
|
912
|
+
# We did not crop the image so far, but we already applied
|
913
|
+
# the cropped transformation! That may be faulty, and needs to
|
914
|
+
# be corrected at a later stage, but this logic, even if clumsy
|
915
|
+
# is good enough: don't shift things twice!
|
916
|
+
if orgbox[0] == 0 and orgbox[1] == 0:
|
917
|
+
actualized_matrix.post_translate(cbox[0], cbox[1])
|
918
|
+
|
919
|
+
actualized_matrix.post_scale(step_x, step_y)
|
920
|
+
actualized_matrix.post_translate(tx, ty)
|
921
|
+
|
922
|
+
# Invert black to white if needed.
|
923
|
+
if self.invert:
|
924
|
+
try:
|
925
|
+
image = ImageOps.invert(image)
|
926
|
+
except OSError as e:
|
927
|
+
print (f"Image inversion crashed: {e}\nMode: {image.mode}, {image.width}x{image.height} pixel")
|
928
|
+
|
929
|
+
# Find rejection mask of white pixels. (already inverted)
|
930
|
+
reject_mask = image.point(lambda e: 0 if e == 255 else 255)
|
931
|
+
image, newbounds = self._process_script(image)
|
932
|
+
# This may have again changed the size of the image (op crop)
|
933
|
+
# so we need to adjust the reject mask...
|
934
|
+
reject_mask = reject_mask.crop(newbounds)
|
935
|
+
|
936
|
+
background = Image.new("L", image.size, "white")
|
937
|
+
background.paste(image, mask=reject_mask)
|
938
|
+
image = background
|
939
|
+
|
940
|
+
image = self._apply_dither(image)
|
941
|
+
|
942
|
+
self._processing = False
|
943
|
+
return actualized_matrix, image
|
944
|
+
|
945
|
+
def _apply_keyhole(self):
|
946
|
+
from PIL import Image, ImageDraw
|
947
|
+
|
948
|
+
image = self._processed_image
|
949
|
+
if image is None:
|
950
|
+
image = self.image
|
951
|
+
if self._keyhole_geometry is not None:
|
952
|
+
# Let's check whether the keyhole dimensions match
|
953
|
+
if self._keyhole_image is not None and (self._keyhole_image.width != image.width or self._keyhole_image.height != image.height):
|
954
|
+
self._keyhole_image = None
|
955
|
+
if self._keyhole_image is None:
|
956
|
+
actualized_matrix = self._actualized_matrix
|
957
|
+
# We can't render something with the usual suspects ie laserrender.render
|
958
|
+
# as we do not have access to wxpython on the command line, so we stick
|
959
|
+
# to the polygon method of ImageDraw instead
|
960
|
+
maskimage = Image.new("L", image.size, "black")
|
961
|
+
draw = ImageDraw.Draw(maskimage)
|
962
|
+
inverted_main_matrix = Matrix(self.matrix).inverse()
|
963
|
+
matrix = actualized_matrix * inverted_main_matrix * self.matrix
|
964
|
+
|
965
|
+
x0, y0 = matrix.point_in_matrix_space((0, 0))
|
966
|
+
x2, y2 = matrix.point_in_matrix_space((image.width, image.height))
|
967
|
+
# print (x0, y0, x2, y2)
|
968
|
+
# Let's simplify things, if we don't have any overlap then the image is white...
|
969
|
+
i_wd = x2 - x0
|
970
|
+
i_ht = y2 - y0
|
971
|
+
gidx = 0
|
972
|
+
for geom in self._keyhole_geometry.as_subpaths():
|
973
|
+
# Let's simplify things, if we don't have any overlap then we don't need to do something
|
974
|
+
# if x0 > bounds[2] or x2 < bounds [0] or y0 > bounds[3] or y2 < bounds[1]:
|
975
|
+
# continue
|
976
|
+
geom_points = list(geom.as_interpolated_points(int(UNITS_PER_MM/10)))
|
977
|
+
points = list()
|
978
|
+
for pt in geom_points:
|
979
|
+
if pt is None:
|
980
|
+
continue
|
981
|
+
gx = pt.real
|
982
|
+
gy = pt.imag
|
983
|
+
x = int(maskimage.width * (gx - x0) / i_wd )
|
984
|
+
y = int(maskimage.height * (gy - y0) / i_ht )
|
985
|
+
points.append( (x, y) )
|
986
|
+
|
987
|
+
# print (points)
|
988
|
+
draw.polygon( points, fill="white", outline="white")
|
989
|
+
self._keyhole_image = maskimage
|
990
|
+
# For debug purposes...
|
991
|
+
# maskimage.save("C:\\temp\\maskimage.png")
|
992
|
+
|
993
|
+
background = Image.new("L", image.size, "white")
|
994
|
+
background.paste(image, mask=self._keyhole_image)
|
995
|
+
image = background
|
996
|
+
return image
|
997
|
+
|
998
|
+
@staticmethod
|
999
|
+
def line(p):
|
1000
|
+
N = len(p) - 1
|
1001
|
+
try:
|
1002
|
+
m = [(p[i + 1][1] - p[i][1]) / (p[i + 1][0] - p[i][0]) for i in range(0, N)]
|
1003
|
+
except ZeroDivisionError:
|
1004
|
+
m = [1] * N
|
1005
|
+
# b = y - mx
|
1006
|
+
b = [p[i][1] - (m[i] * p[i][0]) for i in range(N)]
|
1007
|
+
r = list()
|
1008
|
+
for i in range(p[0][0]):
|
1009
|
+
r.append(0)
|
1010
|
+
for i in range(len(p) - 1):
|
1011
|
+
x0 = p[i][0]
|
1012
|
+
x1 = p[i + 1][0]
|
1013
|
+
range_list = [int(round((m[i] * x) + b[i])) for x in range(x0, x1)]
|
1014
|
+
r.extend(range_list)
|
1015
|
+
for i in range(p[-1][0], 256):
|
1016
|
+
r.append(255)
|
1017
|
+
r.append(round(int(p[-1][1])))
|
1018
|
+
return r
|
1019
|
+
|
1020
|
+
@staticmethod
|
1021
|
+
def spline(p):
|
1022
|
+
"""
|
1023
|
+
Spline interpreter.
|
1024
|
+
|
1025
|
+
Returns all integer locations between different spline interpolation values
|
1026
|
+
@param p: points to be quad spline interpolated.
|
1027
|
+
@return: integer y values for given spline points.
|
1028
|
+
"""
|
1029
|
+
try:
|
1030
|
+
N = len(p) - 1
|
1031
|
+
w = [(p[i + 1][0] - p[i][0]) for i in range(N)]
|
1032
|
+
h = [(p[i + 1][1] - p[i][1]) / w[i] for i in range(N)]
|
1033
|
+
ftt = (
|
1034
|
+
[0]
|
1035
|
+
+ [3 * (h[i + 1] - h[i]) / (w[i + 1] + w[i]) for i in range(N - 1)]
|
1036
|
+
+ [0]
|
1037
|
+
)
|
1038
|
+
A = [(ftt[i + 1] - ftt[i]) / (6 * w[i]) for i in range(N)]
|
1039
|
+
B = [ftt[i] / 2 for i in range(N)]
|
1040
|
+
C = [h[i] - w[i] * (ftt[i + 1] + 2 * ftt[i]) / 6 for i in range(N)]
|
1041
|
+
D = [p[i][1] for i in range(N)]
|
1042
|
+
except ZeroDivisionError:
|
1043
|
+
return list(range(256))
|
1044
|
+
r = list()
|
1045
|
+
for i in range(p[0][0]):
|
1046
|
+
r.append(0)
|
1047
|
+
for i in range(len(p) - 1):
|
1048
|
+
a = p[i][0]
|
1049
|
+
b = p[i + 1][0]
|
1050
|
+
r.extend(
|
1051
|
+
int(
|
1052
|
+
round(
|
1053
|
+
A[i] * (x - a) ** 3
|
1054
|
+
+ B[i] * (x - a) ** 2
|
1055
|
+
+ C[i] * (x - a)
|
1056
|
+
+ D[i]
|
1057
|
+
)
|
1058
|
+
)
|
1059
|
+
for x in range(a, b)
|
1060
|
+
)
|
1061
|
+
for i in range(p[-1][0], 256):
|
1062
|
+
r.append(255)
|
1063
|
+
r.append(round(int(p[-1][1])))
|
1064
|
+
return r
|
1065
|
+
|
1066
|
+
def as_path(self):
|
1067
|
+
image_width, image_height = self.active_image.size
|
1068
|
+
matrix = self.active_matrix
|
1069
|
+
x0, y0 = matrix.point_in_matrix_space((0, 0))
|
1070
|
+
x1, y1 = matrix.point_in_matrix_space((0, image_height))
|
1071
|
+
x2, y2 = matrix.point_in_matrix_space((image_width, image_height))
|
1072
|
+
x3, y3 = matrix.point_in_matrix_space((image_width, 0))
|
1073
|
+
return abs(Path(Polygon((x0, y0), (x1, y1), (x2, y2), (x3, y3), (x0, y0))))
|
1074
|
+
|
1075
|
+
def translated(self, dx, dy, interim=False):
|
1076
|
+
self._cache = None
|
1077
|
+
self._keyhole_image = None
|
1078
|
+
if self._actualized_matrix is not None:
|
1079
|
+
self._actualized_matrix.post_translate(dx, dy)
|
1080
|
+
if self._convex_hull is not None:
|
1081
|
+
self._convex_hull.translate(dx, dy)
|
1082
|
+
return super().translated(dx, dy)
|