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
@@ -1,766 +1,777 @@
|
|
1
|
-
import threading
|
2
|
-
from copy import copy
|
3
|
-
from math import ceil, floor
|
4
|
-
|
5
|
-
from meerk40t.core.node.node import Node
|
6
|
-
from meerk40t.core.units import UNITS_PER_INCH
|
7
|
-
from meerk40t.image.imagetools import RasterScripts
|
8
|
-
from meerk40t.svgelements import Matrix, Path, Polygon
|
9
|
-
|
10
|
-
|
11
|
-
class ImageProcessedNode(Node):
|
12
|
-
"""
|
13
|
-
ImageProcessedNode is the bootstrapped node type for the 'image processed' type.
|
14
|
-
|
15
|
-
This node type accepts an image_node as an operand. And calculates a processed node. Unprocessed nodes cannot
|
16
|
-
rotate and will not resample. This node will allow rotation, resampling, and processing scripts.
|
17
|
-
"""
|
18
|
-
|
19
|
-
def __init__(self, **kwargs):
|
20
|
-
self.node = None
|
21
|
-
self.matrix = None
|
22
|
-
self.overscan = None
|
23
|
-
self.direction = None
|
24
|
-
self.dpi = 500
|
25
|
-
self.operations = list()
|
26
|
-
self.invert = None
|
27
|
-
self.dither = True
|
28
|
-
self.dither_type = "Floyd-Steinberg"
|
29
|
-
self.red = 1.0
|
30
|
-
self.green = 1.0
|
31
|
-
self.blue = 1.0
|
32
|
-
self.lightness = 1.0
|
33
|
-
self.view_invert = False
|
34
|
-
self.prevent_crop = False
|
35
|
-
|
36
|
-
super().__init__(type="elem image", **kwargs)
|
37
|
-
# kwargs can actually reset quite a lot of the properties to none
|
38
|
-
# so we need to revert these changes...
|
39
|
-
if self.red is None:
|
40
|
-
self.red = 1.0
|
41
|
-
if self.green is None:
|
42
|
-
self.green = 1.0
|
43
|
-
if self.blue is None:
|
44
|
-
self.blue = 1.0
|
45
|
-
if self.lightness is None:
|
46
|
-
self.lightness = 1.0
|
47
|
-
if self.operations is None:
|
48
|
-
self.operations = list()
|
49
|
-
if self.dither_type is None:
|
50
|
-
self.dither_type = "Floyd-Steinberg"
|
51
|
-
|
52
|
-
self.__formatter = "{element_type} {id} {width}x{height}"
|
53
|
-
if self.matrix is None:
|
54
|
-
self.matrix = Matrix()
|
55
|
-
if hasattr(self, "href"):
|
56
|
-
# TODO: Move all of this to svgio loading.
|
57
|
-
try:
|
58
|
-
from PIL import Image as PILImage
|
59
|
-
|
60
|
-
if not isinstance(self.href, str):
|
61
|
-
# Error caused by href being int value
|
62
|
-
raise ImportError
|
63
|
-
|
64
|
-
self.image = PILImage.open(self.href)
|
65
|
-
if hasattr(self, "x"):
|
66
|
-
self.matrix.post_translate_x(self.x)
|
67
|
-
if hasattr(self, "y"):
|
68
|
-
self.matrix.post_translate_x(self.y)
|
69
|
-
real_width, real_height = self.image.size
|
70
|
-
declared_width, declared_height = real_width, real_height
|
71
|
-
if hasattr(self, "width"):
|
72
|
-
declared_width = self.width
|
73
|
-
if hasattr(self, "height"):
|
74
|
-
declared_height = self.height
|
75
|
-
try:
|
76
|
-
sx = declared_width / real_width
|
77
|
-
sy = declared_height / real_height
|
78
|
-
self.matrix.post_scale(sx, sy)
|
79
|
-
except ZeroDivisionError:
|
80
|
-
pass
|
81
|
-
delattr(self, "href")
|
82
|
-
delattr(self, "x")
|
83
|
-
delattr(self, "y")
|
84
|
-
delattr(self, "height")
|
85
|
-
delattr(self, "width")
|
86
|
-
except ImportError:
|
87
|
-
self.image = None
|
88
|
-
|
89
|
-
# Step_x/y is the step factor of the image, the reciprocal of the DPI.
|
90
|
-
self.step_x = None
|
91
|
-
self.step_y = None
|
92
|
-
|
93
|
-
self._needs_update = False
|
94
|
-
self._update_thread = None
|
95
|
-
self._update_lock = threading.Lock()
|
96
|
-
self._processed_image = None
|
97
|
-
self._processed_matrix = None
|
98
|
-
self._process_image_failed = False
|
99
|
-
self.message = None
|
100
|
-
if self.operations or self.dither or self.prevent_crop:
|
101
|
-
step = UNITS_PER_INCH / self.dpi
|
102
|
-
step_x = step
|
103
|
-
step_y = step
|
104
|
-
self.process_image(step_x, step_y, not self.prevent_crop)
|
105
|
-
|
106
|
-
def __copy__(self):
|
107
|
-
nd = self.node_dict
|
108
|
-
nd["matrix"] = copy(self.matrix)
|
109
|
-
nd["operations"] = copy(self.operations)
|
110
|
-
return ImageProcessedNode(**nd)
|
111
|
-
|
112
|
-
def __repr__(self):
|
113
|
-
return f"{self.__class__.__name__}('{self.type}', {str(self.image)}, {str(self._parent)})"
|
114
|
-
|
115
|
-
def as_image(self):
|
116
|
-
if self._processed_image is None and (
|
117
|
-
(self.operations is not None and len(self.operations) > 0) or self.dither
|
118
|
-
):
|
119
|
-
step = UNITS_PER_INCH / self.dpi
|
120
|
-
step_x = step
|
121
|
-
step_y = step
|
122
|
-
self.process_image(step_x, step_y, not self.prevent_crop)
|
123
|
-
return self._processed_image, self.bbox()
|
124
|
-
|
125
|
-
@property
|
126
|
-
def active_matrix(self):
|
127
|
-
if self._processed_matrix is None:
|
128
|
-
return self.matrix
|
129
|
-
return self._processed_matrix * self.matrix
|
130
|
-
|
131
|
-
def preprocess(self, context, matrix, plan):
|
132
|
-
"""
|
133
|
-
Preprocess step during the cut planning stages.
|
134
|
-
|
135
|
-
We require a context to calculate the correct step values relative to the device
|
136
|
-
"""
|
137
|
-
self.step_x, self.step_y = context.device.view.dpi_to_steps(self.dpi)
|
138
|
-
self.matrix *= matrix
|
139
|
-
self.set_dirty_bounds()
|
140
|
-
self.process_image(self.step_x, self.step_y, not self.prevent_crop)
|
141
|
-
|
142
|
-
def bbox(self, transformed=True, with_stroke=False):
|
143
|
-
image_width, image_height = self.active_image.size
|
144
|
-
matrix = self.active_matrix
|
145
|
-
x0, y0 = matrix.point_in_matrix_space((0, 0))
|
146
|
-
x1, y1 = matrix.point_in_matrix_space((image_width, image_height))
|
147
|
-
x2, y2 = matrix.point_in_matrix_space((0, image_height))
|
148
|
-
x3, y3 = matrix.point_in_matrix_space((image_width, 0))
|
149
|
-
return (
|
150
|
-
min(x0, x1, x2, x3),
|
151
|
-
min(y0, y1, y2, y3),
|
152
|
-
max(x0, x1, x2, x3),
|
153
|
-
max(y0, y1, y2, y3),
|
154
|
-
)
|
155
|
-
|
156
|
-
def default_map(self, default_map=None):
|
157
|
-
default_map = super().default_map(default_map=default_map)
|
158
|
-
default_map.update(self.__dict__)
|
159
|
-
image = self.active_image
|
160
|
-
default_map["width"] = image.width
|
161
|
-
default_map["height"] = image.height
|
162
|
-
default_map["element_type"] = "Image"
|
163
|
-
return default_map
|
164
|
-
|
165
|
-
def
|
166
|
-
# Dragging element into element.
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
self._points
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
""
|
203
|
-
|
204
|
-
|
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
|
-
self.
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
if
|
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
|
-
return
|
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
|
-
if
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
image = image.
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
image =
|
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
|
-
|
540
|
-
|
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
|
-
if
|
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
|
-
if
|
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
|
-
|
1
|
+
import threading
|
2
|
+
from copy import copy
|
3
|
+
from math import ceil, floor
|
4
|
+
|
5
|
+
from meerk40t.core.node.node import Node
|
6
|
+
from meerk40t.core.units import UNITS_PER_INCH
|
7
|
+
from meerk40t.image.imagetools import RasterScripts
|
8
|
+
from meerk40t.svgelements import Matrix, Path, Polygon
|
9
|
+
|
10
|
+
|
11
|
+
class ImageProcessedNode(Node):
|
12
|
+
"""
|
13
|
+
ImageProcessedNode is the bootstrapped node type for the 'image processed' type.
|
14
|
+
|
15
|
+
This node type accepts an image_node as an operand. And calculates a processed node. Unprocessed nodes cannot
|
16
|
+
rotate and will not resample. This node will allow rotation, resampling, and processing scripts.
|
17
|
+
"""
|
18
|
+
|
19
|
+
def __init__(self, **kwargs):
|
20
|
+
self.node = None
|
21
|
+
self.matrix = None
|
22
|
+
self.overscan = None
|
23
|
+
self.direction = None
|
24
|
+
self.dpi = 500
|
25
|
+
self.operations = list()
|
26
|
+
self.invert = None
|
27
|
+
self.dither = True
|
28
|
+
self.dither_type = "Floyd-Steinberg"
|
29
|
+
self.red = 1.0
|
30
|
+
self.green = 1.0
|
31
|
+
self.blue = 1.0
|
32
|
+
self.lightness = 1.0
|
33
|
+
self.view_invert = False
|
34
|
+
self.prevent_crop = False
|
35
|
+
|
36
|
+
super().__init__(type="elem image", **kwargs)
|
37
|
+
# kwargs can actually reset quite a lot of the properties to none
|
38
|
+
# so, we need to revert these changes...
|
39
|
+
if self.red is None:
|
40
|
+
self.red = 1.0
|
41
|
+
if self.green is None:
|
42
|
+
self.green = 1.0
|
43
|
+
if self.blue is None:
|
44
|
+
self.blue = 1.0
|
45
|
+
if self.lightness is None:
|
46
|
+
self.lightness = 1.0
|
47
|
+
if self.operations is None:
|
48
|
+
self.operations = list()
|
49
|
+
if self.dither_type is None:
|
50
|
+
self.dither_type = "Floyd-Steinberg"
|
51
|
+
|
52
|
+
self.__formatter = "{element_type} {id} {width}x{height}"
|
53
|
+
if self.matrix is None:
|
54
|
+
self.matrix = Matrix()
|
55
|
+
if hasattr(self, "href"):
|
56
|
+
# TODO: Move all of this to svgio loading.
|
57
|
+
try:
|
58
|
+
from PIL import Image as PILImage
|
59
|
+
|
60
|
+
if not isinstance(self.href, str):
|
61
|
+
# Error caused by href being int value
|
62
|
+
raise ImportError
|
63
|
+
|
64
|
+
self.image = PILImage.open(self.href)
|
65
|
+
if hasattr(self, "x"):
|
66
|
+
self.matrix.post_translate_x(self.x)
|
67
|
+
if hasattr(self, "y"):
|
68
|
+
self.matrix.post_translate_x(self.y)
|
69
|
+
real_width, real_height = self.image.size
|
70
|
+
declared_width, declared_height = real_width, real_height
|
71
|
+
if hasattr(self, "width"):
|
72
|
+
declared_width = self.width
|
73
|
+
if hasattr(self, "height"):
|
74
|
+
declared_height = self.height
|
75
|
+
try:
|
76
|
+
sx = declared_width / real_width
|
77
|
+
sy = declared_height / real_height
|
78
|
+
self.matrix.post_scale(sx, sy)
|
79
|
+
except ZeroDivisionError:
|
80
|
+
pass
|
81
|
+
delattr(self, "href")
|
82
|
+
delattr(self, "x")
|
83
|
+
delattr(self, "y")
|
84
|
+
delattr(self, "height")
|
85
|
+
delattr(self, "width")
|
86
|
+
except ImportError:
|
87
|
+
self.image = None
|
88
|
+
|
89
|
+
# Step_x/y is the step factor of the image, the reciprocal of the DPI.
|
90
|
+
self.step_x = None
|
91
|
+
self.step_y = None
|
92
|
+
|
93
|
+
self._needs_update = False
|
94
|
+
self._update_thread = None
|
95
|
+
self._update_lock = threading.Lock()
|
96
|
+
self._processed_image = None
|
97
|
+
self._processed_matrix = None
|
98
|
+
self._process_image_failed = False
|
99
|
+
self.message = None
|
100
|
+
if self.operations or self.dither or self.prevent_crop:
|
101
|
+
step = UNITS_PER_INCH / self.dpi
|
102
|
+
step_x = step
|
103
|
+
step_y = step
|
104
|
+
self.process_image(step_x, step_y, not self.prevent_crop)
|
105
|
+
|
106
|
+
def __copy__(self):
|
107
|
+
nd = self.node_dict
|
108
|
+
nd["matrix"] = copy(self.matrix)
|
109
|
+
nd["operations"] = copy(self.operations)
|
110
|
+
return ImageProcessedNode(**nd)
|
111
|
+
|
112
|
+
def __repr__(self):
|
113
|
+
return f"{self.__class__.__name__}('{self.type}', {str(self.image)}, {str(self._parent)})"
|
114
|
+
|
115
|
+
def as_image(self):
|
116
|
+
if self._processed_image is None and (
|
117
|
+
(self.operations is not None and len(self.operations) > 0) or self.dither
|
118
|
+
):
|
119
|
+
step = UNITS_PER_INCH / self.dpi
|
120
|
+
step_x = step
|
121
|
+
step_y = step
|
122
|
+
self.process_image(step_x, step_y, not self.prevent_crop)
|
123
|
+
return self._processed_image, self.bbox()
|
124
|
+
|
125
|
+
@property
|
126
|
+
def active_matrix(self):
|
127
|
+
if self._processed_matrix is None:
|
128
|
+
return self.matrix
|
129
|
+
return self._processed_matrix * self.matrix
|
130
|
+
|
131
|
+
def preprocess(self, context, matrix, plan):
|
132
|
+
"""
|
133
|
+
Preprocess step during the cut planning stages.
|
134
|
+
|
135
|
+
We require a context to calculate the correct step values relative to the device
|
136
|
+
"""
|
137
|
+
self.step_x, self.step_y = context.device.view.dpi_to_steps(self.dpi)
|
138
|
+
self.matrix *= matrix
|
139
|
+
self.set_dirty_bounds()
|
140
|
+
self.process_image(self.step_x, self.step_y, not self.prevent_crop)
|
141
|
+
|
142
|
+
def bbox(self, transformed=True, with_stroke=False):
|
143
|
+
image_width, image_height = self.active_image.size
|
144
|
+
matrix = self.active_matrix
|
145
|
+
x0, y0 = matrix.point_in_matrix_space((0, 0))
|
146
|
+
x1, y1 = matrix.point_in_matrix_space((image_width, image_height))
|
147
|
+
x2, y2 = matrix.point_in_matrix_space((0, image_height))
|
148
|
+
x3, y3 = matrix.point_in_matrix_space((image_width, 0))
|
149
|
+
return (
|
150
|
+
min(x0, x1, x2, x3),
|
151
|
+
min(y0, y1, y2, y3),
|
152
|
+
max(x0, x1, x2, x3),
|
153
|
+
max(y0, y1, y2, y3),
|
154
|
+
)
|
155
|
+
|
156
|
+
def default_map(self, default_map=None):
|
157
|
+
default_map = super().default_map(default_map=default_map)
|
158
|
+
default_map.update(self.__dict__)
|
159
|
+
image = self.active_image
|
160
|
+
default_map["width"] = image.width
|
161
|
+
default_map["height"] = image.height
|
162
|
+
default_map["element_type"] = "Image"
|
163
|
+
return default_map
|
164
|
+
|
165
|
+
def can_drop(self, drag_node):
|
166
|
+
# Dragging element into element.
|
167
|
+
return bool(
|
168
|
+
hasattr(drag_node, "as_geometry") or
|
169
|
+
hasattr(drag_node, "as_image") or
|
170
|
+
(drag_node.type.startswith("op ") and drag_node.type != "op dots") or
|
171
|
+
drag_node.type in ("file", "group")
|
172
|
+
)
|
173
|
+
|
174
|
+
def drop(self, drag_node, modify=True, flag=False):
|
175
|
+
# Dragging element into element.
|
176
|
+
if not self.can_drop(drag_node):
|
177
|
+
return False
|
178
|
+
if hasattr(drag_node, "as_geometry") or hasattr(drag_node, "as_image") or drag_node.type in ("file", "group"):
|
179
|
+
if modify:
|
180
|
+
self.insert_sibling(drag_node)
|
181
|
+
return True
|
182
|
+
elif drag_node.type.startswith("op"):
|
183
|
+
# If we drag an operation to this node,
|
184
|
+
# then we will reverse the game
|
185
|
+
return drag_node.drop(self, modify=modify, flag=flag)
|
186
|
+
return False
|
187
|
+
|
188
|
+
def revalidate_points(self):
|
189
|
+
bounds = self.bounds
|
190
|
+
if bounds is None:
|
191
|
+
return
|
192
|
+
if len(self._points) < 9:
|
193
|
+
self._points.extend([None] * (9 - len(self._points)))
|
194
|
+
self._points[0] = [bounds[0], bounds[1], "bounds top_left"]
|
195
|
+
self._points[1] = [bounds[2], bounds[1], "bounds top_right"]
|
196
|
+
self._points[2] = [bounds[0], bounds[3], "bounds bottom_left"]
|
197
|
+
self._points[3] = [bounds[2], bounds[3], "bounds bottom_right"]
|
198
|
+
cx = (bounds[0] + bounds[2]) / 2
|
199
|
+
cy = (bounds[1] + bounds[3]) / 2
|
200
|
+
self._points[4] = [cx, cy, "bounds center_center"]
|
201
|
+
self._points[5] = [cx, bounds[1], "bounds top_center"]
|
202
|
+
self._points[6] = [cx, bounds[3], "bounds bottom_center"]
|
203
|
+
self._points[7] = [bounds[0], cy, "bounds center_left"]
|
204
|
+
self._points[8] = [bounds[2], cy, "bounds center_right"]
|
205
|
+
|
206
|
+
def update_point(self, index, point):
|
207
|
+
return False
|
208
|
+
|
209
|
+
def add_point(self, point, index=None):
|
210
|
+
return False
|
211
|
+
|
212
|
+
def update(self, context):
|
213
|
+
"""
|
214
|
+
Update kicks off the image processing thread, which performs RasterWizard script operations on the image node.
|
215
|
+
|
216
|
+
The text should be displayed in the scene by the renderer. And any additional changes will be processed
|
217
|
+
until the new processed image is completed.
|
218
|
+
|
219
|
+
@param context:
|
220
|
+
@return:
|
221
|
+
"""
|
222
|
+
self._needs_update = True
|
223
|
+
if context is not None:
|
224
|
+
self.message = "Processing..."
|
225
|
+
context.signal("refresh_scene", "Scene")
|
226
|
+
if self._update_thread is None:
|
227
|
+
|
228
|
+
def clear(result):
|
229
|
+
self._needs_update = False
|
230
|
+
self._update_thread = None
|
231
|
+
if context is not None:
|
232
|
+
if self._process_image_failed:
|
233
|
+
self.message = "Process image could not exist in memory."
|
234
|
+
else:
|
235
|
+
self.message = None
|
236
|
+
context.signal("refresh_scene", "Scene")
|
237
|
+
context.signal("image_updated", self)
|
238
|
+
|
239
|
+
self._processed_image = None
|
240
|
+
# self.processed_matrix = None
|
241
|
+
if context is None:
|
242
|
+
# Direct execution
|
243
|
+
self._needs_update = False
|
244
|
+
# Calculate scene step_x, step_y values
|
245
|
+
step = UNITS_PER_INCH / self.dpi
|
246
|
+
step_x = step
|
247
|
+
step_y = step
|
248
|
+
self.process_image(step_x, step_y, not self.prevent_crop)
|
249
|
+
# Unset cache.
|
250
|
+
self._cache = None
|
251
|
+
else:
|
252
|
+
self._update_thread = context.threaded(
|
253
|
+
self._process_image_thread, result=clear, daemon=True
|
254
|
+
)
|
255
|
+
|
256
|
+
def _process_image_thread(self):
|
257
|
+
"""
|
258
|
+
The function deletes the caches and processes the image until it no longer needs updating.
|
259
|
+
|
260
|
+
@return:
|
261
|
+
"""
|
262
|
+
while self._needs_update:
|
263
|
+
self._needs_update = False
|
264
|
+
# Calculate scene step_x, step_y values
|
265
|
+
step = UNITS_PER_INCH / self.dpi
|
266
|
+
step_x = step
|
267
|
+
step_y = step
|
268
|
+
self.process_image(step_x, step_y, not self.prevent_crop)
|
269
|
+
# Unset cache.
|
270
|
+
self._cache = None
|
271
|
+
|
272
|
+
def process_image(self, step_x=None, step_y=None, crop=True):
|
273
|
+
"""
|
274
|
+
SVG matrices are defined as follows.
|
275
|
+
[a c e]
|
276
|
+
[b d f]
|
277
|
+
|
278
|
+
Pil requires a, c, e, b, d, f accordingly.
|
279
|
+
|
280
|
+
As of 0.7.2 this converts the image to "L" as part of the process.
|
281
|
+
|
282
|
+
There is a small amount of slop at the edge of converted images sometimes, so it's essential
|
283
|
+
to mark the image as inverted if black should be treated as empty pixels. The scaled down image
|
284
|
+
cannot lose the edge pixels since they could be important, but also dim may not be a multiple
|
285
|
+
of step level which requires an introduced empty edge pixel to be added.
|
286
|
+
"""
|
287
|
+
|
288
|
+
from PIL import Image
|
289
|
+
|
290
|
+
if step_x is None:
|
291
|
+
step_x = self.step_x
|
292
|
+
if step_y is None:
|
293
|
+
step_y = self.step_y
|
294
|
+
try:
|
295
|
+
actualized_matrix, image = self._process_image(step_x, step_y, crop=crop)
|
296
|
+
self._processed_matrix = actualized_matrix * ~self.matrix
|
297
|
+
self._processed_image = image
|
298
|
+
self._process_image_failed = False
|
299
|
+
bb = self.bbox()
|
300
|
+
self._bounds = bb
|
301
|
+
self._paint_bounds = bb
|
302
|
+
except (MemoryError, Image.DecompressionBombError, ValueError):
|
303
|
+
# Memory error if creating requires too much memory.
|
304
|
+
# DecompressionBomb if over 272 megapixels.
|
305
|
+
# ValueError if bounds are NaN.
|
306
|
+
self._process_image_failed = True
|
307
|
+
self.updated()
|
308
|
+
|
309
|
+
@property
|
310
|
+
def opaque_image(self):
|
311
|
+
from PIL import Image
|
312
|
+
|
313
|
+
img = self.image
|
314
|
+
if img is not None:
|
315
|
+
if img.mode == "RGBA":
|
316
|
+
r, g, b, a = img.split()
|
317
|
+
background = Image.new("RGB", img.size, "white")
|
318
|
+
background.paste(img, mask=a)
|
319
|
+
img = background
|
320
|
+
return img
|
321
|
+
|
322
|
+
def _convert_image_to_grayscale(self, image):
|
323
|
+
# Convert image to L type.
|
324
|
+
if image.mode != "L":
|
325
|
+
# Precalculate RGB for L conversion.
|
326
|
+
# if self.red is None:
|
327
|
+
# self.red = 1
|
328
|
+
if self.red is None or self.green is None or self.blue is None:
|
329
|
+
r = 1
|
330
|
+
g = 1
|
331
|
+
b = 1
|
332
|
+
else:
|
333
|
+
r = self.red * 0.299
|
334
|
+
g = self.green * 0.587
|
335
|
+
b = self.blue * 0.114
|
336
|
+
v = self.lightness
|
337
|
+
c = r + g + b
|
338
|
+
try:
|
339
|
+
c /= v
|
340
|
+
r = r / c
|
341
|
+
g = g / c
|
342
|
+
b = b / c
|
343
|
+
except ZeroDivisionError:
|
344
|
+
pass
|
345
|
+
image = image.convert("RGB")
|
346
|
+
image = image.convert("L", matrix=(r, g, b, 1.0))
|
347
|
+
return image
|
348
|
+
|
349
|
+
def _get_transparent_mask(self, image):
|
350
|
+
"""
|
351
|
+
Create Transparency Mask.
|
352
|
+
@param image:
|
353
|
+
@return:
|
354
|
+
"""
|
355
|
+
if image is None:
|
356
|
+
return None
|
357
|
+
if "transparency" in image.info:
|
358
|
+
image = image.convert("RGBA")
|
359
|
+
try:
|
360
|
+
return image.getchannel("A").point(lambda e: 255 - e)
|
361
|
+
except ValueError:
|
362
|
+
return None
|
363
|
+
|
364
|
+
def _apply_mask(self, image, mask, reject_color=None):
|
365
|
+
"""
|
366
|
+
Fill in original image with reject pixels.
|
367
|
+
|
368
|
+
@param image: Image to be masked off.
|
369
|
+
@param mask: Mask to apply to image
|
370
|
+
@param reject_color: Optional specified reject color override. Reject is usually "white" or black if inverted.
|
371
|
+
@return: image with mask pixels filled in with reject pixels
|
372
|
+
"""
|
373
|
+
if not mask:
|
374
|
+
return image
|
375
|
+
if reject_color is None:
|
376
|
+
reject_color = "black" if self.invert else "white"
|
377
|
+
from PIL import Image
|
378
|
+
|
379
|
+
background = image.copy()
|
380
|
+
reject = Image.new("L", image.size, reject_color)
|
381
|
+
background.paste(reject, mask=mask)
|
382
|
+
return background
|
383
|
+
|
384
|
+
def _get_crop_box(self, image):
|
385
|
+
"""
|
386
|
+
Get the bbox cutting off the reject edges. The reject edges depend on the image's invert setting.
|
387
|
+
@param image: Image to get crop box for.
|
388
|
+
@return:
|
389
|
+
"""
|
390
|
+
try:
|
391
|
+
if self.invert:
|
392
|
+
return image.getbbox()
|
393
|
+
else:
|
394
|
+
return image.point(lambda e: 255 - e).getbbox()
|
395
|
+
except ValueError:
|
396
|
+
return None
|
397
|
+
|
398
|
+
def _process_script(self, image):
|
399
|
+
"""
|
400
|
+
Process actual raster script operations. Any required grayscale, inversion, and masking will already have
|
401
|
+
occurred. If there were reject pixels before they will be masked off after this process.
|
402
|
+
|
403
|
+
@param image: image to process with self.operation script.
|
404
|
+
|
405
|
+
@return: processed image
|
406
|
+
"""
|
407
|
+
from PIL import ImageEnhance, ImageFilter, ImageOps
|
408
|
+
|
409
|
+
overall_left = 0
|
410
|
+
overall_top = 0
|
411
|
+
overall_right, overall_bottom = image.size
|
412
|
+
for op in self.operations:
|
413
|
+
name = op["name"]
|
414
|
+
if name == "resample":
|
415
|
+
# This is just a reminder, that while this may still appear in the scripts it is intentionally
|
416
|
+
# ignored (or needs to be revised with the upcoming appearance of passthrough) as it is not
|
417
|
+
# serving the purpose of the past
|
418
|
+
continue
|
419
|
+
if name == "crop":
|
420
|
+
try:
|
421
|
+
if op["enable"] and op["bounds"] is not None:
|
422
|
+
crop = op["bounds"]
|
423
|
+
left = int(crop[0])
|
424
|
+
upper = int(crop[1])
|
425
|
+
right = int(crop[2])
|
426
|
+
lower = int(crop[3])
|
427
|
+
w, h = image.size
|
428
|
+
overall_left += left
|
429
|
+
overall_top += upper
|
430
|
+
overall_right -= w - right
|
431
|
+
overall_bottom -= h - lower
|
432
|
+
image = image.crop((left, upper, right, lower))
|
433
|
+
except KeyError:
|
434
|
+
pass
|
435
|
+
elif name == "edge_enhance":
|
436
|
+
try:
|
437
|
+
if op["enable"]:
|
438
|
+
if image.mode == "P":
|
439
|
+
image = image.convert("L")
|
440
|
+
image = image.filter(filter=ImageFilter.EDGE_ENHANCE)
|
441
|
+
except KeyError:
|
442
|
+
pass
|
443
|
+
elif name == "auto_contrast":
|
444
|
+
try:
|
445
|
+
if op["enable"]:
|
446
|
+
if image.mode not in ("RGB", "L"):
|
447
|
+
# Auto-contrast raises NotImplementedError if P
|
448
|
+
# Auto-contrast raises OSError if not RGB, L.
|
449
|
+
image = image.convert("L")
|
450
|
+
image = ImageOps.autocontrast(image, cutoff=op["cutoff"])
|
451
|
+
except KeyError:
|
452
|
+
pass
|
453
|
+
elif name == "tone":
|
454
|
+
try:
|
455
|
+
if op["enable"] and op["values"] is not None:
|
456
|
+
if image.mode == "L":
|
457
|
+
image = image.convert("P")
|
458
|
+
tone_values = op["values"]
|
459
|
+
if op["type"] == "spline":
|
460
|
+
spline = ImageProcessedNode.spline(tone_values)
|
461
|
+
else:
|
462
|
+
tone_values = [q for q in tone_values if q is not None]
|
463
|
+
spline = ImageProcessedNode.line(tone_values)
|
464
|
+
if len(spline) < 256:
|
465
|
+
spline.extend([255] * (256 - len(spline)))
|
466
|
+
if len(spline) > 256:
|
467
|
+
spline = spline[:256]
|
468
|
+
image = image.point(spline)
|
469
|
+
if image.mode != "L":
|
470
|
+
image = image.convert("L")
|
471
|
+
except KeyError:
|
472
|
+
pass
|
473
|
+
elif name == "contrast":
|
474
|
+
try:
|
475
|
+
if op["enable"]:
|
476
|
+
if op["contrast"] is not None and op["brightness"] is not None:
|
477
|
+
contrast = ImageEnhance.Contrast(image)
|
478
|
+
c = (op["contrast"] + 128.0) / 128.0
|
479
|
+
image = contrast.enhance(c)
|
480
|
+
|
481
|
+
brightness = ImageEnhance.Brightness(image)
|
482
|
+
b = (op["brightness"] + 128.0) / 128.0
|
483
|
+
image = brightness.enhance(b)
|
484
|
+
except KeyError:
|
485
|
+
pass
|
486
|
+
elif name == "gamma":
|
487
|
+
try:
|
488
|
+
if op["enable"] and op["factor"] is not None:
|
489
|
+
if image.mode == "L":
|
490
|
+
gamma_factor = float(op["factor"])
|
491
|
+
|
492
|
+
def crimp(px):
|
493
|
+
px = int(round(px))
|
494
|
+
if px < 0:
|
495
|
+
return 0
|
496
|
+
if px > 255:
|
497
|
+
return 255
|
498
|
+
return px
|
499
|
+
|
500
|
+
if gamma_factor == 0:
|
501
|
+
gamma_lut = [0] * 256
|
502
|
+
else:
|
503
|
+
gamma_lut = [
|
504
|
+
crimp(pow(i / 255, (1.0 / gamma_factor)) * 255)
|
505
|
+
for i in range(256)
|
506
|
+
]
|
507
|
+
image = image.point(gamma_lut)
|
508
|
+
if image.mode != "L":
|
509
|
+
image = image.convert("L")
|
510
|
+
except KeyError:
|
511
|
+
pass
|
512
|
+
elif name == "unsharp_mask":
|
513
|
+
try:
|
514
|
+
if (
|
515
|
+
op["enable"]
|
516
|
+
and op["percent"] is not None
|
517
|
+
and op["radius"] is not None
|
518
|
+
and op["threshold"] is not None
|
519
|
+
):
|
520
|
+
unsharp = ImageFilter.UnsharpMask(
|
521
|
+
radius=op["radius"],
|
522
|
+
percent=op["percent"],
|
523
|
+
threshold=op["threshold"],
|
524
|
+
)
|
525
|
+
image = image.filter(unsharp)
|
526
|
+
except (KeyError, ValueError): # Value error if wrong type of image.
|
527
|
+
pass
|
528
|
+
elif name == "halftone":
|
529
|
+
try:
|
530
|
+
if op["enable"]:
|
531
|
+
image = RasterScripts.halftone(
|
532
|
+
image,
|
533
|
+
sample=op["sample"],
|
534
|
+
angle=op["angle"],
|
535
|
+
oversample=op["oversample"],
|
536
|
+
black=op["black"],
|
537
|
+
)
|
538
|
+
except KeyError:
|
539
|
+
pass
|
540
|
+
elif name == "dither":
|
541
|
+
# Set dither
|
542
|
+
try:
|
543
|
+
if op["enable"] and op["type"] is not None:
|
544
|
+
self.dither_type = op["type"]
|
545
|
+
self.dither = True
|
546
|
+
else:
|
547
|
+
# Takes precedence
|
548
|
+
self.dither = False
|
549
|
+
# image = self._apply_dither(image)
|
550
|
+
except KeyError:
|
551
|
+
pass
|
552
|
+
else:
|
553
|
+
# print(f"Unknown operation in raster-script: {name}")
|
554
|
+
continue
|
555
|
+
return image, (overall_left, overall_top, overall_right, overall_bottom)
|
556
|
+
|
557
|
+
def _apply_dither(self, image):
|
558
|
+
"""
|
559
|
+
Dither image to 1 bit. Floyd-Steinberg is performed by Pillow, other dithers require custom code.
|
560
|
+
|
561
|
+
@param image: grayscale image to dither.
|
562
|
+
@return: 1 bit dithered image
|
563
|
+
"""
|
564
|
+
from meerk40t.image.imagetools import dither
|
565
|
+
|
566
|
+
if self.dither and self.dither_type is not None:
|
567
|
+
if self.dither_type != "Floyd-Steinberg":
|
568
|
+
image = dither(image, self.dither_type)
|
569
|
+
image = image.convert("1")
|
570
|
+
return image
|
571
|
+
|
572
|
+
def _process_image(self, step_x, step_y, crop=True):
|
573
|
+
"""
|
574
|
+
This core code replaces the older actualize and rasterwizard functionalities. It should convert the image to
|
575
|
+
a post-processed form with resulting post-process matrix.
|
576
|
+
|
577
|
+
@param crop: Should the unneeded edges be cropped as part of this process. The need for the edge is determined
|
578
|
+
by the color and the state of the self.invert attribute.
|
579
|
+
@return:
|
580
|
+
"""
|
581
|
+
from PIL import Image, ImageOps
|
582
|
+
|
583
|
+
image = self.image
|
584
|
+
|
585
|
+
transparent_mask = self._get_transparent_mask(image)
|
586
|
+
|
587
|
+
image = self._convert_image_to_grayscale(self.opaque_image)
|
588
|
+
|
589
|
+
image = self._apply_mask(image, transparent_mask)
|
590
|
+
|
591
|
+
# Calculate image box.
|
592
|
+
box = None
|
593
|
+
if crop:
|
594
|
+
box = self._get_crop_box(image)
|
595
|
+
if box is None:
|
596
|
+
# If box is entirely white, bbox caused value error, or crop not set.
|
597
|
+
box = (0, 0, image.width, image.height)
|
598
|
+
|
599
|
+
transform_matrix = copy(self.matrix) # Prevent Knock-on effect.
|
600
|
+
|
601
|
+
# Find the boundary points of the rotated box edges.
|
602
|
+
boundary_points = [
|
603
|
+
transform_matrix.point_in_matrix_space([box[0], box[1]]), # Top-left
|
604
|
+
transform_matrix.point_in_matrix_space([box[2], box[1]]), # Top-right
|
605
|
+
transform_matrix.point_in_matrix_space([box[0], box[3]]), # Bottom-left
|
606
|
+
transform_matrix.point_in_matrix_space([box[2], box[3]]), # Bottom-right
|
607
|
+
]
|
608
|
+
xs = [e[0] for e in boundary_points]
|
609
|
+
ys = [e[1] for e in boundary_points]
|
610
|
+
|
611
|
+
# bbox here is expanded matrix size of box.
|
612
|
+
step_scale_x = 1 / float(step_x)
|
613
|
+
step_scale_y = 1 / float(step_y)
|
614
|
+
|
615
|
+
bbox = min(xs), min(ys), max(xs), max(ys)
|
616
|
+
|
617
|
+
image_width = ceil(bbox[2] * step_scale_x) - floor(bbox[0] * step_scale_x)
|
618
|
+
image_height = ceil(bbox[3] * step_scale_y) - floor(bbox[1] * step_scale_y)
|
619
|
+
tx = bbox[0]
|
620
|
+
ty = bbox[1]
|
621
|
+
transform_matrix.post_translate(-tx, -ty)
|
622
|
+
transform_matrix.post_scale(step_scale_x, step_scale_y)
|
623
|
+
if step_y < 0:
|
624
|
+
# If step_y is negative, translate
|
625
|
+
transform_matrix.post_translate(0, image_height)
|
626
|
+
if step_x < 0:
|
627
|
+
# If step_x is negative, translate
|
628
|
+
transform_matrix.post_translate(image_width, 0)
|
629
|
+
|
630
|
+
try:
|
631
|
+
transform_matrix.inverse()
|
632
|
+
except ZeroDivisionError:
|
633
|
+
# malformed matrix, scale=0 or something.
|
634
|
+
transform_matrix.reset()
|
635
|
+
|
636
|
+
# Perform image transform if needed.
|
637
|
+
if (
|
638
|
+
self.matrix.a != step_x
|
639
|
+
or self.matrix.b != 0.0
|
640
|
+
or self.matrix.c != 0.0
|
641
|
+
or self.matrix.d != step_y
|
642
|
+
):
|
643
|
+
if image_height <= 0:
|
644
|
+
image_height = 1
|
645
|
+
if image_width <= 0:
|
646
|
+
image_width = 1
|
647
|
+
image = image.transform(
|
648
|
+
(image_width, image_height),
|
649
|
+
Image.AFFINE,
|
650
|
+
(
|
651
|
+
transform_matrix.a,
|
652
|
+
transform_matrix.c,
|
653
|
+
transform_matrix.e,
|
654
|
+
transform_matrix.b,
|
655
|
+
transform_matrix.d,
|
656
|
+
transform_matrix.f,
|
657
|
+
),
|
658
|
+
resample=Image.BICUBIC,
|
659
|
+
fillcolor="black" if self.invert else "white",
|
660
|
+
)
|
661
|
+
actualized_matrix = Matrix()
|
662
|
+
|
663
|
+
# If crop applies, apply crop.
|
664
|
+
if crop:
|
665
|
+
box = self._get_crop_box(image)
|
666
|
+
if box is not None:
|
667
|
+
width = box[2] - box[0]
|
668
|
+
height = box[3] - box[1]
|
669
|
+
if width != image.width or height != image.height:
|
670
|
+
image = image.crop(box)
|
671
|
+
actualized_matrix.post_translate(box[0], box[1])
|
672
|
+
|
673
|
+
if step_y < 0:
|
674
|
+
# if step_y is negative, translate.
|
675
|
+
actualized_matrix.post_translate(0, -image_height)
|
676
|
+
if step_x < 0:
|
677
|
+
# if step_x is negative, translate.
|
678
|
+
actualized_matrix.post_translate(-image_width, 0)
|
679
|
+
|
680
|
+
actualized_matrix.post_scale(step_x, step_y)
|
681
|
+
actualized_matrix.post_translate(tx, ty)
|
682
|
+
|
683
|
+
# Invert black to white if needed.
|
684
|
+
if self.invert:
|
685
|
+
image = ImageOps.invert(image)
|
686
|
+
|
687
|
+
# Find rejection mask of white pixels. (already inverted)
|
688
|
+
reject_mask = image.point(lambda e: 0 if e == 255 else 255)
|
689
|
+
|
690
|
+
image, newbounds = self._process_script(image)
|
691
|
+
# This may have again changed the size of the image (op crop)
|
692
|
+
# so we need to adjust the reject mask...
|
693
|
+
reject_mask = reject_mask.crop(newbounds)
|
694
|
+
|
695
|
+
background = Image.new("L", image.size, "white")
|
696
|
+
background.paste(image, mask=reject_mask)
|
697
|
+
image = background
|
698
|
+
|
699
|
+
image = self._apply_dither(image)
|
700
|
+
return actualized_matrix, image
|
701
|
+
|
702
|
+
@staticmethod
|
703
|
+
def line(p):
|
704
|
+
N = len(p) - 1
|
705
|
+
try:
|
706
|
+
m = [(p[i + 1][1] - p[i][1]) / (p[i + 1][0] - p[i][0]) for i in range(0, N)]
|
707
|
+
except ZeroDivisionError:
|
708
|
+
m = [1] * N
|
709
|
+
# b = y - mx
|
710
|
+
b = [p[i][1] - (m[i] * p[i][0]) for i in range(0, N)]
|
711
|
+
r = list()
|
712
|
+
for i in range(0, p[0][0]):
|
713
|
+
r.append(0)
|
714
|
+
for i in range(len(p) - 1):
|
715
|
+
x0 = p[i][0]
|
716
|
+
x1 = p[i + 1][0]
|
717
|
+
range_list = [int(round((m[i] * x) + b[i])) for x in range(x0, x1)]
|
718
|
+
r.extend(range_list)
|
719
|
+
for i in range(p[-1][0], 256):
|
720
|
+
r.append(255)
|
721
|
+
r.append(round(int(p[-1][1])))
|
722
|
+
return r
|
723
|
+
|
724
|
+
@staticmethod
|
725
|
+
def spline(p):
|
726
|
+
"""
|
727
|
+
Spline interpreter.
|
728
|
+
|
729
|
+
Returns all integer locations between different spline interpolation values
|
730
|
+
@param p: points to be quad spline interpolated.
|
731
|
+
@return: integer y values for given spline points.
|
732
|
+
"""
|
733
|
+
try:
|
734
|
+
N = len(p) - 1
|
735
|
+
w = [(p[i + 1][0] - p[i][0]) for i in range(0, N)]
|
736
|
+
h = [(p[i + 1][1] - p[i][1]) / w[i] for i in range(0, N)]
|
737
|
+
ftt = (
|
738
|
+
[0]
|
739
|
+
+ [3 * (h[i + 1] - h[i]) / (w[i + 1] + w[i]) for i in range(0, N - 1)]
|
740
|
+
+ [0]
|
741
|
+
)
|
742
|
+
A = [(ftt[i + 1] - ftt[i]) / (6 * w[i]) for i in range(0, N)]
|
743
|
+
B = [ftt[i] / 2 for i in range(0, N)]
|
744
|
+
C = [h[i] - w[i] * (ftt[i + 1] + 2 * ftt[i]) / 6 for i in range(0, N)]
|
745
|
+
D = [p[i][1] for i in range(0, N)]
|
746
|
+
except ZeroDivisionError:
|
747
|
+
return list(range(256))
|
748
|
+
r = list()
|
749
|
+
for i in range(0, p[0][0]):
|
750
|
+
r.append(0)
|
751
|
+
for i in range(len(p) - 1):
|
752
|
+
a = p[i][0]
|
753
|
+
b = p[i + 1][0]
|
754
|
+
r.extend(
|
755
|
+
int(
|
756
|
+
round(
|
757
|
+
A[i] * (x - a) ** 3
|
758
|
+
+ B[i] * (x - a) ** 2
|
759
|
+
+ C[i] * (x - a)
|
760
|
+
+ D[i]
|
761
|
+
)
|
762
|
+
)
|
763
|
+
for x in range(a, b)
|
764
|
+
)
|
765
|
+
for i in range(p[-1][0], 256):
|
766
|
+
r.append(255)
|
767
|
+
r.append(round(int(p[-1][1])))
|
768
|
+
return r
|
769
|
+
|
770
|
+
def as_path(self):
|
771
|
+
image_width, image_height = self.active_image.size
|
772
|
+
matrix = self.active_matrix
|
773
|
+
x0, y0 = matrix.point_in_matrix_space((0, 0))
|
774
|
+
x1, y1 = matrix.point_in_matrix_space((0, image_height))
|
775
|
+
x2, y2 = matrix.point_in_matrix_space((image_width, image_height))
|
776
|
+
x3, y3 = matrix.point_in_matrix_space((image_width, 0))
|
777
|
+
return abs(Path(Polygon((x0, y0), (x1, y1), (x2, y2), (x3, y3), (x0, y0))))
|