meerk40t 0.9.3001__py2.py3-none-any.whl → 0.9.7010__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +1195 -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 +1844 -1507
- meerk40t/core/elements/clipboard.py +229 -219
- meerk40t/core/elements/element_treeops.py +4561 -2837
- meerk40t/core/elements/element_types.py +125 -105
- meerk40t/core/elements/elements.py +4329 -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 +933 -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/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 +462 -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 +198 -0
- meerk40t/extra/ezd.py +1165 -1165
- meerk40t/extra/hershey.py +835 -340
- meerk40t/extra/imageactions.py +322 -316
- meerk40t/extra/inkscape.py +630 -622
- meerk40t/extra/lbrn.py +424 -424
- meerk40t/extra/outerworld.py +284 -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 +1081 -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 +170 -133
- meerk40t/gui/choicepropertypanel.py +1673 -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 +1430 -846
- meerk40t/gui/icons.py +3422 -2747
- meerk40t/gui/imagesplitter.py +555 -508
- meerk40t/gui/keymap.py +354 -344
- meerk40t/gui/laserpanel.py +892 -806
- meerk40t/gui/laserrender.py +1470 -1232
- meerk40t/gui/lasertoolpanel.py +805 -793
- meerk40t/gui/magnetoptions.py +436 -0
- meerk40t/gui/materialmanager.py +2917 -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 +494 -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 +2468 -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 +589 -346
- meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
- meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
- meerk40t/gui/scenewidgets/selectionwidget.py +2952 -2756
- meerk40t/gui/simpleui.py +357 -333
- meerk40t/gui/simulation.py +2431 -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 +591 -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 +160 -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 +1444 -1169
- meerk40t/gui/wxmmain.py +5578 -4112
- meerk40t/gui/wxmribbon.py +1591 -1076
- meerk40t/gui/wxmscene.py +1635 -1453
- meerk40t/gui/wxmtree.py +2410 -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 +2778 -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 +3809 -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 +102 -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 +390 -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 +672 -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 +940 -940
- meerk40t/tools/rasterplotter.py +1660 -547
- meerk40t/tools/shxparser.py +989 -901
- meerk40t/tools/ttfparser.py +726 -446
- meerk40t/tools/zinglplotter.py +595 -593
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/LICENSE +21 -21
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/METADATA +150 -139
- meerk40t-0.9.7010.dist-info/RECORD +445 -0
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/WHEEL +1 -1
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/top_level.txt +0 -1
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.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.7010.dist-info}/entry_points.txt +0 -0
@@ -1,912 +1,933 @@
|
|
1
|
-
"""
|
2
|
-
This adds console commands that deal with the creation of an offset
|
3
|
-
|
4
|
-
Minimal integration of the Clipper2 library by Angus Johnson
|
5
|
-
https://github.com/AngusJohnson/Clipper2
|
6
|
-
via the pyclipr library of Luke Parry
|
7
|
-
https://github.com/drlukeparry/pyclipr
|
8
|
-
"""
|
9
|
-
|
10
|
-
|
11
|
-
def plugin(kernel, lifecycle=None):
|
12
|
-
_ = kernel.translation
|
13
|
-
if lifecycle == "invalidate":
|
14
|
-
try:
|
15
|
-
import pyclipr
|
16
|
-
except ImportError:
|
17
|
-
# print ("Clipper plugin could not load because pyclipr is not installed.")
|
18
|
-
return True
|
19
|
-
|
20
|
-
if lifecycle == "postboot":
|
21
|
-
init_commands(kernel)
|
22
|
-
|
23
|
-
|
24
|
-
def init_commands(kernel):
|
25
|
-
import numpy as np
|
26
|
-
import pyclipr
|
27
|
-
|
28
|
-
from meerk40t.core.node.node import Linejoin, Node
|
29
|
-
from meerk40t.core.units import UNITS_PER_PIXEL, Length
|
30
|
-
from meerk40t.tools.geomstr import Geomstr
|
31
|
-
|
32
|
-
self = kernel.elements
|
33
|
-
|
34
|
-
_ = kernel.translation
|
35
|
-
|
36
|
-
class ClipperOffset:
|
37
|
-
"""
|
38
|
-
Wraps around the pyclpr interface to clipper offset (inflate paths).
|
39
|
-
|
40
|
-
Typical invocation:
|
41
|
-
data = (node1, node2,)
|
42
|
-
offs = ClipperOffset(interpolation=500)
|
43
|
-
offs.add_nodes(data)
|
44
|
-
offset = float(Length("2mm"))
|
45
|
-
offs.process_data(offset, jointype="round", separate=False)
|
46
|
-
for geom in offs.result_geometry():
|
47
|
-
newnode = self.elem_branch.add(geometry=geom, type="elem path")
|
48
|
-
|
49
|
-
"""
|
50
|
-
|
51
|
-
def __init__(self, interpolation=None):
|
52
|
-
self.np_list = []
|
53
|
-
self.polygon_list = []
|
54
|
-
self._interpolation = None
|
55
|
-
self.interpolation = interpolation
|
56
|
-
self.any_open = False
|
57
|
-
# Create a clipper object
|
58
|
-
self.clipr_offset = pyclipr.ClipperOffset()
|
59
|
-
self.newpath = None
|
60
|
-
self._factor = 1000
|
61
|
-
self.tolerance = 0.25
|
62
|
-
self.factor = self._factor
|
63
|
-
|
64
|
-
# @staticmethod
|
65
|
-
# def testroutine():
|
66
|
-
# # Tuple definition of a path
|
67
|
-
# path = [(0.0, 0.), (100, 0), (100, 100), (0, 100), (0, 0)]
|
68
|
-
# # Create an offsetting object
|
69
|
-
# po = pyclipr.ClipperOffset()
|
70
|
-
# # Set the scale factor to convert to internal integer representation
|
71
|
-
# po.scaleFactor = int(1000)
|
72
|
-
# # add the path - ensuring to use Polygon for the endType argument
|
73
|
-
# npp = np.array(path)
|
74
|
-
# po.addPath(npp, pyclipr.JoinType.Miter, pyclipr.EndType.Polygon)
|
75
|
-
# # Apply the offsetting operation using a delta.
|
76
|
-
# offsetSquare = po.execute(10.0)
|
77
|
-
# print ("test for polygon...")
|
78
|
-
# print (npp)
|
79
|
-
# print (offsetSquare)
|
80
|
-
# print ("done...")
|
81
|
-
# po.clear()
|
82
|
-
# path=[ (100, 100), (1500, 100), (100, 1500), (1500, 1500) ]
|
83
|
-
# path = [(25801, 51602), (129005, 51602), (25801, 129005), (129005, 129005)]
|
84
|
-
# po.scaleFactor = int(1000)
|
85
|
-
# # add the path - ensuring to use Polygon for the endType argument
|
86
|
-
# npp = np.array(path)
|
87
|
-
# po.addPath(npp, pyclipr.JoinType.Miter, pyclipr.EndType.Square)
|
88
|
-
# # Apply the offsetting operation using a delta.
|
89
|
-
# offsetSquare = po.execute(10.0)
|
90
|
-
# print ("test for polyline...")
|
91
|
-
# print (npp)
|
92
|
-
# print (offsetSquare)
|
93
|
-
# print ("done...")
|
94
|
-
|
95
|
-
@property
|
96
|
-
def interpolation(self):
|
97
|
-
return self._interpolation
|
98
|
-
|
99
|
-
@interpolation.setter
|
100
|
-
def interpolation(self, value):
|
101
|
-
if value is None:
|
102
|
-
value = 500
|
103
|
-
self._interpolation =
|
104
|
-
|
105
|
-
@property
|
106
|
-
def factor(self):
|
107
|
-
return self._factor
|
108
|
-
|
109
|
-
@factor.setter
|
110
|
-
def factor(self, value):
|
111
|
-
self._factor = value
|
112
|
-
self.clipr_offset.scaleFactor = self._factor
|
113
|
-
|
114
|
-
def clear(self):
|
115
|
-
self.np_list = []
|
116
|
-
self.polygon_list = []
|
117
|
-
|
118
|
-
def add_geometries(self, geomlist):
|
119
|
-
for g in geomlist:
|
120
|
-
for subg in g.as_contiguous():
|
121
|
-
node_points = list(subg.as_interpolated_points(self.interpolation))
|
122
|
-
flag = subg.is_closed()
|
123
|
-
# print (node_points, flag)
|
124
|
-
self.np_list.append(node_points)
|
125
|
-
self.polygon_list.append(flag)
|
126
|
-
|
127
|
-
def add_nodes(self, nodelist):
|
128
|
-
# breaks down the path to a list of subgeometries.
|
129
|
-
self.clear()
|
130
|
-
# Set the scale factor to convert to internal integer representation
|
131
|
-
# As mks internal variable representation is already based on tats
|
132
|
-
# that should not be necessary
|
133
|
-
bounds = Node.union_bounds(nodelist)
|
134
|
-
factor1 = 1000
|
135
|
-
factor2 = 1
|
136
|
-
# Structures below 500 tats sidelength are ignored...
|
137
|
-
if bounds[2] > 100000 or bounds[3] > 100000:
|
138
|
-
factor1 = 1
|
139
|
-
factor2 = 1000
|
140
|
-
elif bounds[2] > 10000 or bounds[3] > 10000:
|
141
|
-
factor1 = 10
|
142
|
-
factor2 = 100
|
143
|
-
elif bounds[2] > 1000 or bounds[3] > 1000:
|
144
|
-
factor1 = 100
|
145
|
-
factor2 = 10
|
146
|
-
self.factor = factor1
|
147
|
-
self.tolerance = 0.5 * factor2 * 0.5 * factor2
|
148
|
-
|
149
|
-
geom_list = []
|
150
|
-
for node in nodelist:
|
151
|
-
# print (f"Looking at {node.type} - {node.
|
152
|
-
if hasattr(node, "as_geometry"):
|
153
|
-
# Let's get list of points with the
|
154
|
-
# required interpolation density
|
155
|
-
g = node.as_geometry()
|
156
|
-
geom_list.append(g)
|
157
|
-
else:
|
158
|
-
bb = node.bounds
|
159
|
-
if bb is None:
|
160
|
-
# Node has no bounds or space, therefore no clipline.
|
161
|
-
continue
|
162
|
-
g = Geomstr.rect(
|
163
|
-
bb[0], bb[1], bb[2] - bb[0], bb[3] - bb[1], rx=0, ry=0
|
164
|
-
)
|
165
|
-
geom_list.append(g)
|
166
|
-
self.add_geometries(geom_list)
|
167
|
-
|
168
|
-
def add_path(self, path):
|
169
|
-
# breaks down the path to a list of subgeometries.
|
170
|
-
self.clear()
|
171
|
-
# Set the scale factor to convert to internal integer representation
|
172
|
-
# As mks internal variable representation is already based on tats
|
173
|
-
# that should not be necessary
|
174
|
-
bounds = path.bbox(transformed=True)
|
175
|
-
factor1 = 1000
|
176
|
-
if bounds[2] > 100000 or bounds[3] > 100000:
|
177
|
-
factor1 = 1
|
178
|
-
elif bounds[2] > 10000 or bounds[3] > 10000:
|
179
|
-
factor1 = 10
|
180
|
-
elif bounds[2] > 1000 or bounds[3] > 1000:
|
181
|
-
factor1 = 100
|
182
|
-
factor2 = 1000 / factor1
|
183
|
-
self.factor = factor1
|
184
|
-
self.tolerance = 0.5 * factor2 * 0.5 * factor2
|
185
|
-
geom_list = []
|
186
|
-
g = Geomstr.svg(path)
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
self.
|
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
|
-
self.
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
# print(newp)
|
242
|
-
|
243
|
-
|
244
|
-
# print (subp)
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
idx
|
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
|
-
result_list.append(pt[
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
result_list.append(
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
cag
|
307
|
-
cag.
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
self.
|
319
|
-
self.
|
320
|
-
self.
|
321
|
-
self.
|
322
|
-
self.
|
323
|
-
self.
|
324
|
-
|
325
|
-
self.
|
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
|
-
# print
|
358
|
-
# print (
|
359
|
-
# print (
|
360
|
-
# print ("
|
361
|
-
# print (
|
362
|
-
# print ("
|
363
|
-
# print (
|
364
|
-
# print ("
|
365
|
-
# print (
|
366
|
-
# print ("
|
367
|
-
# print (
|
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
|
-
|
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
|
-
|
627
|
-
|
628
|
-
help=_(
|
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
|
-
if
|
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
|
-
@self.
|
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
|
-
if
|
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
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
"
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
"
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
keep
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
1
|
+
"""
|
2
|
+
This adds console commands that deal with the creation of an offset
|
3
|
+
|
4
|
+
Minimal integration of the Clipper2 library by Angus Johnson
|
5
|
+
https://github.com/AngusJohnson/Clipper2
|
6
|
+
via the pyclipr library of Luke Parry
|
7
|
+
https://github.com/drlukeparry/pyclipr
|
8
|
+
"""
|
9
|
+
|
10
|
+
|
11
|
+
def plugin(kernel, lifecycle=None):
|
12
|
+
_ = kernel.translation
|
13
|
+
if lifecycle == "invalidate":
|
14
|
+
try:
|
15
|
+
import pyclipr
|
16
|
+
except ImportError:
|
17
|
+
# print ("Clipper plugin could not load because pyclipr is not installed.")
|
18
|
+
return True
|
19
|
+
|
20
|
+
if lifecycle == "postboot":
|
21
|
+
init_commands(kernel)
|
22
|
+
|
23
|
+
|
24
|
+
def init_commands(kernel):
|
25
|
+
import numpy as np
|
26
|
+
import pyclipr
|
27
|
+
|
28
|
+
from meerk40t.core.node.node import Linejoin, Node
|
29
|
+
from meerk40t.core.units import UNITS_PER_PIXEL, Length
|
30
|
+
from meerk40t.tools.geomstr import Geomstr
|
31
|
+
|
32
|
+
self = kernel.elements
|
33
|
+
|
34
|
+
_ = kernel.translation
|
35
|
+
|
36
|
+
class ClipperOffset:
|
37
|
+
"""
|
38
|
+
Wraps around the pyclpr interface to clipper offset (inflate paths).
|
39
|
+
|
40
|
+
Typical invocation:
|
41
|
+
data = (node1, node2,)
|
42
|
+
offs = ClipperOffset(interpolation=500)
|
43
|
+
offs.add_nodes(data)
|
44
|
+
offset = float(Length("2mm"))
|
45
|
+
offs.process_data(offset, jointype="round", separate=False)
|
46
|
+
for geom in offs.result_geometry():
|
47
|
+
newnode = self.elem_branch.add(geometry=geom, type="elem path")
|
48
|
+
|
49
|
+
"""
|
50
|
+
|
51
|
+
def __init__(self, interpolation=None):
|
52
|
+
self.np_list = []
|
53
|
+
self.polygon_list = []
|
54
|
+
self._interpolation = None
|
55
|
+
self.interpolation = interpolation
|
56
|
+
self.any_open = False
|
57
|
+
# Create a clipper object
|
58
|
+
self.clipr_offset = pyclipr.ClipperOffset()
|
59
|
+
self.newpath = None
|
60
|
+
self._factor = 1000
|
61
|
+
self.tolerance = 0.25
|
62
|
+
self.factor = self._factor
|
63
|
+
|
64
|
+
# @staticmethod
|
65
|
+
# def testroutine():
|
66
|
+
# # Tuple definition of a path
|
67
|
+
# path = [(0.0, 0.), (100, 0), (100, 100), (0, 100), (0, 0)]
|
68
|
+
# # Create an offsetting object
|
69
|
+
# po = pyclipr.ClipperOffset()
|
70
|
+
# # Set the scale factor to convert to internal integer representation
|
71
|
+
# po.scaleFactor = int(1000)
|
72
|
+
# # add the path - ensuring to use Polygon for the endType argument
|
73
|
+
# npp = np.array(path)
|
74
|
+
# po.addPath(npp, pyclipr.JoinType.Miter, pyclipr.EndType.Polygon)
|
75
|
+
# # Apply the offsetting operation using a delta.
|
76
|
+
# offsetSquare = po.execute(10.0)
|
77
|
+
# print ("test for polygon...")
|
78
|
+
# print (npp)
|
79
|
+
# print (offsetSquare)
|
80
|
+
# print ("done...")
|
81
|
+
# po.clear()
|
82
|
+
# path=[ (100, 100), (1500, 100), (100, 1500), (1500, 1500) ]
|
83
|
+
# path = [(25801, 51602), (129005, 51602), (25801, 129005), (129005, 129005)]
|
84
|
+
# po.scaleFactor = int(1000)
|
85
|
+
# # add the path - ensuring to use Polygon for the endType argument
|
86
|
+
# npp = np.array(path)
|
87
|
+
# po.addPath(npp, pyclipr.JoinType.Miter, pyclipr.EndType.Square)
|
88
|
+
# # Apply the offsetting operation using a delta.
|
89
|
+
# offsetSquare = po.execute(10.0)
|
90
|
+
# print ("test for polyline...")
|
91
|
+
# print (npp)
|
92
|
+
# print (offsetSquare)
|
93
|
+
# print ("done...")
|
94
|
+
|
95
|
+
@property
|
96
|
+
def interpolation(self):
|
97
|
+
return self._interpolation
|
98
|
+
|
99
|
+
@interpolation.setter
|
100
|
+
def interpolation(self, value):
|
101
|
+
if value is None:
|
102
|
+
value = 500
|
103
|
+
self._interpolation = value
|
104
|
+
|
105
|
+
@property
|
106
|
+
def factor(self):
|
107
|
+
return self._factor
|
108
|
+
|
109
|
+
@factor.setter
|
110
|
+
def factor(self, value):
|
111
|
+
self._factor = value
|
112
|
+
self.clipr_offset.scaleFactor = self._factor
|
113
|
+
|
114
|
+
def clear(self):
|
115
|
+
self.np_list = []
|
116
|
+
self.polygon_list = []
|
117
|
+
|
118
|
+
def add_geometries(self, geomlist):
|
119
|
+
for g in geomlist:
|
120
|
+
for subg in g.as_contiguous():
|
121
|
+
node_points = list(subg.as_interpolated_points(self.interpolation))
|
122
|
+
flag = subg.is_closed()
|
123
|
+
# print (node_points, flag)
|
124
|
+
self.np_list.append(node_points)
|
125
|
+
self.polygon_list.append(flag)
|
126
|
+
|
127
|
+
def add_nodes(self, nodelist):
|
128
|
+
# breaks down the path to a list of subgeometries.
|
129
|
+
self.clear()
|
130
|
+
# Set the scale factor to convert to internal integer representation
|
131
|
+
# As mks internal variable representation is already based on tats
|
132
|
+
# that should not be necessary
|
133
|
+
bounds = Node.union_bounds(nodelist)
|
134
|
+
factor1 = 1000
|
135
|
+
factor2 = 1
|
136
|
+
# Structures below 500 tats sidelength are ignored...
|
137
|
+
if bounds[2] > 100000 or bounds[3] > 100000:
|
138
|
+
factor1 = 1
|
139
|
+
factor2 = 1000
|
140
|
+
elif bounds[2] > 10000 or bounds[3] > 10000:
|
141
|
+
factor1 = 10
|
142
|
+
factor2 = 100
|
143
|
+
elif bounds[2] > 1000 or bounds[3] > 1000:
|
144
|
+
factor1 = 100
|
145
|
+
factor2 = 10
|
146
|
+
self.factor = factor1
|
147
|
+
self.tolerance = 0.5 * factor2 * 0.5 * factor2
|
148
|
+
|
149
|
+
geom_list = []
|
150
|
+
for node in nodelist:
|
151
|
+
# print (f"Looking at {node.type} - {node.display_label()}")
|
152
|
+
if hasattr(node, "as_geometry"):
|
153
|
+
# Let's get list of points with the
|
154
|
+
# required interpolation density
|
155
|
+
g = node.as_geometry()
|
156
|
+
geom_list.append(g)
|
157
|
+
else:
|
158
|
+
bb = node.bounds
|
159
|
+
if bb is None:
|
160
|
+
# Node has no bounds or space, therefore no clipline.
|
161
|
+
continue
|
162
|
+
g = Geomstr.rect(
|
163
|
+
bb[0], bb[1], bb[2] - bb[0], bb[3] - bb[1], rx=0, ry=0
|
164
|
+
)
|
165
|
+
geom_list.append(g)
|
166
|
+
self.add_geometries(geom_list)
|
167
|
+
|
168
|
+
def add_path(self, path):
|
169
|
+
# breaks down the path to a list of subgeometries.
|
170
|
+
self.clear()
|
171
|
+
# Set the scale factor to convert to internal integer representation
|
172
|
+
# As mks internal variable representation is already based on tats
|
173
|
+
# that should not be necessary
|
174
|
+
bounds = path.bbox(transformed=True)
|
175
|
+
factor1 = 1000
|
176
|
+
if bounds[2] > 100000 or bounds[3] > 100000:
|
177
|
+
factor1 = 1
|
178
|
+
elif bounds[2] > 10000 or bounds[3] > 10000:
|
179
|
+
factor1 = 10
|
180
|
+
elif bounds[2] > 1000 or bounds[3] > 1000:
|
181
|
+
factor1 = 100
|
182
|
+
factor2 = 1000 / factor1
|
183
|
+
self.factor = factor1
|
184
|
+
self.tolerance = 0.5 * factor2 * 0.5 * factor2
|
185
|
+
geom_list = []
|
186
|
+
g = Geomstr.svg(path)
|
187
|
+
if g.index:
|
188
|
+
geom_list.append(g)
|
189
|
+
self.add_geometries(geom_list)
|
190
|
+
|
191
|
+
def process_data(self, offset, jointype="round", separate=False):
|
192
|
+
self.clipr_offset.clear()
|
193
|
+
self.newpath = None
|
194
|
+
if jointype.startswith("r"): # round
|
195
|
+
pyc_jointype = pyclipr.JoinType.Round
|
196
|
+
elif jointype.startswith("s"): # square
|
197
|
+
pyc_jointype = pyclipr.JoinType.Square
|
198
|
+
else:
|
199
|
+
pyc_jointype = pyclipr.JoinType.Miter
|
200
|
+
for node_points, is_polygon in zip(self.np_list, self.polygon_list):
|
201
|
+
# There may be a smarter way to do this, but geomstr
|
202
|
+
# provides an array of complex numbers. pyclipr on the other
|
203
|
+
# hand would like to have points as (x, y) and not as (x + y * 1j)
|
204
|
+
complex_array = np.array(node_points)
|
205
|
+
temp = np.column_stack((complex_array.real, complex_array.imag))
|
206
|
+
np_points = temp.astype(int)
|
207
|
+
|
208
|
+
# add the path - ensuring to use Polygon for the endType argument
|
209
|
+
|
210
|
+
if is_polygon:
|
211
|
+
pyc_endtype = pyclipr.EndType.Polygon
|
212
|
+
else:
|
213
|
+
pyc_endtype = pyclipr.EndType.Square
|
214
|
+
|
215
|
+
self.clipr_offset.addPath(np_points, pyc_jointype, pyc_endtype)
|
216
|
+
if separate:
|
217
|
+
# Apply the offsetting operation using a delta.
|
218
|
+
newp = self.clipr_offset.execute(offset)
|
219
|
+
if self.newpath is None:
|
220
|
+
self.newpath = list()
|
221
|
+
self.newpath.append(newp)
|
222
|
+
self.clipr_offset.clear()
|
223
|
+
|
224
|
+
if not separate:
|
225
|
+
# Apply the offsetting operation using a delta.
|
226
|
+
self.newpath = self.clipr_offset.execute(offset)
|
227
|
+
|
228
|
+
def result_geometry(self):
|
229
|
+
if len(self.newpath) == 0:
|
230
|
+
# print(f"Collapsed clipline for {node.type}:{node.display_label()}\n{np_points}")
|
231
|
+
return None
|
232
|
+
if isinstance(self.newpath[0], (tuple, list)):
|
233
|
+
# Can execute directly
|
234
|
+
target = self.newpath
|
235
|
+
else:
|
236
|
+
# Create a temporary list
|
237
|
+
target = (self.newpath,)
|
238
|
+
|
239
|
+
idx = 0
|
240
|
+
for newp in target:
|
241
|
+
# print (f"Type of newp: {type(newp).__name__}")
|
242
|
+
# print(newp)
|
243
|
+
for subp in newp:
|
244
|
+
# print (f"Type of subp: {type(subp).__name__}")
|
245
|
+
# print (subp)
|
246
|
+
result_list = []
|
247
|
+
pt_count = len(subp)
|
248
|
+
# print (f"{idx}#: {pt_count} pts")
|
249
|
+
idx += 1
|
250
|
+
if pt_count < 2:
|
251
|
+
continue
|
252
|
+
# Sometimes we get artifacts: a small array
|
253
|
+
# with very small structures.
|
254
|
+
# We try to identify and to discard them
|
255
|
+
# Structures below 500 tats sidelength are ignored...
|
256
|
+
maxd = 0
|
257
|
+
lastpt = None
|
258
|
+
had_error = False
|
259
|
+
for pt in subp:
|
260
|
+
if lastpt is not None:
|
261
|
+
try:
|
262
|
+
dx = abs(lastpt[0] - pt[0])
|
263
|
+
dy = abs(lastpt[1] - pt[1])
|
264
|
+
except IndexError:
|
265
|
+
# Invalid structure! Ignore
|
266
|
+
had_error = True
|
267
|
+
break
|
268
|
+
maxd += dx * dx + dy * dy
|
269
|
+
lastpt = pt
|
270
|
+
if maxd > self.tolerance:
|
271
|
+
break
|
272
|
+
|
273
|
+
# print (f"Substructure: {maxd:.3f} ({self.tolerance:.3f})")
|
274
|
+
if had_error or maxd < self.tolerance:
|
275
|
+
# print (f"Artifact ignored: {maxd:.3f}")
|
276
|
+
continue
|
277
|
+
|
278
|
+
for pt in subp:
|
279
|
+
result_list.append(pt[0])
|
280
|
+
result_list.append(pt[1])
|
281
|
+
try:
|
282
|
+
p1x = result_list[0]
|
283
|
+
p1y = result_list[1]
|
284
|
+
p2x = result_list[-2]
|
285
|
+
p2y = result_list[-1]
|
286
|
+
dx = abs(p1x - p2x)
|
287
|
+
dy = abs(p1y - p2y)
|
288
|
+
if dx > 10 or dy > 10:
|
289
|
+
result_list.append(p1x)
|
290
|
+
result_list.append(p1y)
|
291
|
+
except IndexError:
|
292
|
+
# channel(f"Invalid clipline for {node.type}:{node.display_label()}")
|
293
|
+
continue
|
294
|
+
geom = Geomstr.lines(*result_list)
|
295
|
+
yield geom
|
296
|
+
|
297
|
+
class ClipperCAG:
|
298
|
+
"""
|
299
|
+
Wraps around the pyclpr interface to clipper to run clip operations:
|
300
|
+
supported:
|
301
|
+
method: Union, Difference, Intersect, Xor
|
302
|
+
filltype: EvenOdd, Positive, Negative, NonZero
|
303
|
+
|
304
|
+
Typical invocation:
|
305
|
+
data = (node1, node2,)
|
306
|
+
cag = ClipperCAG(interpolation=500)
|
307
|
+
cag.add_nodes(data)
|
308
|
+
cag.process_data(method="union", filltype="EvenOdd")
|
309
|
+
geom = cag.result_geometry()
|
310
|
+
newnode = self.elem_branch.add(geometry=geom, type="elem path")
|
311
|
+
|
312
|
+
"""
|
313
|
+
|
314
|
+
def __init__(self, interpolation=None):
|
315
|
+
# Create a clipper object
|
316
|
+
self.clipr_clipper = pyclipr.Clipper()
|
317
|
+
|
318
|
+
self.np_list = []
|
319
|
+
self.polygon_list = []
|
320
|
+
self.first = 0
|
321
|
+
self._interpolation = None
|
322
|
+
self.interpolation = interpolation
|
323
|
+
self.any_open = False
|
324
|
+
self.newpath = None
|
325
|
+
self._factor = 1000
|
326
|
+
|
327
|
+
self.factor = self._factor
|
328
|
+
self.tolerance = 0.5 * 0.5
|
329
|
+
|
330
|
+
# @staticmethod
|
331
|
+
# def testroutine():
|
332
|
+
# # Tuple definition of a path
|
333
|
+
# path_clip = [(0.0, 0.), (0, 105.1234), (100, 105.1234), (100, 0), (0, 0)]
|
334
|
+
# open1 = False
|
335
|
+
# # path_subject = [(0, 0), (0, 50), (100, 50), (100, 0), (0,0)]
|
336
|
+
# path_subject = [(0, 0), (300, 300)]
|
337
|
+
# open2 = True
|
338
|
+
|
339
|
+
# # Create a clipping object
|
340
|
+
# pc = pyclipr.Clipper()
|
341
|
+
# pc.scaleFactor = int(1000)
|
342
|
+
|
343
|
+
# # Add the paths to the clipping object. Ensure the subject and clip arguments are set to differentiate
|
344
|
+
# # the paths during the Boolean operation. The final argument specifies if the path is
|
345
|
+
# # open.
|
346
|
+
# pc.addPath(np.array(path_subject), pyclipr.PathType.Subject, open2)
|
347
|
+
# pc.addPath(np.array(path_clip), pyclipr.PathType.Clip, open1)
|
348
|
+
|
349
|
+
# """ Test Polygon Clipping """
|
350
|
+
# # Below returns paths
|
351
|
+
# out1 = pc.execute(pyclipr.ClipType.Intersection, pyclipr.FillType.EvenOdd)
|
352
|
+
# out2 = pc.execute(pyclipr.ClipType.Union, pyclipr.FillType.EvenOdd)
|
353
|
+
# out3 = pc.execute(pyclipr.ClipType.Difference, pyclipr.FillType.EvenOdd)
|
354
|
+
# out4 = pc.execute(pyclipr.ClipType.Xor, pyclipr.FillType.EvenOdd)
|
355
|
+
# # Return open paths...
|
356
|
+
# out5 = pc.execute(pyclipr.ClipType.Union, pyclipr.FillType.EvenOdd, returnOpenPaths=True)
|
357
|
+
# print("In:")
|
358
|
+
# print (path_clip)
|
359
|
+
# print (path_subject)
|
360
|
+
# print ("intersect")
|
361
|
+
# print (out1)
|
362
|
+
# print ("union")
|
363
|
+
# print (out2)
|
364
|
+
# print ("difference")
|
365
|
+
# print (out3)
|
366
|
+
# print ("xor")
|
367
|
+
# print (out4)
|
368
|
+
# print ("union with open paths")
|
369
|
+
# print (out5)
|
370
|
+
|
371
|
+
@property
|
372
|
+
def interpolation(self):
|
373
|
+
return self._interpolation
|
374
|
+
|
375
|
+
@interpolation.setter
|
376
|
+
def interpolation(self, value):
|
377
|
+
if value is None:
|
378
|
+
value = 500
|
379
|
+
self._interpolation = value
|
380
|
+
|
381
|
+
@property
|
382
|
+
def factor(self):
|
383
|
+
return self._factor
|
384
|
+
|
385
|
+
@factor.setter
|
386
|
+
def factor(self, value):
|
387
|
+
self._factor = value
|
388
|
+
self.clipr_clipper.scaleFactor = self._factor
|
389
|
+
|
390
|
+
def clear(self):
|
391
|
+
self.np_list = []
|
392
|
+
self.polygon_list = []
|
393
|
+
self.first = 0
|
394
|
+
|
395
|
+
def add_nodes(self, nodelist):
|
396
|
+
# breaks down the path to a list of subgeometries.
|
397
|
+
self.clear()
|
398
|
+
# Set the scale factor to convert to internal integer representation
|
399
|
+
# As mks internal variable representation is already based on tats
|
400
|
+
# that should not be necessary
|
401
|
+
bounds = Node.union_bounds(nodelist)
|
402
|
+
factor1 = int(1000)
|
403
|
+
if bounds[2] > 100000 or bounds[3] > 100000:
|
404
|
+
factor1 = int(1)
|
405
|
+
elif bounds[2] > 10000 or bounds[3] > 10000:
|
406
|
+
factor1 = int(10)
|
407
|
+
elif bounds[2] > 1000 or bounds[3] > 1000:
|
408
|
+
factor1 = int(100)
|
409
|
+
factor2 = 1000 / factor1
|
410
|
+
self.factor = factor1
|
411
|
+
self.tolerance = 0.5 * factor2 * 0.5 * factor2
|
412
|
+
first = True
|
413
|
+
self.first = 0
|
414
|
+
for node in nodelist:
|
415
|
+
# print (f"Looking at {node.type} - {node.display_label()}")
|
416
|
+
if hasattr(node, "as_geometry"):
|
417
|
+
# Let's get list of points with the
|
418
|
+
# required interpolation density
|
419
|
+
g = node.as_geometry()
|
420
|
+
idx = 0
|
421
|
+
for subg in g.as_contiguous():
|
422
|
+
if not subg:
|
423
|
+
continue
|
424
|
+
node_points = list(
|
425
|
+
subg.as_interpolated_points(self.interpolation)
|
426
|
+
)
|
427
|
+
flag = subg.is_closed()
|
428
|
+
# print (node_points, flag)
|
429
|
+
self.np_list.append(node_points)
|
430
|
+
self.polygon_list.append(flag)
|
431
|
+
if first:
|
432
|
+
self.first += 1
|
433
|
+
# print (f"Adding structure #{idx} with {len(node_points)} pts")
|
434
|
+
idx += 1
|
435
|
+
else:
|
436
|
+
bb = node.bounds
|
437
|
+
if bb is None:
|
438
|
+
# Node has no bounds or space, therefore no clipline.
|
439
|
+
continue
|
440
|
+
node_points = (
|
441
|
+
bb[0] + bb[1] * 1j,
|
442
|
+
bb[0] + bb[3] * 1j,
|
443
|
+
bb[2] + bb[3] * 1j,
|
444
|
+
bb[2] + bb[1] * 1j,
|
445
|
+
bb[0] + bb[1] * 1j,
|
446
|
+
)
|
447
|
+
self.np_list.append(node_points)
|
448
|
+
self.polygon_list.append(True)
|
449
|
+
if first:
|
450
|
+
self.first += 1
|
451
|
+
|
452
|
+
first = False
|
453
|
+
|
454
|
+
def _add_data(self):
|
455
|
+
self.clipr_clipper.clear()
|
456
|
+
first = True
|
457
|
+
self.any_open = False
|
458
|
+
idx = 0
|
459
|
+
for node_points, is_polygon in zip(self.np_list, self.polygon_list):
|
460
|
+
# print (f"Add {'polygon' if is_polygon else 'polyline'}: {node_points}")
|
461
|
+
|
462
|
+
# There may be a smarter way to do this, but geomstr
|
463
|
+
# provides an array of complex numbers. pyclipr on the other
|
464
|
+
# hand would like to have points as (x, y) and not as (x + y * 1j)
|
465
|
+
complex_array = np.array(node_points)
|
466
|
+
temp = np.column_stack((complex_array.real, complex_array.imag))
|
467
|
+
np_points = temp.astype(int)
|
468
|
+
|
469
|
+
if idx < self.first:
|
470
|
+
pyc_pathtype = pyclipr.PathType.Subject
|
471
|
+
else:
|
472
|
+
pyc_pathtype = pyclipr.PathType.Clip
|
473
|
+
|
474
|
+
# print (f"Add path {pyc_pathtype} with {is_polygon}: {len(np_points)} pts")
|
475
|
+
if not is_polygon:
|
476
|
+
self.any_open = True
|
477
|
+
self.clipr_clipper.addPath(np_points, pyc_pathtype, not is_polygon)
|
478
|
+
idx += 1
|
479
|
+
|
480
|
+
def process_data(self, method, filltype):
|
481
|
+
self._add_data()
|
482
|
+
if method.startswith("d"):
|
483
|
+
pyc_method = pyclipr.ClipType.Difference
|
484
|
+
elif method.startswith("i"):
|
485
|
+
pyc_method = pyclipr.ClipType.Intersection
|
486
|
+
elif method.startswith("x"):
|
487
|
+
pyc_method = pyclipr.ClipType.Xor
|
488
|
+
else:
|
489
|
+
pyc_method = pyclipr.ClipType.Union
|
490
|
+
# Definition of enumerators has changed, so let#s try to be backwards compatible
|
491
|
+
if filltype.startswith("no") or filltype.startswith("z"):
|
492
|
+
try:
|
493
|
+
pyc_filltype = pyclipr.FillRule.NonZero
|
494
|
+
except AttributeError:
|
495
|
+
pyc_filltype = pyclipr.FillType.NonZero
|
496
|
+
elif filltype.startswith("p") or filltype.startswith("+"):
|
497
|
+
try:
|
498
|
+
pyc_filltype = pyclipr.FillRule.Positive
|
499
|
+
except AttributeError:
|
500
|
+
pyc_filltype = pyclipr.FillType.Positive
|
501
|
+
elif filltype.startswith("ne") or filltype.startswith("-"):
|
502
|
+
try:
|
503
|
+
pyc_filltype = pyclipr.FillRule.Negative
|
504
|
+
except AttributeError:
|
505
|
+
pyc_filltype = pyclipr.FillType.Negative
|
506
|
+
else:
|
507
|
+
try:
|
508
|
+
pyc_filltype = pyclipr.FillRule.EvenOdd
|
509
|
+
except AttributeError:
|
510
|
+
pyc_filltype = pyclipr.FillType.EvenOdd
|
511
|
+
|
512
|
+
if self.any_open and pyc_method in (pyclipr.ClipType.Union,):
|
513
|
+
self.newpath = self.clipr_clipper.execute(
|
514
|
+
pyc_method, pyc_filltype, returnOpenPaths=True
|
515
|
+
)
|
516
|
+
else:
|
517
|
+
self.newpath = self.clipr_clipper.execute(pyc_method, pyc_filltype)
|
518
|
+
|
519
|
+
def result_geometry(self):
|
520
|
+
if len(self.newpath) == 0:
|
521
|
+
# print(f"Collapsed clipline for {node.type}:{node.display_label()}\n{np_points}")
|
522
|
+
return None
|
523
|
+
if isinstance(self.newpath[0], (tuple, list)):
|
524
|
+
# Can execute directly
|
525
|
+
target = self.newpath
|
526
|
+
else:
|
527
|
+
# Create a temporary list
|
528
|
+
target = (self.newpath,)
|
529
|
+
|
530
|
+
idx = 0
|
531
|
+
allgeom = None
|
532
|
+
for newp in target:
|
533
|
+
# print (f"Type of newp: {type(newp).__name__}")
|
534
|
+
# print(newp)
|
535
|
+
for subp in newp:
|
536
|
+
# print (f"Type of subp: {type(subp).__name__}")
|
537
|
+
# print (subp)
|
538
|
+
result_list = []
|
539
|
+
pt_count = len(subp)
|
540
|
+
# print (f"{idx}#: {pt_count} pts")
|
541
|
+
idx += 1
|
542
|
+
if pt_count < 2:
|
543
|
+
continue
|
544
|
+
# Sometimes we get artifacts: a small array
|
545
|
+
# with very small structures.
|
546
|
+
# We try to identify and to discard them
|
547
|
+
# 500 x 500
|
548
|
+
maxd = 0
|
549
|
+
lastpt = None
|
550
|
+
had_error = False
|
551
|
+
for pt in subp:
|
552
|
+
if lastpt is not None:
|
553
|
+
try:
|
554
|
+
dx = abs(lastpt[0] - pt[0])
|
555
|
+
dy = abs(lastpt[1] - pt[1])
|
556
|
+
except IndexError:
|
557
|
+
# Invalid structure! Ignore
|
558
|
+
had_error = True
|
559
|
+
break
|
560
|
+
maxd += dx * dx + dy * dy
|
561
|
+
lastpt = pt
|
562
|
+
if maxd > self.tolerance:
|
563
|
+
break
|
564
|
+
|
565
|
+
if had_error or maxd < self.tolerance:
|
566
|
+
# print (f"Artifact ignored: {maxd:.3f}")
|
567
|
+
continue
|
568
|
+
|
569
|
+
for pt in subp:
|
570
|
+
result_list.append(pt[0])
|
571
|
+
result_list.append(pt[1])
|
572
|
+
try:
|
573
|
+
p1x = result_list[0]
|
574
|
+
p1y = result_list[1]
|
575
|
+
p2x = result_list[-2]
|
576
|
+
p2y = result_list[-1]
|
577
|
+
dx = abs(p1x - p2x)
|
578
|
+
dy = abs(p1y - p2y)
|
579
|
+
if dx > 10 or dy > 10:
|
580
|
+
result_list.append(p1x)
|
581
|
+
result_list.append(p1y)
|
582
|
+
except IndexError:
|
583
|
+
# channel(f"Invalid clipline for {node.type}:{node.display_label()}")
|
584
|
+
continue
|
585
|
+
geom = Geomstr.lines(*result_list)
|
586
|
+
if allgeom is None:
|
587
|
+
allgeom = geom
|
588
|
+
else:
|
589
|
+
# Add a end marker
|
590
|
+
allgeom.end()
|
591
|
+
allgeom.append(geom)
|
592
|
+
# print (geom)
|
593
|
+
yield allgeom
|
594
|
+
|
595
|
+
def offset_path(self, path, offset_value=0):
|
596
|
+
# As this oveloading a regular method in a class
|
597
|
+
# it needs to have the very same definition (including the class
|
598
|
+
# reference self)
|
599
|
+
offs = ClipperOffset(interpolation=500)
|
600
|
+
offs.add_path(path)
|
601
|
+
offs.process_data(offset_value, jointype="round", separate=False)
|
602
|
+
rp = None
|
603
|
+
# Attention geometry is already at device resolution, so we need to use a small tolerance
|
604
|
+
for geo in offs.result_geometry():
|
605
|
+
g = geo.simplify(tolerance=0.1)
|
606
|
+
if g is not None:
|
607
|
+
p = g.as_path()
|
608
|
+
if rp is None:
|
609
|
+
rp = p
|
610
|
+
else:
|
611
|
+
rp += p
|
612
|
+
if rp is None:
|
613
|
+
rp = path
|
614
|
+
return rp
|
615
|
+
|
616
|
+
classify_new = self.post_classify
|
617
|
+
|
618
|
+
# We are patching the class responsible for Cut nodes in general,
|
619
|
+
# so that any new instance of this class will be able to use the
|
620
|
+
# new functionality.
|
621
|
+
from meerk40t.core.node.op_cut import CutOpNode
|
622
|
+
|
623
|
+
CutOpNode.offset_routine = offset_path
|
624
|
+
|
625
|
+
@self.console_argument(
|
626
|
+
"offset",
|
627
|
+
type=str,
|
628
|
+
help=_(
|
629
|
+
"offset to line mm (positive values to left/outside, negative values to right/inside)"
|
630
|
+
),
|
631
|
+
)
|
632
|
+
@self.console_option(
|
633
|
+
"jointype", "j", type=str, help=_("join type: round, miter, square")
|
634
|
+
)
|
635
|
+
@self.console_option(
|
636
|
+
"separate",
|
637
|
+
"s",
|
638
|
+
action="store_true",
|
639
|
+
type=bool,
|
640
|
+
help=_("deal with subpaths separately"),
|
641
|
+
)
|
642
|
+
@self.console_option(
|
643
|
+
"interpolation", "i", type=int, help=_("interpolation points per segment")
|
644
|
+
)
|
645
|
+
@self.console_command(
|
646
|
+
"offset",
|
647
|
+
help=_("create an offset path for any of the given elements"),
|
648
|
+
input_type=(None, "elements"),
|
649
|
+
output_type="elements",
|
650
|
+
)
|
651
|
+
def element_offset_path(
|
652
|
+
command,
|
653
|
+
channel,
|
654
|
+
_,
|
655
|
+
offset=None,
|
656
|
+
jointype=None,
|
657
|
+
separate=None,
|
658
|
+
interpolation=None,
|
659
|
+
data=None,
|
660
|
+
post=None,
|
661
|
+
**kwargs,
|
662
|
+
):
|
663
|
+
if data is None:
|
664
|
+
data = list(self.elems(emphasized=True))
|
665
|
+
if len(data) == 0:
|
666
|
+
channel(_("No elements selected"))
|
667
|
+
return "elements", data
|
668
|
+
if interpolation is None:
|
669
|
+
interpolation = 500
|
670
|
+
if separate is None:
|
671
|
+
separate = False
|
672
|
+
if offset is None:
|
673
|
+
offset = 0
|
674
|
+
else:
|
675
|
+
try:
|
676
|
+
ll = Length(offset)
|
677
|
+
offset = float(ll)
|
678
|
+
except ValueError:
|
679
|
+
offset = 0
|
680
|
+
if offset == 0.0:
|
681
|
+
channel("Invalid offset, nothing to do")
|
682
|
+
return
|
683
|
+
if jointype is None:
|
684
|
+
jointype = "miter"
|
685
|
+
jointype = jointype.lower()
|
686
|
+
default_stroke = None
|
687
|
+
for node in data:
|
688
|
+
if hasattr(node, "stroke"):
|
689
|
+
default_stroke = node.stroke
|
690
|
+
break
|
691
|
+
if default_stroke is None:
|
692
|
+
default_stroke = self._default_stroke
|
693
|
+
data_out = []
|
694
|
+
c_off = ClipperOffset(interpolation=interpolation)
|
695
|
+
c_off.add_nodes(data)
|
696
|
+
c_off.process_data(offset, jointype=jointype, separate=separate)
|
697
|
+
for geom in c_off.result_geometry():
|
698
|
+
if geom is not None:
|
699
|
+
newnode = self.elem_branch.add(
|
700
|
+
geometry=geom, type="elem path", stroke=default_stroke
|
701
|
+
)
|
702
|
+
newnode.stroke_width = UNITS_PER_PIXEL
|
703
|
+
newnode.linejoin = Linejoin.JOIN_ROUND
|
704
|
+
newnode.label = f"Offset: {Length(offset).length_mm}"
|
705
|
+
data_out.append(newnode)
|
706
|
+
|
707
|
+
# Newly created! Classification needed?
|
708
|
+
if len(data_out) > 0:
|
709
|
+
post.append(classify_new(data_out))
|
710
|
+
self.signal("refresh_scene", "Scene")
|
711
|
+
return "elements", data_out
|
712
|
+
|
713
|
+
# Pocketing
|
714
|
+
@self.console_argument(
|
715
|
+
"offset",
|
716
|
+
type=str,
|
717
|
+
help=_(
|
718
|
+
"offset to line mm (negative values to left/outside, positive values to right/inside)"
|
719
|
+
),
|
720
|
+
)
|
721
|
+
@self.console_option(
|
722
|
+
"jointype", "j", type=str, help=_("join type: round, miter, square")
|
723
|
+
)
|
724
|
+
@self.console_option(
|
725
|
+
"separate",
|
726
|
+
"s",
|
727
|
+
action="store_true",
|
728
|
+
type=bool,
|
729
|
+
help=_("deal with subpaths separately"),
|
730
|
+
)
|
731
|
+
@self.console_option(
|
732
|
+
"repeats",
|
733
|
+
"r",
|
734
|
+
type=int,
|
735
|
+
help=_("amount of repetitions, 0=until area is fully filled"),
|
736
|
+
)
|
737
|
+
@self.console_option(
|
738
|
+
"interpolation", "i", type=int, help=_("interpolation points per segment")
|
739
|
+
)
|
740
|
+
@self.console_command(
|
741
|
+
"pocket",
|
742
|
+
help=_("create a pocketing path for any of the given elements"),
|
743
|
+
input_type=(None, "elements"),
|
744
|
+
output_type="elements",
|
745
|
+
)
|
746
|
+
def element_pocket_path(
|
747
|
+
command,
|
748
|
+
channel,
|
749
|
+
_,
|
750
|
+
offset=None,
|
751
|
+
jointype=None,
|
752
|
+
separate=None,
|
753
|
+
repeats=0,
|
754
|
+
interpolation=None,
|
755
|
+
data=None,
|
756
|
+
post=None,
|
757
|
+
**kwargs,
|
758
|
+
):
|
759
|
+
if data is None:
|
760
|
+
data = list(self.elems(emphasized=True))
|
761
|
+
if len(data) == 0:
|
762
|
+
channel(_("No elements selected"))
|
763
|
+
return "elements", data
|
764
|
+
if interpolation is None:
|
765
|
+
interpolation = 500
|
766
|
+
if separate is None:
|
767
|
+
separate = False
|
768
|
+
if offset is None:
|
769
|
+
offset = 0
|
770
|
+
else:
|
771
|
+
try:
|
772
|
+
ll = Length(offset)
|
773
|
+
offset = float(ll)
|
774
|
+
except ValueError:
|
775
|
+
offset = 0
|
776
|
+
if offset == 0.0:
|
777
|
+
channel("Invalid offset, nothing to do")
|
778
|
+
return
|
779
|
+
# Our definition for an offset is different this time
|
780
|
+
offset *= -1
|
781
|
+
if repeats is None or repeats < 0:
|
782
|
+
repeats = 0
|
783
|
+
if offset > 0 and repeats == 0:
|
784
|
+
channel(
|
785
|
+
"You need to provide the -r parameter to set the amount of repetitions"
|
786
|
+
)
|
787
|
+
return
|
788
|
+
|
789
|
+
if jointype is None:
|
790
|
+
jointype = "miter"
|
791
|
+
jointype = jointype.lower()
|
792
|
+
default_stroke = None
|
793
|
+
for node in data:
|
794
|
+
if hasattr(node, "stroke"):
|
795
|
+
default_stroke = node.stroke
|
796
|
+
break
|
797
|
+
if default_stroke is None:
|
798
|
+
default_stroke = self._default_stroke
|
799
|
+
data_out = []
|
800
|
+
rep_count = 0
|
801
|
+
mydata = [e for e in data]
|
802
|
+
while (rep_count < repeats or repeats == 0) and len(mydata) > 0:
|
803
|
+
rep_count += 1
|
804
|
+
c_off = ClipperOffset(interpolation=interpolation)
|
805
|
+
c_off.add_nodes(mydata)
|
806
|
+
c_off.process_data(offset, jointype=jointype, separate=separate)
|
807
|
+
mydata.clear()
|
808
|
+
for geom in c_off.result_geometry():
|
809
|
+
if geom is not None:
|
810
|
+
newnode = self.elem_branch.add(
|
811
|
+
geometry=geom, type="elem path", stroke=default_stroke
|
812
|
+
)
|
813
|
+
newnode.stroke_width = UNITS_PER_PIXEL
|
814
|
+
newnode.linejoin = Linejoin.JOIN_ROUND
|
815
|
+
newnode.label = f"Offset: {Length(offset).length_mm}"
|
816
|
+
mydata.append(newnode)
|
817
|
+
data_out.append(newnode)
|
818
|
+
|
819
|
+
# Newly created! Classification needed?
|
820
|
+
if len(data_out) > 0:
|
821
|
+
post.append(classify_new(data_out))
|
822
|
+
self.signal("refresh_scene", "Scene")
|
823
|
+
return "elements", data_out
|
824
|
+
|
825
|
+
# ---- Let's add some CAG commands....
|
826
|
+
@self.console_argument(
|
827
|
+
"method",
|
828
|
+
type=str,
|
829
|
+
help=_("method to use (one of union, difference, intersection, xor)"),
|
830
|
+
)
|
831
|
+
@self.console_option(
|
832
|
+
"filltype",
|
833
|
+
"f",
|
834
|
+
type=str,
|
835
|
+
help=_("filltype to use (one of evenodd, nonzero, negative, positive)"),
|
836
|
+
)
|
837
|
+
@self.console_option(
|
838
|
+
"interpolation", "i", type=int, help=_("interpolation points per segment")
|
839
|
+
)
|
840
|
+
@self.console_option(
|
841
|
+
"keep",
|
842
|
+
"k",
|
843
|
+
action="store_true",
|
844
|
+
type=bool,
|
845
|
+
help=_("keep the original elements, will be removed by default"),
|
846
|
+
)
|
847
|
+
@self.console_command(
|
848
|
+
"clipper",
|
849
|
+
help=_("create a logical combination of the given elements"),
|
850
|
+
input_type=(None, "elements"),
|
851
|
+
output_type="elements",
|
852
|
+
)
|
853
|
+
def element_clipper(
|
854
|
+
command,
|
855
|
+
channel,
|
856
|
+
_,
|
857
|
+
method=None,
|
858
|
+
filltype=None,
|
859
|
+
interpolation=None,
|
860
|
+
keep=None,
|
861
|
+
data=None,
|
862
|
+
post=None,
|
863
|
+
**kwargs,
|
864
|
+
):
|
865
|
+
if data is None:
|
866
|
+
data = list(self.elems(emphasized=True))
|
867
|
+
if len(data) == 0:
|
868
|
+
channel(_("No elements selected"))
|
869
|
+
return "elements", data
|
870
|
+
# Sort data according to selection data so that first selected element becomes the master
|
871
|
+
data.sort(key=lambda n: n.emphasized_time)
|
872
|
+
firstnode = data[0]
|
873
|
+
|
874
|
+
if interpolation is None:
|
875
|
+
interpolation = 500
|
876
|
+
if method is None:
|
877
|
+
method = "union"
|
878
|
+
method = method.lower()
|
879
|
+
if filltype is None:
|
880
|
+
filltype = "evenodd"
|
881
|
+
filltype = filltype.lower()
|
882
|
+
if keep is None:
|
883
|
+
keep = False
|
884
|
+
|
885
|
+
if method.startswith("d"):
|
886
|
+
long_method = "Difference"
|
887
|
+
elif method.startswith("i"):
|
888
|
+
long_method = "Intersection"
|
889
|
+
elif method.startswith("x"):
|
890
|
+
long_method = "Xor"
|
891
|
+
else:
|
892
|
+
long_method = "Union"
|
893
|
+
|
894
|
+
if filltype.startswith("no") or filltype.startswith("z"):
|
895
|
+
long_filltype = "NonZero"
|
896
|
+
elif filltype.startswith("p") or filltype.startswith("+"):
|
897
|
+
long_filltype = "Positive"
|
898
|
+
elif filltype.startswith("ne") or filltype.startswith("-"):
|
899
|
+
long_filltype = "Negative"
|
900
|
+
else:
|
901
|
+
long_filltype = "EvenOdd"
|
902
|
+
|
903
|
+
channel(f"Method={long_method}, filltype={long_filltype}")
|
904
|
+
|
905
|
+
data_out = list()
|
906
|
+
|
907
|
+
# Create a clipper object
|
908
|
+
clipper = ClipperCAG(interpolation=interpolation)
|
909
|
+
clipper.add_nodes(data)
|
910
|
+
# Perform the clip operation
|
911
|
+
clipper.process_data(method=method, filltype=filltype)
|
912
|
+
# _("Create clipper data")
|
913
|
+
with self.undoscope("Create clipper data"):
|
914
|
+
for geom in clipper.result_geometry():
|
915
|
+
if geom is not None:
|
916
|
+
newnode = self.elem_branch.add(
|
917
|
+
geometry=geom, type="elem path", stroke=firstnode.stroke
|
918
|
+
)
|
919
|
+
newnode.stroke_width = UNITS_PER_PIXEL
|
920
|
+
newnode.linejoin = Linejoin.JOIN_ROUND
|
921
|
+
newnode.label = f"{long_method} of {firstnode.id if firstnode.label is None else firstnode.display_label()}"
|
922
|
+
data_out.append(newnode)
|
923
|
+
|
924
|
+
# Newly created! Classification needed?
|
925
|
+
if len(data_out) > 0:
|
926
|
+
post.append(classify_new(data_out))
|
927
|
+
self.signal("refresh_scene", "Scene")
|
928
|
+
if not keep:
|
929
|
+
self.remove_nodes(data)
|
930
|
+
|
931
|
+
return "elements", data_out
|
932
|
+
|
933
|
+
# --------------------------- END COMMANDS ------------------------------
|