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,2074 +1,2088 @@
|
|
1
|
-
import math
|
2
|
-
from copy import copy
|
3
|
-
|
4
|
-
import wx
|
5
|
-
|
6
|
-
from meerk40t.gui.icons import (
|
7
|
-
STD_ICON_SIZE,
|
8
|
-
icon_node_add,
|
9
|
-
icon_node_append,
|
10
|
-
icon_node_break,
|
11
|
-
icon_node_close,
|
12
|
-
icon_node_curve,
|
13
|
-
icon_node_delete,
|
14
|
-
icon_node_join,
|
15
|
-
icon_node_line,
|
16
|
-
icon_node_line_all,
|
17
|
-
icon_node_smooth,
|
18
|
-
icon_node_smooth_all,
|
19
|
-
icon_node_symmetric,
|
20
|
-
)
|
21
|
-
from meerk40t.gui.laserrender import swizzlecolor
|
22
|
-
from meerk40t.gui.
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
from meerk40t.gui.
|
29
|
-
from meerk40t.
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
self.
|
57
|
-
self.
|
58
|
-
self.
|
59
|
-
self.
|
60
|
-
self.
|
61
|
-
|
62
|
-
|
63
|
-
self.
|
64
|
-
self.
|
65
|
-
self.
|
66
|
-
self.
|
67
|
-
self.pen
|
68
|
-
self.
|
69
|
-
|
70
|
-
self.pen_ctrl
|
71
|
-
self.
|
72
|
-
self.pen_ctrl_semi
|
73
|
-
self.
|
74
|
-
self.pen_highlight
|
75
|
-
self.
|
76
|
-
self.pen_highlight_line
|
77
|
-
self.
|
78
|
-
self.pen_selection
|
79
|
-
self.pen_selection.
|
80
|
-
self.
|
81
|
-
self.
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
"
|
88
|
-
"l": (self.convert_to_line, _("Line"), False, True),
|
89
|
-
"c": (self.convert_to_curve, _("Curve"), False, True),
|
90
|
-
"s": (self.cubic_symmetrical, _("Symmetric"), False, True),
|
91
|
-
"i": (self.insert_midpoint, _("Insert"), True, True),
|
92
|
-
"
|
93
|
-
"
|
94
|
-
"
|
95
|
-
"
|
96
|
-
"
|
97
|
-
"
|
98
|
-
"
|
99
|
-
"
|
100
|
-
|
101
|
-
|
102
|
-
self.
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
#
|
109
|
-
#
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
#
|
133
|
-
#
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
#
|
157
|
-
#
|
158
|
-
#
|
159
|
-
"
|
160
|
-
|
161
|
-
|
162
|
-
True,
|
163
|
-
True,
|
164
|
-
|
165
|
-
_("Insert"),
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
True,
|
172
|
-
|
173
|
-
_("Append"),
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
True,
|
179
|
-
True,
|
180
|
-
|
181
|
-
_("Delete"),
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
True,
|
187
|
-
|
188
|
-
|
189
|
-
_("
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
True,
|
195
|
-
|
196
|
-
|
197
|
-
_("
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
True,
|
203
|
-
|
204
|
-
|
205
|
-
_("
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
True,
|
211
|
-
|
212
|
-
|
213
|
-
_("Join"),
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
True,
|
219
|
-
|
220
|
-
|
221
|
-
_("Break"),
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
True,
|
227
|
-
|
228
|
-
|
229
|
-
_("Smooth"),
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
_("
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
_("
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
True,
|
252
|
-
|
253
|
-
_("
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
False,
|
259
|
-
|
260
|
-
|
261
|
-
_("
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
{
|
270
|
-
|
271
|
-
"
|
272
|
-
"
|
273
|
-
"
|
274
|
-
"
|
275
|
-
"
|
276
|
-
"
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
self.
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
"""
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
self.
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
pen.SetWidth(
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
set_width_pen(self.
|
340
|
-
set_width_pen(self.
|
341
|
-
set_width_pen(self.
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
"""
|
353
|
-
|
354
|
-
|
355
|
-
self.
|
356
|
-
|
357
|
-
self.
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
"
|
377
|
-
"
|
378
|
-
"
|
379
|
-
"
|
380
|
-
"
|
381
|
-
"
|
382
|
-
"
|
383
|
-
"
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
self.path = selected_node.
|
393
|
-
elif hasattr(selected_node, "
|
394
|
-
self.path = selected_node.
|
395
|
-
elif hasattr(selected_node, "
|
396
|
-
self.path = selected_node.as_path()
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
self.path
|
403
|
-
|
404
|
-
|
405
|
-
#
|
406
|
-
#
|
407
|
-
#
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
#
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
next_seg =
|
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
|
-
self.nodes
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
"
|
450
|
-
"
|
451
|
-
"
|
452
|
-
"
|
453
|
-
"
|
454
|
-
"
|
455
|
-
"
|
456
|
-
"
|
457
|
-
"
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
self.nodes
|
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
|
-
self.message
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
self.message
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
"""
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
ptx, pty
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
p.
|
664
|
-
|
665
|
-
p.
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
"""
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
gc.
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
self.
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
offset
|
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
|
-
orgnode = self.nodes[idx -
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
(ptx
|
766
|
-
(ptx, pty
|
767
|
-
(ptx
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
(
|
785
|
-
|
786
|
-
(ptx
|
787
|
-
(ptx, pty
|
788
|
-
(ptx
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
"""
|
796
|
-
|
797
|
-
|
798
|
-
self.
|
799
|
-
self.
|
800
|
-
self.
|
801
|
-
self.
|
802
|
-
self.
|
803
|
-
self.
|
804
|
-
self.scene.
|
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
|
-
self.
|
840
|
-
|
841
|
-
|
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
|
-
break
|
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
|
-
newnode.
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
self.
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
if self.
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
if isinstance(seg, Move):
|
964
|
-
#
|
965
|
-
|
966
|
-
if isinstance(seg,
|
967
|
-
# Ready
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
#
|
990
|
-
# )
|
991
|
-
# print(
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
) *
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
) *
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
"""
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
if self.
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
if self.
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
startpt.
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
)
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
dx = segment.control.x -
|
1195
|
-
dy = segment.control.y -
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
)
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
if
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
)
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1250
|
-
|
1251
|
-
|
1252
|
-
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1259
|
-
|
1260
|
-
|
1261
|
-
|
1262
|
-
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1268
|
-
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1282
|
-
"""
|
1283
|
-
|
1284
|
-
|
1285
|
-
|
1286
|
-
if self.
|
1287
|
-
|
1288
|
-
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1292
|
-
|
1293
|
-
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1298
|
-
|
1299
|
-
|
1300
|
-
|
1301
|
-
|
1302
|
-
|
1303
|
-
|
1304
|
-
#
|
1305
|
-
|
1306
|
-
|
1307
|
-
|
1308
|
-
)
|
1309
|
-
|
1310
|
-
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1314
|
-
|
1315
|
-
|
1316
|
-
|
1317
|
-
|
1318
|
-
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
1323
|
-
|
1324
|
-
|
1325
|
-
|
1326
|
-
|
1327
|
-
|
1328
|
-
|
1329
|
-
|
1330
|
-
|
1331
|
-
|
1332
|
-
|
1333
|
-
|
1334
|
-
|
1335
|
-
|
1336
|
-
|
1337
|
-
|
1338
|
-
|
1339
|
-
|
1340
|
-
|
1341
|
-
|
1342
|
-
|
1343
|
-
|
1344
|
-
|
1345
|
-
|
1346
|
-
|
1347
|
-
|
1348
|
-
|
1349
|
-
|
1350
|
-
|
1351
|
-
|
1352
|
-
|
1353
|
-
|
1354
|
-
|
1355
|
-
|
1356
|
-
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1366
|
-
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1372
|
-
|
1373
|
-
|
1374
|
-
|
1375
|
-
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1383
|
-
|
1384
|
-
|
1385
|
-
|
1386
|
-
|
1387
|
-
|
1388
|
-
|
1389
|
-
|
1390
|
-
if self.
|
1391
|
-
|
1392
|
-
|
1393
|
-
|
1394
|
-
|
1395
|
-
|
1396
|
-
|
1397
|
-
|
1398
|
-
|
1399
|
-
|
1400
|
-
|
1401
|
-
|
1402
|
-
|
1403
|
-
|
1404
|
-
|
1405
|
-
|
1406
|
-
|
1407
|
-
|
1408
|
-
|
1409
|
-
|
1410
|
-
|
1411
|
-
|
1412
|
-
|
1413
|
-
|
1414
|
-
|
1415
|
-
if self.
|
1416
|
-
|
1417
|
-
|
1418
|
-
|
1419
|
-
|
1420
|
-
|
1421
|
-
|
1422
|
-
|
1423
|
-
|
1424
|
-
|
1425
|
-
|
1426
|
-
|
1427
|
-
|
1428
|
-
|
1429
|
-
|
1430
|
-
|
1431
|
-
|
1432
|
-
|
1433
|
-
|
1434
|
-
|
1435
|
-
|
1436
|
-
|
1437
|
-
if self.
|
1438
|
-
|
1439
|
-
|
1440
|
-
|
1441
|
-
|
1442
|
-
|
1443
|
-
|
1444
|
-
|
1445
|
-
|
1446
|
-
|
1447
|
-
|
1448
|
-
|
1449
|
-
|
1450
|
-
|
1451
|
-
|
1452
|
-
|
1453
|
-
startpt.
|
1454
|
-
|
1455
|
-
|
1456
|
-
|
1457
|
-
|
1458
|
-
|
1459
|
-
|
1460
|
-
|
1461
|
-
|
1462
|
-
|
1463
|
-
|
1464
|
-
|
1465
|
-
|
1466
|
-
|
1467
|
-
startpt.
|
1468
|
-
|
1469
|
-
|
1470
|
-
|
1471
|
-
|
1472
|
-
|
1473
|
-
|
1474
|
-
|
1475
|
-
|
1476
|
-
|
1477
|
-
|
1478
|
-
|
1479
|
-
|
1480
|
-
|
1481
|
-
|
1482
|
-
|
1483
|
-
|
1484
|
-
|
1485
|
-
|
1486
|
-
|
1487
|
-
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1492
|
-
|
1493
|
-
|
1494
|
-
|
1495
|
-
|
1496
|
-
|
1497
|
-
|
1498
|
-
|
1499
|
-
|
1500
|
-
|
1501
|
-
|
1502
|
-
|
1503
|
-
|
1504
|
-
|
1505
|
-
|
1506
|
-
|
1507
|
-
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
1511
|
-
|
1512
|
-
|
1513
|
-
|
1514
|
-
|
1515
|
-
|
1516
|
-
|
1517
|
-
|
1518
|
-
|
1519
|
-
|
1520
|
-
|
1521
|
-
|
1522
|
-
|
1523
|
-
|
1524
|
-
|
1525
|
-
|
1526
|
-
|
1527
|
-
|
1528
|
-
|
1529
|
-
if isinstance(seg, Move):
|
1530
|
-
#
|
1531
|
-
|
1532
|
-
if isinstance(seg,
|
1533
|
-
# Ready
|
1534
|
-
|
1535
|
-
|
1536
|
-
|
1537
|
-
|
1538
|
-
|
1539
|
-
|
1540
|
-
|
1541
|
-
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
1545
|
-
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
|
1553
|
-
if self.
|
1554
|
-
|
1555
|
-
|
1556
|
-
|
1557
|
-
|
1558
|
-
|
1559
|
-
|
1560
|
-
|
1561
|
-
|
1562
|
-
|
1563
|
-
|
1564
|
-
|
1565
|
-
|
1566
|
-
|
1567
|
-
|
1568
|
-
|
1569
|
-
|
1570
|
-
|
1571
|
-
|
1572
|
-
if
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
continue
|
1577
|
-
nextseg
|
1578
|
-
|
1579
|
-
|
1580
|
-
|
1581
|
-
|
1582
|
-
|
1583
|
-
|
1584
|
-
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1588
|
-
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
1592
|
-
|
1593
|
-
|
1594
|
-
|
1595
|
-
|
1596
|
-
|
1597
|
-
|
1598
|
-
|
1599
|
-
|
1600
|
-
|
1601
|
-
|
1602
|
-
|
1603
|
-
|
1604
|
-
|
1605
|
-
|
1606
|
-
|
1607
|
-
|
1608
|
-
|
1609
|
-
|
1610
|
-
|
1611
|
-
|
1612
|
-
|
1613
|
-
|
1614
|
-
|
1615
|
-
|
1616
|
-
|
1617
|
-
pt2.
|
1618
|
-
|
1619
|
-
|
1620
|
-
pt2 =
|
1621
|
-
|
1622
|
-
|
1623
|
-
|
1624
|
-
|
1625
|
-
|
1626
|
-
|
1627
|
-
|
1628
|
-
|
1629
|
-
|
1630
|
-
|
1631
|
-
|
1632
|
-
|
1633
|
-
|
1634
|
-
|
1635
|
-
|
1636
|
-
|
1637
|
-
|
1638
|
-
|
1639
|
-
|
1640
|
-
|
1641
|
-
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
1645
|
-
|
1646
|
-
|
1647
|
-
|
1648
|
-
|
1649
|
-
|
1650
|
-
|
1651
|
-
|
1652
|
-
|
1653
|
-
|
1654
|
-
|
1655
|
-
|
1656
|
-
|
1657
|
-
|
1658
|
-
|
1659
|
-
|
1660
|
-
|
1661
|
-
|
1662
|
-
|
1663
|
-
|
1664
|
-
|
1665
|
-
|
1666
|
-
|
1667
|
-
|
1668
|
-
|
1669
|
-
|
1670
|
-
segment.
|
1671
|
-
|
1672
|
-
|
1673
|
-
|
1674
|
-
|
1675
|
-
|
1676
|
-
|
1677
|
-
|
1678
|
-
|
1679
|
-
|
1680
|
-
|
1681
|
-
#
|
1682
|
-
# )
|
1683
|
-
|
1684
|
-
|
1685
|
-
|
1686
|
-
|
1687
|
-
|
1688
|
-
|
1689
|
-
|
1690
|
-
|
1691
|
-
|
1692
|
-
|
1693
|
-
|
1694
|
-
|
1695
|
-
|
1696
|
-
|
1697
|
-
|
1698
|
-
|
1699
|
-
|
1700
|
-
|
1701
|
-
|
1702
|
-
|
1703
|
-
|
1704
|
-
|
1705
|
-
segment.
|
1706
|
-
|
1707
|
-
|
1708
|
-
|
1709
|
-
|
1710
|
-
|
1711
|
-
|
1712
|
-
|
1713
|
-
|
1714
|
-
|
1715
|
-
|
1716
|
-
|
1717
|
-
|
1718
|
-
|
1719
|
-
|
1720
|
-
|
1721
|
-
|
1722
|
-
|
1723
|
-
|
1724
|
-
|
1725
|
-
newsegment
|
1726
|
-
|
1727
|
-
|
1728
|
-
|
1729
|
-
|
1730
|
-
|
1731
|
-
|
1732
|
-
|
1733
|
-
|
1734
|
-
|
1735
|
-
|
1736
|
-
|
1737
|
-
|
1738
|
-
|
1739
|
-
|
1740
|
-
|
1741
|
-
|
1742
|
-
|
1743
|
-
|
1744
|
-
|
1745
|
-
|
1746
|
-
|
1747
|
-
|
1748
|
-
|
1749
|
-
if self.
|
1750
|
-
|
1751
|
-
|
1752
|
-
|
1753
|
-
|
1754
|
-
self.shape.points
|
1755
|
-
|
1756
|
-
|
1757
|
-
|
1758
|
-
|
1759
|
-
|
1760
|
-
|
1761
|
-
|
1762
|
-
|
1763
|
-
|
1764
|
-
|
1765
|
-
|
1766
|
-
|
1767
|
-
|
1768
|
-
|
1769
|
-
|
1770
|
-
|
1771
|
-
|
1772
|
-
|
1773
|
-
|
1774
|
-
|
1775
|
-
self.path[valididx + 1].
|
1776
|
-
|
1777
|
-
|
1778
|
-
|
1779
|
-
|
1780
|
-
|
1781
|
-
|
1782
|
-
|
1783
|
-
|
1784
|
-
|
1785
|
-
|
1786
|
-
|
1787
|
-
|
1788
|
-
|
1789
|
-
|
1790
|
-
|
1791
|
-
|
1792
|
-
|
1793
|
-
|
1794
|
-
|
1795
|
-
|
1796
|
-
|
1797
|
-
|
1798
|
-
|
1799
|
-
|
1800
|
-
|
1801
|
-
|
1802
|
-
|
1803
|
-
|
1804
|
-
|
1805
|
-
|
1806
|
-
|
1807
|
-
|
1808
|
-
|
1809
|
-
|
1810
|
-
|
1811
|
-
|
1812
|
-
|
1813
|
-
|
1814
|
-
|
1815
|
-
|
1816
|
-
|
1817
|
-
|
1818
|
-
|
1819
|
-
|
1820
|
-
|
1821
|
-
|
1822
|
-
|
1823
|
-
|
1824
|
-
|
1825
|
-
|
1826
|
-
|
1827
|
-
|
1828
|
-
|
1829
|
-
self.
|
1830
|
-
self.
|
1831
|
-
|
1832
|
-
self.scene.
|
1833
|
-
self.
|
1834
|
-
|
1835
|
-
|
1836
|
-
|
1837
|
-
|
1838
|
-
|
1839
|
-
|
1840
|
-
|
1841
|
-
|
1842
|
-
|
1843
|
-
|
1844
|
-
|
1845
|
-
|
1846
|
-
|
1847
|
-
|
1848
|
-
|
1849
|
-
|
1850
|
-
|
1851
|
-
|
1852
|
-
|
1853
|
-
for
|
1854
|
-
|
1855
|
-
|
1856
|
-
|
1857
|
-
|
1858
|
-
|
1859
|
-
|
1860
|
-
|
1861
|
-
|
1862
|
-
|
1863
|
-
|
1864
|
-
|
1865
|
-
|
1866
|
-
|
1867
|
-
|
1868
|
-
|
1869
|
-
|
1870
|
-
|
1871
|
-
|
1872
|
-
|
1873
|
-
|
1874
|
-
|
1875
|
-
|
1876
|
-
|
1877
|
-
|
1878
|
-
|
1879
|
-
|
1880
|
-
|
1881
|
-
return RESPONSE_CONSUME
|
1882
|
-
|
1883
|
-
|
1884
|
-
|
1885
|
-
|
1886
|
-
|
1887
|
-
|
1888
|
-
|
1889
|
-
|
1890
|
-
|
1891
|
-
|
1892
|
-
|
1893
|
-
|
1894
|
-
|
1895
|
-
|
1896
|
-
|
1897
|
-
|
1898
|
-
|
1899
|
-
|
1900
|
-
|
1901
|
-
|
1902
|
-
|
1903
|
-
|
1904
|
-
|
1905
|
-
|
1906
|
-
|
1907
|
-
|
1908
|
-
|
1909
|
-
|
1910
|
-
|
1911
|
-
self.scene.request_refresh()
|
1912
|
-
|
1913
|
-
|
1914
|
-
|
1915
|
-
|
1916
|
-
|
1917
|
-
|
1918
|
-
if
|
1919
|
-
|
1920
|
-
|
1921
|
-
|
1922
|
-
|
1923
|
-
|
1924
|
-
|
1925
|
-
|
1926
|
-
|
1927
|
-
|
1928
|
-
|
1929
|
-
|
1930
|
-
|
1931
|
-
|
1932
|
-
self.
|
1933
|
-
|
1934
|
-
|
1935
|
-
|
1936
|
-
|
1937
|
-
|
1938
|
-
|
1939
|
-
|
1940
|
-
|
1941
|
-
|
1942
|
-
|
1943
|
-
|
1944
|
-
|
1945
|
-
|
1946
|
-
|
1947
|
-
|
1948
|
-
|
1949
|
-
|
1950
|
-
|
1951
|
-
|
1952
|
-
|
1953
|
-
|
1954
|
-
|
1955
|
-
|
1956
|
-
|
1957
|
-
|
1958
|
-
|
1959
|
-
|
1960
|
-
|
1961
|
-
|
1962
|
-
|
1963
|
-
|
1964
|
-
|
1965
|
-
|
1966
|
-
|
1967
|
-
|
1968
|
-
|
1969
|
-
|
1970
|
-
|
1971
|
-
|
1972
|
-
|
1973
|
-
|
1974
|
-
|
1975
|
-
|
1976
|
-
|
1977
|
-
|
1978
|
-
|
1979
|
-
|
1980
|
-
|
1981
|
-
|
1982
|
-
|
1983
|
-
|
1984
|
-
|
1985
|
-
|
1986
|
-
|
1987
|
-
|
1988
|
-
|
1989
|
-
|
1990
|
-
if not self.
|
1991
|
-
|
1992
|
-
|
1993
|
-
|
1994
|
-
|
1995
|
-
|
1996
|
-
|
1997
|
-
|
1998
|
-
|
1999
|
-
|
2000
|
-
|
2001
|
-
|
2002
|
-
|
2003
|
-
|
2004
|
-
|
2005
|
-
|
2006
|
-
|
2007
|
-
|
2008
|
-
|
2009
|
-
|
2010
|
-
|
2011
|
-
|
2012
|
-
|
2013
|
-
|
2014
|
-
|
2015
|
-
|
2016
|
-
|
2017
|
-
|
2018
|
-
|
2019
|
-
|
2020
|
-
|
2021
|
-
|
2022
|
-
|
2023
|
-
|
2024
|
-
|
2025
|
-
|
2026
|
-
|
2027
|
-
|
2028
|
-
|
2029
|
-
|
2030
|
-
|
2031
|
-
|
2032
|
-
|
2033
|
-
|
2034
|
-
|
2035
|
-
|
2036
|
-
|
2037
|
-
|
2038
|
-
|
2039
|
-
|
2040
|
-
|
2041
|
-
|
2042
|
-
|
2043
|
-
|
2044
|
-
|
2045
|
-
|
2046
|
-
|
2047
|
-
|
2048
|
-
|
2049
|
-
|
2050
|
-
|
2051
|
-
|
2052
|
-
|
2053
|
-
|
2054
|
-
|
2055
|
-
|
2056
|
-
|
2057
|
-
|
2058
|
-
|
2059
|
-
|
2060
|
-
|
2061
|
-
|
2062
|
-
|
2063
|
-
|
2064
|
-
|
2065
|
-
|
2066
|
-
|
2067
|
-
|
2068
|
-
|
2069
|
-
|
2070
|
-
|
2071
|
-
|
2072
|
-
|
2073
|
-
|
2074
|
-
|
1
|
+
import math
|
2
|
+
from copy import copy
|
3
|
+
|
4
|
+
import wx
|
5
|
+
|
6
|
+
from meerk40t.gui.icons import (
|
7
|
+
STD_ICON_SIZE,
|
8
|
+
icon_node_add,
|
9
|
+
icon_node_append,
|
10
|
+
icon_node_break,
|
11
|
+
icon_node_close,
|
12
|
+
icon_node_curve,
|
13
|
+
icon_node_delete,
|
14
|
+
icon_node_join,
|
15
|
+
icon_node_line,
|
16
|
+
icon_node_line_all,
|
17
|
+
icon_node_smooth,
|
18
|
+
icon_node_smooth_all,
|
19
|
+
icon_node_symmetric,
|
20
|
+
)
|
21
|
+
from meerk40t.gui.laserrender import swizzlecolor
|
22
|
+
from meerk40t.gui.scene.sceneconst import (
|
23
|
+
RESPONSE_CHAIN,
|
24
|
+
RESPONSE_CONSUME,
|
25
|
+
RESPONSE_DROP,
|
26
|
+
)
|
27
|
+
from meerk40t.gui.toolwidgets.toolwidget import ToolWidget
|
28
|
+
from meerk40t.gui.wxutils import get_matrix_scale
|
29
|
+
from meerk40t.svgelements import (
|
30
|
+
Arc,
|
31
|
+
Close,
|
32
|
+
CubicBezier,
|
33
|
+
Line,
|
34
|
+
Move,
|
35
|
+
Path,
|
36
|
+
Point,
|
37
|
+
Polygon,
|
38
|
+
Polyline,
|
39
|
+
QuadraticBezier,
|
40
|
+
)
|
41
|
+
from meerk40t.tools.geomstr import Geomstr
|
42
|
+
|
43
|
+
_ = wx.GetTranslation
|
44
|
+
|
45
|
+
|
46
|
+
class EditTool(ToolWidget):
|
47
|
+
"""
|
48
|
+
Edit tool allows you to view and edit the nodes within a
|
49
|
+
selected element in the scene. It can currently handle
|
50
|
+
polylines / polygons and paths.
|
51
|
+
"""
|
52
|
+
|
53
|
+
def __init__(self, scene, mode=None):
|
54
|
+
ToolWidget.__init__(self, scene)
|
55
|
+
self._listener_active = False
|
56
|
+
self.nodes = []
|
57
|
+
self.shape = None
|
58
|
+
self.path = None
|
59
|
+
self.element = None
|
60
|
+
self.selected_index = None
|
61
|
+
|
62
|
+
self.move_type = "node"
|
63
|
+
self.node_type = "path"
|
64
|
+
self.p1 = None
|
65
|
+
self.p2 = None
|
66
|
+
self.pen = wx.Pen()
|
67
|
+
self.pen.SetColour(wx.BLUE)
|
68
|
+
# wx.Colour(swizzlecolor(self.scene.context.elements.default_stroke))
|
69
|
+
self.pen_ctrl = wx.Pen()
|
70
|
+
self.pen_ctrl.SetColour(wx.CYAN)
|
71
|
+
self.pen_ctrl_semi = wx.Pen()
|
72
|
+
self.pen_ctrl_semi.SetColour(wx.GREEN)
|
73
|
+
self.pen_highlight = wx.Pen()
|
74
|
+
self.pen_highlight.SetColour(wx.RED)
|
75
|
+
self.pen_highlight_line = wx.Pen()
|
76
|
+
self.pen_highlight_line.SetColour(wx.Colour(255, 0, 0, 80))
|
77
|
+
self.pen_selection = wx.Pen()
|
78
|
+
self.pen_selection.SetColour(self.scene.colors.color_selection3)
|
79
|
+
self.pen_selection.SetStyle(wx.PENSTYLE_SHORT_DASH)
|
80
|
+
self.brush_highlight = wx.Brush(wx.RED_BRUSH)
|
81
|
+
self.brush_normal = wx.Brush(wx.TRANSPARENT_BRUSH)
|
82
|
+
# want to have sharp edges
|
83
|
+
self.pen_selection.SetJoin(wx.JOIN_MITER)
|
84
|
+
# "key": (routine, info, available for poly, available for path)
|
85
|
+
self.commands = {
|
86
|
+
"d": (self.delete_nodes, _("Delete"), True, True),
|
87
|
+
"delete": (self.delete_nodes, _("Delete"), True, True),
|
88
|
+
"l": (self.convert_to_line, _("Line"), False, True),
|
89
|
+
"c": (self.convert_to_curve, _("Curve"), False, True),
|
90
|
+
"s": (self.cubic_symmetrical, _("Symmetric"), False, True),
|
91
|
+
"i": (self.insert_midpoint, _("Insert"), True, True),
|
92
|
+
"insert": (self.insert_midpoint, _("Insert"), True, True),
|
93
|
+
"a": (self.append_line, _("Append"), True, True),
|
94
|
+
"b": (self.break_path, _("Break"), False, True),
|
95
|
+
"j": (self.join_path, _("Join"), False, True),
|
96
|
+
"o": (self.smoothen, _("Smooth"), False, True),
|
97
|
+
"z": (self.toggle_close, _("Close path"), True, True),
|
98
|
+
"v": (self.smoothen_all, _("Smooth all"), False, True),
|
99
|
+
"w": (self.linear_all, _("Line all"), False, True),
|
100
|
+
"p": (self.convert_to_path, _("To path"), True, False),
|
101
|
+
}
|
102
|
+
self.define_buttons()
|
103
|
+
self.message = ""
|
104
|
+
|
105
|
+
def define_buttons(self):
|
106
|
+
def becomes_enabled(needs_selection, active_for_path, active_for_poly):
|
107
|
+
def routine(*args):
|
108
|
+
# print(
|
109
|
+
# f"Was asked to perform with {my_selection}, {my_active_poly}, {my_active_path} while {self.anyselected} + {self.node_type}"
|
110
|
+
# )
|
111
|
+
if self.element is None:
|
112
|
+
return False
|
113
|
+
flag_sel = True
|
114
|
+
flag_poly = False
|
115
|
+
flag_path = False
|
116
|
+
if my_selection and not self.anyselected:
|
117
|
+
flag_sel = False
|
118
|
+
if my_active_poly and self.node_type == "polyline":
|
119
|
+
flag_poly = True
|
120
|
+
if my_active_path and self.node_type == "path":
|
121
|
+
flag_path = True
|
122
|
+
flag = flag_sel and (flag_path or flag_poly)
|
123
|
+
return flag
|
124
|
+
|
125
|
+
my_selection = needs_selection
|
126
|
+
my_active_poly = active_for_poly
|
127
|
+
my_active_path = active_for_path
|
128
|
+
return routine
|
129
|
+
|
130
|
+
def becomes_visible(active_for_path, active_for_poly):
|
131
|
+
def routine(*args):
|
132
|
+
# print(
|
133
|
+
# f"Was asked to perform with {my_active_poly}, {my_active_path} while {self.anyselected} + {self.node_type}"
|
134
|
+
# )
|
135
|
+
flag_poly = False
|
136
|
+
flag_path = False
|
137
|
+
if my_active_poly and self.node_type == "polyline":
|
138
|
+
flag_poly = True
|
139
|
+
if my_active_path and self.node_type == "path":
|
140
|
+
flag_path = True
|
141
|
+
flag = flag_path or flag_poly
|
142
|
+
return flag
|
143
|
+
|
144
|
+
my_active_path = active_for_path
|
145
|
+
my_active_poly = active_for_poly
|
146
|
+
return routine
|
147
|
+
|
148
|
+
def do_action(code):
|
149
|
+
def routine(*args):
|
150
|
+
self.perform_action(mycode)
|
151
|
+
|
152
|
+
mycode = code
|
153
|
+
return routine
|
154
|
+
|
155
|
+
cmd_icons = {
|
156
|
+
# "command": [
|
157
|
+
# image, requires_selection,
|
158
|
+
# active_for_path, active_for_poly,
|
159
|
+
# "tooltiptext", button],
|
160
|
+
"i": [
|
161
|
+
icon_node_add,
|
162
|
+
True,
|
163
|
+
True,
|
164
|
+
True,
|
165
|
+
_("Insert point before"),
|
166
|
+
_("Insert"),
|
167
|
+
],
|
168
|
+
"a": [
|
169
|
+
icon_node_append,
|
170
|
+
False,
|
171
|
+
True,
|
172
|
+
True,
|
173
|
+
_("Append point at end"),
|
174
|
+
_("Append"),
|
175
|
+
],
|
176
|
+
"d": [
|
177
|
+
icon_node_delete,
|
178
|
+
True,
|
179
|
+
True,
|
180
|
+
True,
|
181
|
+
_("Delete point"),
|
182
|
+
_("Delete"),
|
183
|
+
],
|
184
|
+
"l": [
|
185
|
+
icon_node_line,
|
186
|
+
True,
|
187
|
+
True,
|
188
|
+
False,
|
189
|
+
_("Make segment a line"),
|
190
|
+
_("> Line"),
|
191
|
+
],
|
192
|
+
"c": [
|
193
|
+
icon_node_curve,
|
194
|
+
True,
|
195
|
+
True,
|
196
|
+
False,
|
197
|
+
_("Make segment a curve"),
|
198
|
+
_("> Curve"),
|
199
|
+
],
|
200
|
+
"s": [
|
201
|
+
icon_node_symmetric,
|
202
|
+
True,
|
203
|
+
True,
|
204
|
+
False,
|
205
|
+
_("Make segment symmetrical"),
|
206
|
+
_("Symmetric"),
|
207
|
+
],
|
208
|
+
"j": [
|
209
|
+
icon_node_join,
|
210
|
+
True,
|
211
|
+
True,
|
212
|
+
False,
|
213
|
+
_("Join two segments"),
|
214
|
+
_("Join"),
|
215
|
+
],
|
216
|
+
"b": [
|
217
|
+
icon_node_break,
|
218
|
+
True,
|
219
|
+
True,
|
220
|
+
False,
|
221
|
+
_("Break segment apart"),
|
222
|
+
_("Break"),
|
223
|
+
],
|
224
|
+
"o": [
|
225
|
+
icon_node_smooth,
|
226
|
+
True,
|
227
|
+
True,
|
228
|
+
False,
|
229
|
+
_("Smooth transit to adjacent segments"),
|
230
|
+
_("Smooth"),
|
231
|
+
],
|
232
|
+
"v": [
|
233
|
+
icon_node_smooth_all,
|
234
|
+
False,
|
235
|
+
True,
|
236
|
+
False,
|
237
|
+
_("Convert all lines into smooth curves"),
|
238
|
+
_("Very smooth"),
|
239
|
+
],
|
240
|
+
"w": [
|
241
|
+
icon_node_line_all,
|
242
|
+
False,
|
243
|
+
True,
|
244
|
+
False,
|
245
|
+
_("Convert all segments into lines"),
|
246
|
+
_("Line all"),
|
247
|
+
],
|
248
|
+
"z": [
|
249
|
+
icon_node_close,
|
250
|
+
False,
|
251
|
+
True,
|
252
|
+
True,
|
253
|
+
_("Toggle closed status"),
|
254
|
+
_("Close"),
|
255
|
+
],
|
256
|
+
"p": [
|
257
|
+
icon_node_smooth_all,
|
258
|
+
False,
|
259
|
+
False,
|
260
|
+
True,
|
261
|
+
_("Convert polyline to a path element"),
|
262
|
+
_("To Path"),
|
263
|
+
],
|
264
|
+
}
|
265
|
+
icon_size = STD_ICON_SIZE
|
266
|
+
for command, entry in cmd_icons.items():
|
267
|
+
# print(command, f"button/secondarytool_edit/tool_{command}")
|
268
|
+
self.scene.context.kernel.register(
|
269
|
+
f"button/secondarytool_edit/tool_{command}",
|
270
|
+
{
|
271
|
+
"label": entry[5],
|
272
|
+
"icon": entry[0],
|
273
|
+
"tip": entry[4],
|
274
|
+
"help": "nodeedit",
|
275
|
+
"action": do_action(command),
|
276
|
+
"size": icon_size,
|
277
|
+
"rule_enabled": becomes_enabled(entry[1], entry[2], entry[3]),
|
278
|
+
"rule_visible": becomes_visible(entry[2], entry[3]),
|
279
|
+
},
|
280
|
+
)
|
281
|
+
|
282
|
+
def enable_rules(self):
|
283
|
+
toolbar = self.scene.context.lookup("ribbonbar/tools")
|
284
|
+
if toolbar is not None:
|
285
|
+
toolbar.apply_enable_rules()
|
286
|
+
|
287
|
+
def final(self, context):
|
288
|
+
"""
|
289
|
+
Shutdown routine for widget that unregisters the listener routines
|
290
|
+
and closes the toolbar window.
|
291
|
+
This could be called more than once which, if not dealt with, will
|
292
|
+
cause a console warning message
|
293
|
+
"""
|
294
|
+
if self._listener_active:
|
295
|
+
self.scene.context.unlisten("emphasized", self.on_emphasized_changed)
|
296
|
+
self.scene.context.unlisten("nodeedit", self.on_signal_nodeedit)
|
297
|
+
self._listener_active = False
|
298
|
+
self.scene.request_refresh()
|
299
|
+
|
300
|
+
def init(self, context):
|
301
|
+
"""
|
302
|
+
Startup routine for widget that establishes the listener routines
|
303
|
+
and opens the toolbar window
|
304
|
+
"""
|
305
|
+
self.scene.context.listen("emphasized", self.on_emphasized_changed)
|
306
|
+
self.scene.context.listen("nodeedit", self.on_signal_nodeedit)
|
307
|
+
self._listener_active = True
|
308
|
+
|
309
|
+
def on_emphasized_changed(self, origin, *args):
|
310
|
+
"""
|
311
|
+
Receiver routine for scene selection signal
|
312
|
+
"""
|
313
|
+
selected_node = self.scene.context.elements.first_element(emphasized=True)
|
314
|
+
if selected_node is not self.element:
|
315
|
+
self.calculate_points(selected_node)
|
316
|
+
self.scene.request_refresh()
|
317
|
+
self.enable_rules()
|
318
|
+
|
319
|
+
def set_pen_widths(self):
|
320
|
+
"""
|
321
|
+
Calculate the pen widths according to the current scene zoom levels,
|
322
|
+
so that they always appear 1 pixel wide - except for the
|
323
|
+
pen associated to the path segment outline that gets a 2 pixel wide 'halo'
|
324
|
+
"""
|
325
|
+
|
326
|
+
def set_width_pen(pen, width):
|
327
|
+
try:
|
328
|
+
try:
|
329
|
+
pen.SetWidth(width)
|
330
|
+
except TypeError:
|
331
|
+
pen.SetWidth(int(width))
|
332
|
+
except OverflowError:
|
333
|
+
pass # Exceeds 32 bit signed integer.
|
334
|
+
|
335
|
+
matrix = self.scene.widget_root.scene_widget.matrix
|
336
|
+
linewidth = 1.0 / get_matrix_scale(matrix)
|
337
|
+
if linewidth < 1:
|
338
|
+
linewidth = 1
|
339
|
+
set_width_pen(self.pen, linewidth)
|
340
|
+
set_width_pen(self.pen_highlight, linewidth)
|
341
|
+
set_width_pen(self.pen_ctrl, linewidth)
|
342
|
+
set_width_pen(self.pen_ctrl_semi, linewidth)
|
343
|
+
set_width_pen(self.pen_selection, linewidth)
|
344
|
+
value = linewidth
|
345
|
+
if self.element is not None and hasattr(self.element, "stroke_width"):
|
346
|
+
if self.element.stroke_width is not None:
|
347
|
+
value = self.element.stroke_width
|
348
|
+
value += 4 * linewidth
|
349
|
+
set_width_pen(self.pen_highlight_line, value)
|
350
|
+
|
351
|
+
def calculate_points(self, selected_node):
|
352
|
+
"""
|
353
|
+
Parse the element and create a list of dictionaries with relevant information required for display and logic
|
354
|
+
"""
|
355
|
+
self.message = ""
|
356
|
+
|
357
|
+
self.element = selected_node
|
358
|
+
self.selected_index = None
|
359
|
+
self.nodes = []
|
360
|
+
# print ("After load:")
|
361
|
+
# self.debug_path()
|
362
|
+
if selected_node is None:
|
363
|
+
return
|
364
|
+
self.shape = None
|
365
|
+
self.path = None
|
366
|
+
if selected_node.type == "elem polyline":
|
367
|
+
self.node_type = "polyline"
|
368
|
+
try:
|
369
|
+
self.shape = selected_node.shape
|
370
|
+
except AttributeError:
|
371
|
+
return
|
372
|
+
start = 0
|
373
|
+
for idx, pt in enumerate(self.shape.points):
|
374
|
+
self.nodes.append(
|
375
|
+
{
|
376
|
+
"prev": None,
|
377
|
+
"next": None,
|
378
|
+
"point": pt,
|
379
|
+
"segment": None,
|
380
|
+
"path": self.shape,
|
381
|
+
"type": "point",
|
382
|
+
"connector": -1,
|
383
|
+
"selected": False,
|
384
|
+
"segtype": "L",
|
385
|
+
"start": start,
|
386
|
+
}
|
387
|
+
)
|
388
|
+
else:
|
389
|
+
self.node_type = "path"
|
390
|
+
# self.path = selected_node.geometry.as_path()
|
391
|
+
if hasattr(selected_node, "path"):
|
392
|
+
self.path = selected_node.path
|
393
|
+
elif hasattr(selected_node, "geometry"):
|
394
|
+
self.path = selected_node.geometry.as_path()
|
395
|
+
elif hasattr(selected_node, "as_geometry"):
|
396
|
+
self.path = selected_node.as_geometry().as_path()
|
397
|
+
elif hasattr(selected_node, "as_path"):
|
398
|
+
self.path = selected_node.as_path()
|
399
|
+
else:
|
400
|
+
return
|
401
|
+
# print(self.path.d(), self.path)
|
402
|
+
if self.path is None:
|
403
|
+
return
|
404
|
+
self.path.approximate_arcs_with_cubics()
|
405
|
+
# print(self.path.d(), self.path)
|
406
|
+
# try:
|
407
|
+
# except AttributeError:
|
408
|
+
# return
|
409
|
+
# print (f"Path: {str(path)}")
|
410
|
+
prev_seg = None
|
411
|
+
start = 0
|
412
|
+
# Idx of last point
|
413
|
+
l_idx = 0
|
414
|
+
for idx, segment in enumerate(self.path):
|
415
|
+
# print(
|
416
|
+
# f"{idx}# {type(segment).__name__} - S={segment.start} - E={segment.end}"
|
417
|
+
# )
|
418
|
+
if idx < len(self.path) - 1:
|
419
|
+
next_seg = self.path[idx + 1]
|
420
|
+
else:
|
421
|
+
next_seg = None
|
422
|
+
if isinstance(segment, Move):
|
423
|
+
if idx != start:
|
424
|
+
start = idx
|
425
|
+
|
426
|
+
if isinstance(segment, Close):
|
427
|
+
# We don't do anything with a Close - it's drawn anyway
|
428
|
+
pass
|
429
|
+
elif isinstance(segment, Line):
|
430
|
+
self.nodes.append(
|
431
|
+
{
|
432
|
+
"prev": prev_seg,
|
433
|
+
"next": next_seg,
|
434
|
+
"point": segment.end,
|
435
|
+
"segment": segment,
|
436
|
+
"path": self.path,
|
437
|
+
"type": "point",
|
438
|
+
"connector": -1,
|
439
|
+
"selected": False,
|
440
|
+
"segtype": "Z" if isinstance(segment, Close) else "L",
|
441
|
+
"start": start,
|
442
|
+
"pathindex": idx,
|
443
|
+
}
|
444
|
+
)
|
445
|
+
nidx = len(self.nodes) - 1
|
446
|
+
elif isinstance(segment, Move):
|
447
|
+
self.nodes.append(
|
448
|
+
{
|
449
|
+
"prev": prev_seg,
|
450
|
+
"next": next_seg,
|
451
|
+
"point": segment.end,
|
452
|
+
"segment": segment,
|
453
|
+
"path": self.path,
|
454
|
+
"type": "point",
|
455
|
+
"connector": -1,
|
456
|
+
"selected": False,
|
457
|
+
"segtype": "M",
|
458
|
+
"start": start,
|
459
|
+
"pathindex": idx,
|
460
|
+
}
|
461
|
+
)
|
462
|
+
nidx = len(self.nodes) - 1
|
463
|
+
elif isinstance(segment, QuadraticBezier):
|
464
|
+
self.nodes.append(
|
465
|
+
{
|
466
|
+
"prev": prev_seg,
|
467
|
+
"next": next_seg,
|
468
|
+
"point": segment.end,
|
469
|
+
"segment": segment,
|
470
|
+
"path": self.path,
|
471
|
+
"type": "point",
|
472
|
+
"connector": -1,
|
473
|
+
"selected": False,
|
474
|
+
"segtype": "Q",
|
475
|
+
"start": start,
|
476
|
+
"pathindex": idx,
|
477
|
+
}
|
478
|
+
)
|
479
|
+
nidx = len(self.nodes) - 1
|
480
|
+
self.nodes.append(
|
481
|
+
{
|
482
|
+
"prev": None,
|
483
|
+
"next": None,
|
484
|
+
"point": segment.control,
|
485
|
+
"segment": segment,
|
486
|
+
"path": self.path,
|
487
|
+
"type": "control",
|
488
|
+
"connector": nidx,
|
489
|
+
"selected": False,
|
490
|
+
"segtype": "",
|
491
|
+
"start": start,
|
492
|
+
"pathindex": idx,
|
493
|
+
}
|
494
|
+
)
|
495
|
+
elif isinstance(segment, CubicBezier):
|
496
|
+
self.nodes.append(
|
497
|
+
{
|
498
|
+
"prev": prev_seg,
|
499
|
+
"next": next_seg,
|
500
|
+
"point": segment.end,
|
501
|
+
"segment": segment,
|
502
|
+
"path": self.path,
|
503
|
+
"type": "point",
|
504
|
+
"connector": -1,
|
505
|
+
"selected": False,
|
506
|
+
"segtype": "C",
|
507
|
+
"start": start,
|
508
|
+
"pathindex": idx,
|
509
|
+
}
|
510
|
+
)
|
511
|
+
nidx = len(self.nodes) - 1
|
512
|
+
self.nodes.append(
|
513
|
+
{
|
514
|
+
"prev": None,
|
515
|
+
"next": None,
|
516
|
+
"point": segment.control1,
|
517
|
+
"segment": segment,
|
518
|
+
"path": self.path,
|
519
|
+
"type": "control",
|
520
|
+
"connector": l_idx,
|
521
|
+
"selected": False,
|
522
|
+
"segtype": "",
|
523
|
+
"start": start,
|
524
|
+
"pathindex": idx,
|
525
|
+
}
|
526
|
+
)
|
527
|
+
self.nodes.append(
|
528
|
+
{
|
529
|
+
"prev": None,
|
530
|
+
"next": None,
|
531
|
+
"point": segment.control2,
|
532
|
+
"segment": segment,
|
533
|
+
"path": self.path,
|
534
|
+
"type": "control",
|
535
|
+
"connector": nidx,
|
536
|
+
"selected": False,
|
537
|
+
"segtype": "",
|
538
|
+
"start": start,
|
539
|
+
"pathindex": idx,
|
540
|
+
}
|
541
|
+
)
|
542
|
+
# midp = segment.point(0.5)
|
543
|
+
midp = self.get_bezier_point(segment, 0.5)
|
544
|
+
self.nodes.append(
|
545
|
+
{
|
546
|
+
"prev": None,
|
547
|
+
"next": None,
|
548
|
+
"point": midp,
|
549
|
+
"segment": segment,
|
550
|
+
"path": self.path,
|
551
|
+
"type": "midpoint",
|
552
|
+
"connector": -1,
|
553
|
+
"selected": False,
|
554
|
+
"segtype": "",
|
555
|
+
"start": start,
|
556
|
+
"pathindex": idx,
|
557
|
+
}
|
558
|
+
)
|
559
|
+
elif isinstance(segment, Arc):
|
560
|
+
self.nodes.append(
|
561
|
+
{
|
562
|
+
"prev": prev_seg,
|
563
|
+
"next": next_seg,
|
564
|
+
"point": segment.end,
|
565
|
+
"segment": segment,
|
566
|
+
"path": self.path,
|
567
|
+
"type": "point",
|
568
|
+
"connector": -1,
|
569
|
+
"selected": False,
|
570
|
+
"segtype": "A",
|
571
|
+
"start": start,
|
572
|
+
"pathindex": idx,
|
573
|
+
}
|
574
|
+
)
|
575
|
+
nidx = len(self.nodes) - 1
|
576
|
+
self.nodes.append(
|
577
|
+
{
|
578
|
+
"prev": None,
|
579
|
+
"next": None,
|
580
|
+
"point": segment.center,
|
581
|
+
"segment": segment,
|
582
|
+
"path": self.path,
|
583
|
+
"type": "control",
|
584
|
+
"connector": nidx,
|
585
|
+
"selected": False,
|
586
|
+
"segtype": "",
|
587
|
+
"start": start,
|
588
|
+
"pathindex": idx,
|
589
|
+
}
|
590
|
+
)
|
591
|
+
prev_seg = segment
|
592
|
+
l_idx = nidx
|
593
|
+
for cmd in self.commands:
|
594
|
+
action = self.commands[cmd]
|
595
|
+
if self.node_type == "path" and action[3]:
|
596
|
+
if self.message:
|
597
|
+
self.message += ", "
|
598
|
+
self.message += f"{cmd}: {action[1]}"
|
599
|
+
if self.node_type == "polyline" and action[2]:
|
600
|
+
if self.message:
|
601
|
+
self.message += ", "
|
602
|
+
self.message += f"{cmd}: {action[1]}"
|
603
|
+
|
604
|
+
self.enable_rules()
|
605
|
+
|
606
|
+
def calc_and_draw(self, gc):
|
607
|
+
"""
|
608
|
+
Takes a svgelements.Path and converts it to a GraphicsContext.Graphics Path
|
609
|
+
"""
|
610
|
+
|
611
|
+
def deal_with_segment(seg, init):
|
612
|
+
if isinstance(seg, Line):
|
613
|
+
if not init:
|
614
|
+
init = True
|
615
|
+
ptx, pty = node.matrix.point_in_matrix_space(seg.start)
|
616
|
+
p.MoveToPoint(ptx, pty)
|
617
|
+
ptx, pty = node.matrix.point_in_matrix_space(seg.end)
|
618
|
+
p.AddLineToPoint(ptx, pty)
|
619
|
+
elif isinstance(seg, Close):
|
620
|
+
if not init:
|
621
|
+
init = True
|
622
|
+
ptx, pty = node.matrix.point_in_matrix_space(seg.start)
|
623
|
+
p.MoveToPoint(ptx, pty)
|
624
|
+
p.CloseSubpath()
|
625
|
+
elif isinstance(seg, QuadraticBezier):
|
626
|
+
if not init:
|
627
|
+
init = True
|
628
|
+
ptx, pty = node.matrix.point_in_matrix_space(seg.start)
|
629
|
+
p.MoveToPoint(ptx, pty)
|
630
|
+
ptx, pty = node.matrix.point_in_matrix_space(seg.end)
|
631
|
+
c1x, c1y = node.matrix.point_in_matrix_space(seg.control)
|
632
|
+
p.AddQuadCurveToPoint(c1x, c1y, ptx, pty)
|
633
|
+
elif isinstance(seg, CubicBezier):
|
634
|
+
if not init:
|
635
|
+
init = True
|
636
|
+
ptx, pty = node.matrix.point_in_matrix_space(seg.start)
|
637
|
+
p.MoveToPoint(ptx, pty)
|
638
|
+
ptx, pty = node.matrix.point_in_matrix_space(seg.end)
|
639
|
+
c1x, c1y = node.matrix.point_in_matrix_space(seg.control1)
|
640
|
+
c2x, c2y = node.matrix.point_in_matrix_space(seg.control2)
|
641
|
+
p.AddCurveToPoint(c1x, c1y, c2x, c2y, ptx, pty)
|
642
|
+
elif isinstance(seg, Arc):
|
643
|
+
if not init:
|
644
|
+
init = True
|
645
|
+
ptx, pty = node.matrix.point_in_matrix_space(seg.start)
|
646
|
+
p.MoveToPoint(ptx, pty)
|
647
|
+
for curve in seg.as_cubic_curves():
|
648
|
+
ptx, pty = node.matrix.point_in_matrix_space(curve.end)
|
649
|
+
c1x, c1y = node.matrix.point_in_matrix_space(curve.control1)
|
650
|
+
c2x, c2y = node.matrix.point_in_matrix_space(curve.control2)
|
651
|
+
p.AddCurveToPoint(c1x, c1y, c2x, c2y, ptx, pty)
|
652
|
+
return init
|
653
|
+
|
654
|
+
node = self.element
|
655
|
+
p = gc.CreatePath()
|
656
|
+
if self.node_type == "polyline":
|
657
|
+
for idx, entry in enumerate(self.nodes):
|
658
|
+
ptx, pty = node.matrix.point_in_matrix_space(entry["point"])
|
659
|
+
# print (f"Idx={idx}, selected={entry['selected']}, prev={'-' if idx == 0 else self.nodes[idx-1]['selected']}")
|
660
|
+
if idx == 1 and (
|
661
|
+
self.nodes[0]["selected"] or self.nodes[1]["selected"]
|
662
|
+
):
|
663
|
+
p.AddLineToPoint(ptx, pty)
|
664
|
+
elif idx == 0 or not entry["selected"]:
|
665
|
+
p.MoveToPoint(ptx, pty)
|
666
|
+
else:
|
667
|
+
p.AddLineToPoint(ptx, pty)
|
668
|
+
else:
|
669
|
+
# path = self.path
|
670
|
+
init = False
|
671
|
+
for idx, entry in enumerate(self.nodes):
|
672
|
+
if not entry["type"] == "point":
|
673
|
+
continue
|
674
|
+
# treatment = ""
|
675
|
+
e = entry["segment"]
|
676
|
+
if isinstance(e, Move):
|
677
|
+
if entry["selected"]:
|
678
|
+
# The next segment needs to be highlighted...
|
679
|
+
ptx, pty = node.matrix.point_in_matrix_space(e.end)
|
680
|
+
p.MoveToPoint(ptx, pty)
|
681
|
+
e = entry["next"]
|
682
|
+
init = deal_with_segment(e, init)
|
683
|
+
# treatment = "move+next"
|
684
|
+
else:
|
685
|
+
ptx, pty = node.matrix.point_in_matrix_space(e.end)
|
686
|
+
p.MoveToPoint(ptx, pty)
|
687
|
+
init = True
|
688
|
+
# treatment = "move"
|
689
|
+
elif not entry["selected"]:
|
690
|
+
ptx, pty = node.matrix.point_in_matrix_space(e.end)
|
691
|
+
p.MoveToPoint(ptx, pty)
|
692
|
+
init = True
|
693
|
+
# treatment = "nonselected"
|
694
|
+
else:
|
695
|
+
init = deal_with_segment(e, init)
|
696
|
+
# treatment = "selected"
|
697
|
+
# print (f"#{idx} {entry['type']} got treatment: {treatment}")
|
698
|
+
|
699
|
+
gc.SetPen(self.pen_highlight_line)
|
700
|
+
gc.DrawPath(p)
|
701
|
+
|
702
|
+
def process_draw(self, gc: wx.GraphicsContext):
|
703
|
+
"""
|
704
|
+
Widget-Routine to draw the different elements on the provided GraphicContext
|
705
|
+
"""
|
706
|
+
|
707
|
+
def draw_selection_rectangle():
|
708
|
+
x0 = min(self.p1.real, self.p2.real)
|
709
|
+
y0 = min(self.p1.imag, self.p2.imag)
|
710
|
+
x1 = max(self.p1.real, self.p2.real)
|
711
|
+
y1 = max(self.p1.imag, self.p2.imag)
|
712
|
+
gc.SetPen(self.pen_selection)
|
713
|
+
gc.SetBrush(wx.TRANSPARENT_BRUSH)
|
714
|
+
gc.DrawRectangle(x0, y0, x1 - x0, y1 - y0)
|
715
|
+
|
716
|
+
if not self.nodes:
|
717
|
+
return
|
718
|
+
self.set_pen_widths()
|
719
|
+
if self.p1 is not None and self.p2 is not None:
|
720
|
+
# Selection mode!
|
721
|
+
draw_selection_rectangle()
|
722
|
+
return
|
723
|
+
offset = 5
|
724
|
+
s = math.sqrt(abs(self.scene.widget_root.scene_widget.matrix.determinant))
|
725
|
+
offset /= s
|
726
|
+
gc.SetBrush(wx.TRANSPARENT_BRUSH)
|
727
|
+
idx = -1
|
728
|
+
node = self.element
|
729
|
+
self.calc_and_draw(gc)
|
730
|
+
for entry in self.nodes:
|
731
|
+
idx += 1
|
732
|
+
ptx, pty = node.matrix.point_in_matrix_space(entry["point"])
|
733
|
+
if entry["type"] == "point":
|
734
|
+
if idx == self.selected_index or entry["selected"]:
|
735
|
+
gc.SetPen(self.pen_highlight)
|
736
|
+
gc.SetBrush(self.brush_highlight)
|
737
|
+
factor = 1.25
|
738
|
+
else:
|
739
|
+
gc.SetPen(self.pen)
|
740
|
+
gc.SetBrush(self.brush_normal)
|
741
|
+
factor = 1
|
742
|
+
gc.DrawEllipse(
|
743
|
+
ptx - factor * offset,
|
744
|
+
pty - factor * offset,
|
745
|
+
offset * 2 * factor,
|
746
|
+
offset * 2 * factor,
|
747
|
+
)
|
748
|
+
elif entry["type"] == "control":
|
749
|
+
if idx == self.selected_index or entry["selected"]:
|
750
|
+
factor = 1.25
|
751
|
+
gc.SetPen(self.pen_highlight)
|
752
|
+
else:
|
753
|
+
factor = 1
|
754
|
+
gc.SetPen(self.pen_ctrl)
|
755
|
+
# Do we have a second controlpoint at the same segment?
|
756
|
+
if isinstance(entry["segment"], CubicBezier):
|
757
|
+
orgnode = None
|
758
|
+
if idx > 0 and self.nodes[idx - 1]["type"] == "point":
|
759
|
+
orgnode = self.nodes[idx - 1]
|
760
|
+
elif idx > 1 and self.nodes[idx - 2]["type"] == "point":
|
761
|
+
orgnode = self.nodes[idx - 2]
|
762
|
+
if orgnode is not None and orgnode["selected"]:
|
763
|
+
gc.SetPen(self.pen_ctrl_semi)
|
764
|
+
pattern = [
|
765
|
+
(ptx - factor * offset, pty),
|
766
|
+
(ptx, pty + factor * offset),
|
767
|
+
(ptx + factor * offset, pty),
|
768
|
+
(ptx, pty - factor * offset),
|
769
|
+
(ptx - factor * offset, pty),
|
770
|
+
]
|
771
|
+
gc.DrawLines(pattern)
|
772
|
+
if 0 <= entry["connector"] < len(self.nodes):
|
773
|
+
orgnode = self.nodes[entry["connector"]]
|
774
|
+
org_pt = orgnode["point"]
|
775
|
+
org_ptx, org_pty = node.matrix.point_in_matrix_space(org_pt)
|
776
|
+
pattern = [(ptx, pty), (org_ptx, org_pty)]
|
777
|
+
gc.DrawLines(pattern)
|
778
|
+
elif entry["type"] == "midpoint":
|
779
|
+
if idx == self.selected_index or entry["selected"]:
|
780
|
+
factor = 1.25
|
781
|
+
gc.SetPen(self.pen_highlight)
|
782
|
+
else:
|
783
|
+
factor = 1
|
784
|
+
gc.SetPen(self.pen_ctrl)
|
785
|
+
pattern = [
|
786
|
+
(ptx - factor * offset, pty),
|
787
|
+
(ptx, pty + factor * offset),
|
788
|
+
(ptx + factor * offset, pty),
|
789
|
+
(ptx, pty - factor * offset),
|
790
|
+
(ptx - factor * offset, pty),
|
791
|
+
]
|
792
|
+
gc.DrawLines(pattern)
|
793
|
+
|
794
|
+
def done(self):
|
795
|
+
"""
|
796
|
+
We are done with node editing, so shutdown stuff
|
797
|
+
"""
|
798
|
+
self.scene.pane.tool_active = False
|
799
|
+
self.scene.pane.modif_active = False
|
800
|
+
self.scene.pane.suppress_selection = False
|
801
|
+
self.p1 = None
|
802
|
+
self.p2 = None
|
803
|
+
self.move_type = "node"
|
804
|
+
self.scene.context("tool none\n")
|
805
|
+
self.scene.context.signal("statusmsg", "")
|
806
|
+
self.scene.context.elements.validate_selected_area()
|
807
|
+
self.scene.request_refresh()
|
808
|
+
|
809
|
+
def modify_element(self, reload=True):
|
810
|
+
"""
|
811
|
+
Central routine that tells the system that the node was
|
812
|
+
changed, if 'reload' is set to True then it requires
|
813
|
+
reload/recalculation of the properties (e.g. after the
|
814
|
+
segment structure of a path was changed)
|
815
|
+
"""
|
816
|
+
if self.element is None:
|
817
|
+
return
|
818
|
+
if self.shape is not None:
|
819
|
+
self.element.geometry = Geomstr.svg(Path(self.shape))
|
820
|
+
elif self.path is not None:
|
821
|
+
self.element.geometry = Geomstr.svg(self.path)
|
822
|
+
self.element.altered()
|
823
|
+
try:
|
824
|
+
__ = self.element.bbox()
|
825
|
+
except AttributeError:
|
826
|
+
pass
|
827
|
+
self.scene.context.elements.validate_selected_area()
|
828
|
+
self.scene.request_refresh()
|
829
|
+
self.scene.context.signal("element_property_reload", [self.element])
|
830
|
+
if reload:
|
831
|
+
self.calculate_points(self.element)
|
832
|
+
self.scene.request_refresh()
|
833
|
+
self.enable_rules()
|
834
|
+
|
835
|
+
def clear_selection(self):
|
836
|
+
"""
|
837
|
+
Clears the selection
|
838
|
+
"""
|
839
|
+
if self.nodes is not None:
|
840
|
+
for entry in self.nodes:
|
841
|
+
entry["selected"] = False
|
842
|
+
self.enable_rules()
|
843
|
+
|
844
|
+
def first_segment_in_subpath(self, index):
|
845
|
+
"""
|
846
|
+
Provides the first non-move/close segment in the subpath
|
847
|
+
to which the segment at location index belongs to
|
848
|
+
"""
|
849
|
+
result = None
|
850
|
+
if not self.element is None and hasattr(self.element, "path"):
|
851
|
+
for idx in range(index, -1, -1):
|
852
|
+
seg = self.path[idx]
|
853
|
+
if isinstance(seg, (Move, Close)):
|
854
|
+
break
|
855
|
+
result = seg
|
856
|
+
return result
|
857
|
+
|
858
|
+
def last_segment_in_subpath(self, index):
|
859
|
+
"""
|
860
|
+
Provides the last non-move/close segment in the subpath
|
861
|
+
to which the segment at location index belongs to
|
862
|
+
"""
|
863
|
+
result = None
|
864
|
+
if not self.element is None and hasattr(self.element, "path"):
|
865
|
+
for idx in range(index, len(self.path)):
|
866
|
+
seg = self.path[idx]
|
867
|
+
if isinstance(seg, (Move, Close)):
|
868
|
+
break
|
869
|
+
result = seg
|
870
|
+
return result
|
871
|
+
|
872
|
+
def is_closed_subpath(self, index):
|
873
|
+
"""
|
874
|
+
Provides the last segment in the subpath
|
875
|
+
to which the segment at location index belongs to
|
876
|
+
"""
|
877
|
+
result = False
|
878
|
+
if not self.element is None and hasattr(self.element, "path"):
|
879
|
+
for idx in range(index, len(self.path)):
|
880
|
+
seg = self.path[idx]
|
881
|
+
if isinstance(seg, Move):
|
882
|
+
break
|
883
|
+
if isinstance(seg, Close):
|
884
|
+
result = True
|
885
|
+
break
|
886
|
+
return result
|
887
|
+
|
888
|
+
def convert_to_path(self):
|
889
|
+
"""
|
890
|
+
Converts a polyline element to a path and reloads the scene
|
891
|
+
"""
|
892
|
+
if self.element is None or hasattr(self.element, "path"):
|
893
|
+
return
|
894
|
+
node = self.element
|
895
|
+
oldstuff = []
|
896
|
+
for attrib in ("stroke", "fill", "stroke_width", "stroke_scaled"):
|
897
|
+
if hasattr(node, attrib):
|
898
|
+
oldval = getattr(node, attrib, None)
|
899
|
+
oldstuff.append([attrib, oldval])
|
900
|
+
try:
|
901
|
+
path = node.as_path()
|
902
|
+
# There are some challenges around the treatment
|
903
|
+
# of arcs within svgelements, so let's circumvent
|
904
|
+
# them for the time being (until resolved)
|
905
|
+
# by replacing arc segments with cubic Béziers
|
906
|
+
if node.type in ("elem path", "elem ellipse"):
|
907
|
+
path.approximate_arcs_with_cubics()
|
908
|
+
except AttributeError:
|
909
|
+
return
|
910
|
+
newnode = node.replace_node(path=path, type="elem path")
|
911
|
+
for item in oldstuff:
|
912
|
+
setattr(newnode, item[0], item[1])
|
913
|
+
newnode.altered()
|
914
|
+
self.element = newnode
|
915
|
+
self.shape = None
|
916
|
+
self.path = path
|
917
|
+
self.modify_element(reload=True)
|
918
|
+
|
919
|
+
def toggle_close(self):
|
920
|
+
"""
|
921
|
+
Toggle the closed status for a polyline or path element
|
922
|
+
"""
|
923
|
+
if self.element is None or self.nodes is None:
|
924
|
+
return
|
925
|
+
modified = False
|
926
|
+
if self.node_type == "polyline":
|
927
|
+
dist = (self.shape.points[0].x - self.shape.points[-1].x) ** 2 + (
|
928
|
+
self.shape.points[0].y - self.shape.points[-1].y
|
929
|
+
) ** 2
|
930
|
+
if dist < 1: # Closed
|
931
|
+
newshape = Polyline(self.shape)
|
932
|
+
if len(newshape.points) > 2:
|
933
|
+
newshape.points.pop(-1)
|
934
|
+
else:
|
935
|
+
newshape = Polygon(self.shape)
|
936
|
+
self.shape = newshape
|
937
|
+
modified = True
|
938
|
+
else:
|
939
|
+
dealt_with = []
|
940
|
+
if not self.anyselected:
|
941
|
+
# Let's select the last point, so the last segment will be closed/opened
|
942
|
+
for idx in range(len(self.nodes) - 1, -1, -1):
|
943
|
+
entry = self.nodes[idx]
|
944
|
+
if entry["type"] == "point":
|
945
|
+
entry["selected"] = True
|
946
|
+
break
|
947
|
+
|
948
|
+
for idx in range(len(self.nodes) - 1, -1, -1):
|
949
|
+
entry = self.nodes[idx]
|
950
|
+
if entry["selected"] and entry["type"] == "point":
|
951
|
+
# What's the index of the last selected element
|
952
|
+
# Have we dealt with that before? i.e. not multiple toggles.
|
953
|
+
segstart = entry["start"]
|
954
|
+
if segstart in dealt_with:
|
955
|
+
continue
|
956
|
+
dealt_with.append(segstart)
|
957
|
+
# Let's establish the last segment in the path
|
958
|
+
prevseg = None
|
959
|
+
is_closed = False
|
960
|
+
firstseg = None
|
961
|
+
for sidx in range(segstart, len(self.path), 1):
|
962
|
+
seg = self.path[sidx]
|
963
|
+
if isinstance(seg, Move) and prevseg is None:
|
964
|
+
# Not the one at the very beginning!
|
965
|
+
continue
|
966
|
+
if isinstance(seg, Move):
|
967
|
+
# Ready
|
968
|
+
break
|
969
|
+
if isinstance(seg, Close):
|
970
|
+
# Ready
|
971
|
+
is_closed = True
|
972
|
+
break
|
973
|
+
if firstseg is None:
|
974
|
+
firstseg = seg
|
975
|
+
lastidx = sidx
|
976
|
+
prevseg = seg
|
977
|
+
if firstseg is not None and not is_closed:
|
978
|
+
dist = firstseg.start.distance_to(prevseg.end)
|
979
|
+
if dist < 1:
|
980
|
+
lastidx -= 1
|
981
|
+
is_closed = True
|
982
|
+
# else:
|
983
|
+
# dist = 1e6
|
984
|
+
if is_closed:
|
985
|
+
# it's enough just to delete it...
|
986
|
+
del self.path[lastidx + 1]
|
987
|
+
modified = True
|
988
|
+
else:
|
989
|
+
# Need to insert a Close segment
|
990
|
+
# print(f"Inserting a close, dist={dist:.2f}")
|
991
|
+
# print(
|
992
|
+
# f"First seg, idx={segstart}, type={type(firstseg).__name__}"
|
993
|
+
# )
|
994
|
+
# print(f"Last seg, idx={lastidx}, type={type(prevseg).__name__}")
|
995
|
+
newseg = Close(
|
996
|
+
start=Point(prevseg.end.x, prevseg.end.y),
|
997
|
+
end=Point(prevseg.end.x, prevseg.end.y),
|
998
|
+
)
|
999
|
+
self.path.insert(lastidx + 1, newseg)
|
1000
|
+
modified = True
|
1001
|
+
|
1002
|
+
if modified:
|
1003
|
+
self.modify_element(True)
|
1004
|
+
|
1005
|
+
@staticmethod
|
1006
|
+
def get_bezier_point(segment, t):
|
1007
|
+
"""
|
1008
|
+
Provide a point on the cubic Bézier curve for t (0 <= t <= 1)
|
1009
|
+
Args:
|
1010
|
+
segment (PathSegment): a cubic bezier
|
1011
|
+
t (float): (0 <= t <= 1)
|
1012
|
+
Computation: b(t) = (1-t)^3 * P0 + 3*(1-t)^2*t*P1 + 3*(1-t)*t^2*P2 + t^3 * P3
|
1013
|
+
"""
|
1014
|
+
p0 = segment.start
|
1015
|
+
p1 = segment.control1
|
1016
|
+
p2 = segment.control2
|
1017
|
+
p3 = segment.end
|
1018
|
+
result = (
|
1019
|
+
(1 - t) ** 3 * p0
|
1020
|
+
+ 3 * (1 - t) ** 2 * t * p1
|
1021
|
+
+ 3 * (1 - t) * t**2 * p2
|
1022
|
+
+ t**3 * p3
|
1023
|
+
)
|
1024
|
+
return result
|
1025
|
+
|
1026
|
+
@staticmethod
|
1027
|
+
def revise_bezier_to_point(segment, midpoint, change_2nd_control=False):
|
1028
|
+
"""
|
1029
|
+
Adjust the two control points for a cubic Bézier segment,
|
1030
|
+
so that the given point will lie on the cubic Bézier curve for t=0.5
|
1031
|
+
Args:
|
1032
|
+
segment (PathSegment): a cubic bezier segment to be amended
|
1033
|
+
midpoint (Point): the new point
|
1034
|
+
change_2nd_control: modify the 2nd control point, rather than the first
|
1035
|
+
Computation: b(t) = (1-t)^3 * P0 + 3*(1-t)^2*t*P1 + 3*(1-t)*t^2*P2 + t^3 * P3
|
1036
|
+
"""
|
1037
|
+
t = 0.5
|
1038
|
+
p0 = segment.start
|
1039
|
+
p1 = segment.control1
|
1040
|
+
p2 = segment.control2
|
1041
|
+
p3 = segment.end
|
1042
|
+
if change_2nd_control:
|
1043
|
+
factor = 1 / (3 * (1 - t) * t**2)
|
1044
|
+
result = (
|
1045
|
+
midpoint - (1 - t) ** 3 * p0 - 3 * (1 - t) ** 2 * t * p1 - t**3 * p3
|
1046
|
+
) * factor
|
1047
|
+
segment.control2 = result
|
1048
|
+
else:
|
1049
|
+
factor = 1 / (3 * (1 - t) ** 2 * t)
|
1050
|
+
result = (
|
1051
|
+
midpoint - (1 - t) ** 3 * p0 - 3 * (1 - t) * t**2 * p2 - t**3 * p3
|
1052
|
+
) * factor
|
1053
|
+
segment.control1 = result
|
1054
|
+
|
1055
|
+
def adjust_midpoint(self, index):
|
1056
|
+
"""
|
1057
|
+
Computes and sets the midpoint of a cubic bezier segment
|
1058
|
+
"""
|
1059
|
+
for j in range(3):
|
1060
|
+
k = index + 1 + j
|
1061
|
+
if k < len(self.nodes) and self.nodes[k]["type"] == "midpoint":
|
1062
|
+
self.nodes[k]["point"] = self.get_bezier_point(
|
1063
|
+
self.nodes[index]["segment"], 0.5
|
1064
|
+
)
|
1065
|
+
break
|
1066
|
+
|
1067
|
+
def smoothen(self):
|
1068
|
+
"""
|
1069
|
+
Smoothen a circular bezier segment to adjacent segments, i.e. adjust
|
1070
|
+
the control points so that they are an extension of the previous/next segment
|
1071
|
+
"""
|
1072
|
+
if self.element is None or self.nodes is None:
|
1073
|
+
return
|
1074
|
+
modified = False
|
1075
|
+
if self.node_type == "polyline":
|
1076
|
+
# Not valid for a polyline Could make a path now but that might be more than the user expected...
|
1077
|
+
return
|
1078
|
+
for entry in self.nodes:
|
1079
|
+
if entry["selected"] and entry["segtype"] == "C": # Cubic Bezier only
|
1080
|
+
segment = entry["segment"]
|
1081
|
+
pt_start = segment.start
|
1082
|
+
pt_end = segment.end
|
1083
|
+
other_segment = entry["prev"]
|
1084
|
+
if other_segment is not None:
|
1085
|
+
if isinstance(other_segment, Line):
|
1086
|
+
other_pt_x = other_segment.start.x
|
1087
|
+
other_pt_y = other_segment.start.y
|
1088
|
+
dx = pt_start.x - other_pt_x
|
1089
|
+
dy = pt_start.y - other_pt_y
|
1090
|
+
segment.control1.x = pt_start.x + 0.25 * dx
|
1091
|
+
segment.control1.y = pt_start.y + 0.25 * dy
|
1092
|
+
modified = True
|
1093
|
+
elif isinstance(other_segment, CubicBezier):
|
1094
|
+
other_pt_x = other_segment.control2.x
|
1095
|
+
other_pt_y = other_segment.control2.y
|
1096
|
+
dx = pt_start.x - other_pt_x
|
1097
|
+
dy = pt_start.y - other_pt_y
|
1098
|
+
segment.control1.x = pt_start.x + dx
|
1099
|
+
segment.control1.y = pt_start.y + dy
|
1100
|
+
modified = True
|
1101
|
+
elif isinstance(other_segment, QuadraticBezier):
|
1102
|
+
other_pt_x = other_segment.control.x
|
1103
|
+
other_pt_y = other_segment.control.y
|
1104
|
+
dx = pt_start.x - other_pt_x
|
1105
|
+
dy = pt_start.y - other_pt_y
|
1106
|
+
segment.control1.x = pt_start.x + dx
|
1107
|
+
segment.control1.y = pt_start.y + dy
|
1108
|
+
modified = True
|
1109
|
+
elif isinstance(other_segment, Arc):
|
1110
|
+
# We need the tangent in the end-point,
|
1111
|
+
other_pt_x = other_segment.end.x
|
1112
|
+
other_pt_y = other_segment.end.y
|
1113
|
+
dx = pt_start.x - other_pt_x
|
1114
|
+
dy = pt_start.y - other_pt_y
|
1115
|
+
segment.control1.x = pt_start.x + dx
|
1116
|
+
segment.control1.y = pt_start.y + dy
|
1117
|
+
modified = True
|
1118
|
+
other_segment = entry["next"]
|
1119
|
+
if other_segment is not None:
|
1120
|
+
if isinstance(other_segment, Line):
|
1121
|
+
other_pt_x = other_segment.end.x
|
1122
|
+
other_pt_y = other_segment.end.y
|
1123
|
+
dx = pt_end.x - other_pt_x
|
1124
|
+
dy = pt_end.y - other_pt_y
|
1125
|
+
segment.control2.x = pt_end.x + 0.25 * dx
|
1126
|
+
segment.control2.y = pt_end.y + 0.25 * dy
|
1127
|
+
modified = True
|
1128
|
+
elif isinstance(other_segment, CubicBezier):
|
1129
|
+
other_pt_x = other_segment.control1.x
|
1130
|
+
other_pt_y = other_segment.control1.y
|
1131
|
+
dx = pt_end.x - other_pt_x
|
1132
|
+
dy = pt_end.y - other_pt_y
|
1133
|
+
segment.control2.x = pt_end.x + dx
|
1134
|
+
segment.control2.y = pt_end.y + dy
|
1135
|
+
modified = True
|
1136
|
+
elif isinstance(other_segment, QuadraticBezier):
|
1137
|
+
other_pt_x = other_segment.control.x
|
1138
|
+
other_pt_y = other_segment.control.y
|
1139
|
+
dx = pt_end.x - other_pt_x
|
1140
|
+
dy = pt_end.y - other_pt_y
|
1141
|
+
segment.control2.x = pt_end.x + dx
|
1142
|
+
segment.control2.y = pt_end.y + dy
|
1143
|
+
modified = True
|
1144
|
+
elif isinstance(other_segment, Arc):
|
1145
|
+
# We need the tangent in the end-point,
|
1146
|
+
other_pt_x = other_segment.start.x
|
1147
|
+
other_pt_y = other_segment.start.y
|
1148
|
+
dx = pt_end.x - other_pt_x
|
1149
|
+
dy = pt_end.y - other_pt_y
|
1150
|
+
segment.control2.x = pt_end.x + dx
|
1151
|
+
segment.control2.y = pt_end.y + dy
|
1152
|
+
modified = True
|
1153
|
+
if modified:
|
1154
|
+
self.modify_element(True)
|
1155
|
+
|
1156
|
+
def smoothen_all(self):
|
1157
|
+
"""
|
1158
|
+
Convert all segments of the path that are not cubic Béziers into
|
1159
|
+
such segments and apply the same smoothen logic as in smoothen(),
|
1160
|
+
i.e. adjust the control points of two neighbouring segments
|
1161
|
+
so that the three points
|
1162
|
+
'prev control2' - 'prev/end=next start' - 'next control1'
|
1163
|
+
are collinear
|
1164
|
+
"""
|
1165
|
+
if self.element is None or self.nodes is None:
|
1166
|
+
return
|
1167
|
+
modified = False
|
1168
|
+
if self.node_type == "polyline":
|
1169
|
+
# Not valid for a polyline Could make a path now but that might be more than the user expected...
|
1170
|
+
return
|
1171
|
+
# Pass 1 - make all lines a cubic bezier
|
1172
|
+
for idx, segment in enumerate(self.path):
|
1173
|
+
if isinstance(segment, Line):
|
1174
|
+
startpt = copy(segment.start)
|
1175
|
+
endpt = copy(segment.end)
|
1176
|
+
ctrl1pt = Point(
|
1177
|
+
startpt.x + 0.25 * (endpt.x - startpt.x),
|
1178
|
+
startpt.y + 0.25 * (endpt.y - startpt.y),
|
1179
|
+
)
|
1180
|
+
ctrl2pt = Point(
|
1181
|
+
startpt.x + 0.75 * (endpt.x - startpt.x),
|
1182
|
+
startpt.y + 0.75 * (endpt.y - startpt.y),
|
1183
|
+
)
|
1184
|
+
newsegment = CubicBezier(
|
1185
|
+
start=startpt, end=endpt, control1=ctrl1pt, control2=ctrl2pt
|
1186
|
+
)
|
1187
|
+
self.path[idx] = newsegment
|
1188
|
+
modified = True
|
1189
|
+
elif isinstance(segment, QuadraticBezier):
|
1190
|
+
# The cubic control - points lie on 2/3 of the way of the
|
1191
|
+
# line-segments from the endpoint to the quadratic control-point
|
1192
|
+
startpt = copy(segment.start)
|
1193
|
+
endpt = copy(segment.end)
|
1194
|
+
dx = segment.control.x - startpt.x
|
1195
|
+
dy = segment.control.y - startpt.y
|
1196
|
+
ctrl1pt = Point(startpt.x + 2 / 3 * dx, startpt.y + 2 / 3 * dy)
|
1197
|
+
dx = segment.control.x - endpt.x
|
1198
|
+
dy = segment.control.y - endpt.y
|
1199
|
+
ctrl2pt = Point(endpt.x + 2 / 3 * dx, endpt.y + 2 / 3 * dy)
|
1200
|
+
newsegment = CubicBezier(
|
1201
|
+
start=startpt, end=endpt, control1=ctrl1pt, control2=ctrl2pt
|
1202
|
+
)
|
1203
|
+
self.path[idx] = newsegment
|
1204
|
+
modified = True
|
1205
|
+
elif isinstance(segment, Arc):
|
1206
|
+
for newsegment in list(segment.as_cubic_curves(1)):
|
1207
|
+
self.path[idx] = newsegment
|
1208
|
+
break
|
1209
|
+
modified = True
|
1210
|
+
# Pass 2 - make all control lines align
|
1211
|
+
prevseg = None
|
1212
|
+
lastidx = len(self.path) - 1
|
1213
|
+
for idx, segment in enumerate(self.path):
|
1214
|
+
nextseg = None
|
1215
|
+
if idx < lastidx:
|
1216
|
+
nextseg = self.path[idx + 1]
|
1217
|
+
if isinstance(nextseg, (Move, Close)):
|
1218
|
+
nextseg = None
|
1219
|
+
if isinstance(segment, CubicBezier):
|
1220
|
+
if prevseg is None:
|
1221
|
+
if self.is_closed_subpath(idx):
|
1222
|
+
otherseg = self.last_segment_in_subpath(idx)
|
1223
|
+
prevseg = Line(
|
1224
|
+
start=Point(otherseg.end.x, otherseg.end.y),
|
1225
|
+
end=Point(segment.start.x, segment.start.y),
|
1226
|
+
)
|
1227
|
+
if prevseg is not None:
|
1228
|
+
angle1 = Point.angle(prevseg.end, prevseg.start)
|
1229
|
+
angle2 = Point.angle(segment.start, segment.end)
|
1230
|
+
d_angle = math.tau / 2 - (angle1 - angle2)
|
1231
|
+
while d_angle >= math.tau:
|
1232
|
+
d_angle -= math.tau
|
1233
|
+
while d_angle < -math.tau:
|
1234
|
+
d_angle += math.tau
|
1235
|
+
|
1236
|
+
# print (f"to prev: Angle 1 = {angle1/math.tau*360:.1f}°, Angle 2 = {angle2/math.tau*360:.1f}°, Delta = {d_angle/math.tau*360:.1f}°")
|
1237
|
+
dist = segment.start.distance_to(segment.control1)
|
1238
|
+
candidate1 = Point.polar(segment.start, angle2 - d_angle / 2, dist)
|
1239
|
+
candidate2 = Point.polar(segment.start, angle1 + d_angle / 2, dist)
|
1240
|
+
if segment.end.distance_to(candidate1) < segment.end.distance_to(
|
1241
|
+
candidate2
|
1242
|
+
):
|
1243
|
+
segment.control1 = candidate1
|
1244
|
+
else:
|
1245
|
+
segment.control1 = candidate2
|
1246
|
+
modified = True
|
1247
|
+
if nextseg is None:
|
1248
|
+
if self.is_closed_subpath(idx):
|
1249
|
+
otherseg = self.first_segment_in_subpath(idx)
|
1250
|
+
nextseg = Line(
|
1251
|
+
start=Point(segment.end.x, segment.end.y),
|
1252
|
+
end=Point(otherseg.start.x, otherseg.start.y),
|
1253
|
+
)
|
1254
|
+
if nextseg is not None:
|
1255
|
+
angle1 = Point.angle(segment.end, segment.start)
|
1256
|
+
angle2 = Point.angle(nextseg.start, nextseg.end)
|
1257
|
+
d_angle = math.tau / 2 - (angle1 - angle2)
|
1258
|
+
while d_angle >= math.tau:
|
1259
|
+
d_angle -= math.tau
|
1260
|
+
while d_angle < -math.tau:
|
1261
|
+
d_angle += math.tau
|
1262
|
+
|
1263
|
+
# print (f"to next: Angle 1 = {angle1/math.tau*360:.1f}°, Angle 2 = {angle2/math.tau*360:.1f}°, Delta = {d_angle/math.tau*360:.1f}°")
|
1264
|
+
dist = segment.end.distance_to(segment.control2)
|
1265
|
+
candidate1 = Point.polar(segment.end, angle2 - d_angle / 2, dist)
|
1266
|
+
candidate2 = Point.polar(segment.end, angle1 + d_angle / 2, dist)
|
1267
|
+
if segment.start.distance_to(
|
1268
|
+
candidate1
|
1269
|
+
) < segment.start.distance_to(candidate2):
|
1270
|
+
segment.control2 = candidate1
|
1271
|
+
else:
|
1272
|
+
segment.control2 = candidate2
|
1273
|
+
modified = True
|
1274
|
+
if isinstance(segment, (Move, Close)):
|
1275
|
+
prevseg = None
|
1276
|
+
else:
|
1277
|
+
prevseg = segment
|
1278
|
+
if modified:
|
1279
|
+
self.modify_element(True)
|
1280
|
+
|
1281
|
+
def cubic_symmetrical(self):
|
1282
|
+
"""
|
1283
|
+
Adjust the two control points control1 and control2 of a cubic segment
|
1284
|
+
so that they are symmetrical to the perpendicular bisector on start - end
|
1285
|
+
"""
|
1286
|
+
if self.element is None or self.nodes is None:
|
1287
|
+
return
|
1288
|
+
modified = False
|
1289
|
+
if self.node_type == "polyline":
|
1290
|
+
# Not valid for a polyline Could make a path now but that might be more than the user expected...
|
1291
|
+
return
|
1292
|
+
for entry in self.nodes:
|
1293
|
+
if entry["selected"] and entry["segtype"] == "C": # Cubic Bezier only
|
1294
|
+
segment = entry["segment"]
|
1295
|
+
pt_start = segment.start
|
1296
|
+
pt_end = segment.end
|
1297
|
+
midpoint = Point(
|
1298
|
+
(pt_end.x + pt_start.x) / 2, (pt_end.y + pt_start.y) / 2
|
1299
|
+
)
|
1300
|
+
angle_to_end = midpoint.angle_to(pt_end)
|
1301
|
+
angle_to_start = midpoint.angle_to(pt_start)
|
1302
|
+
angle_to_control2 = midpoint.angle_to(segment.control2)
|
1303
|
+
distance = midpoint.distance_to(segment.control2)
|
1304
|
+
# The new point
|
1305
|
+
angle_to_control1 = angle_to_start + (angle_to_end - angle_to_control2)
|
1306
|
+
# newx = midpoint.x + distance * math.cos(angle_to_control1)
|
1307
|
+
# newy = midpoint.y + distance * math.sin(angle_to_control1)
|
1308
|
+
# segment.control1 = Point(newx, newy)
|
1309
|
+
segment.control1 = Point.polar(
|
1310
|
+
(midpoint.x, midpoint.y), angle_to_control1, distance
|
1311
|
+
)
|
1312
|
+
modified = True
|
1313
|
+
if modified:
|
1314
|
+
self.modify_element(True)
|
1315
|
+
|
1316
|
+
def delete_nodes(self):
|
1317
|
+
"""
|
1318
|
+
Delete all selected (point) nodes
|
1319
|
+
"""
|
1320
|
+
if self.element is None or self.nodes is None:
|
1321
|
+
return
|
1322
|
+
modified = False
|
1323
|
+
for idx in range(len(self.nodes) - 1, -1, -1):
|
1324
|
+
entry = self.nodes[idx]
|
1325
|
+
if entry["selected"] and entry["type"] == "point":
|
1326
|
+
if self.node_type == "polyline":
|
1327
|
+
if len(self.shape.points) > 2:
|
1328
|
+
modified = True
|
1329
|
+
self.shape.points.pop(idx)
|
1330
|
+
else:
|
1331
|
+
break
|
1332
|
+
else:
|
1333
|
+
idx = entry["pathindex"]
|
1334
|
+
prevseg = None
|
1335
|
+
nextseg = None
|
1336
|
+
seg = self.path[idx]
|
1337
|
+
if idx > 0:
|
1338
|
+
prevseg = self.path[idx - 1]
|
1339
|
+
if idx < len(self.path) - 1:
|
1340
|
+
nextseg = self.path[idx + 1]
|
1341
|
+
if nextseg is None:
|
1342
|
+
# Last point of the path
|
1343
|
+
# Can just be deleted, provided we have something
|
1344
|
+
# in front...
|
1345
|
+
if prevseg is None or isinstance(prevseg, (Move, Close)):
|
1346
|
+
continue
|
1347
|
+
del self.path[idx]
|
1348
|
+
modified = True
|
1349
|
+
elif isinstance(nextseg, (Move, Close)):
|
1350
|
+
# last point of the subsegment...
|
1351
|
+
# We need to have another full segment in the front
|
1352
|
+
# otherwise we would end up with a single point...
|
1353
|
+
if prevseg is None or isinstance(prevseg, (Move, Close)):
|
1354
|
+
continue
|
1355
|
+
nextseg.start.x = seg.start.x
|
1356
|
+
nextseg.start.y = seg.start.y
|
1357
|
+
if isinstance(nextseg, Close):
|
1358
|
+
nextseg.end.x = seg.start.x
|
1359
|
+
nextseg.end.y = seg.start.y
|
1360
|
+
|
1361
|
+
del self.path[idx]
|
1362
|
+
modified = True
|
1363
|
+
else:
|
1364
|
+
# Could be the first point...
|
1365
|
+
if prevseg is None and (
|
1366
|
+
nextseg is None or isinstance(nextseg, (Move, Close))
|
1367
|
+
):
|
1368
|
+
continue
|
1369
|
+
if prevseg is None: # # Move
|
1370
|
+
seg.end = Point(nextseg.end.x, nextseg.end.y)
|
1371
|
+
del self.path[idx + 1]
|
1372
|
+
modified = True
|
1373
|
+
elif isinstance(seg, Move): # # Move
|
1374
|
+
seg.end = Point(nextseg.end.x, nextseg.end.y)
|
1375
|
+
del self.path[idx + 1]
|
1376
|
+
modified = True
|
1377
|
+
else:
|
1378
|
+
nextseg.start.x = prevseg.end.x
|
1379
|
+
nextseg.start.y = prevseg.end.y
|
1380
|
+
del self.path[idx]
|
1381
|
+
modified = True
|
1382
|
+
|
1383
|
+
if modified:
|
1384
|
+
self.modify_element(True)
|
1385
|
+
|
1386
|
+
def convert_to_line(self):
|
1387
|
+
"""
|
1388
|
+
Convert all selected segments to a line
|
1389
|
+
"""
|
1390
|
+
if self.element is None or self.nodes is None:
|
1391
|
+
return
|
1392
|
+
modified = False
|
1393
|
+
if self.node_type == "polyline":
|
1394
|
+
# Not valid for a polyline Could make a path now but that might be more than the user expected...
|
1395
|
+
return
|
1396
|
+
for entry in self.nodes:
|
1397
|
+
if entry["selected"] and entry["type"] == "point":
|
1398
|
+
idx = entry["pathindex"]
|
1399
|
+
if entry["segment"] is None or entry["segment"].start is None:
|
1400
|
+
continue
|
1401
|
+
startpt = Point(entry["segment"].start.x, entry["segment"].start.y)
|
1402
|
+
endpt = Point(entry["segment"].end.x, entry["segment"].end.y)
|
1403
|
+
if entry["segtype"] not in ("C", "Q", "A"):
|
1404
|
+
continue
|
1405
|
+
newsegment = Line(start=startpt, end=endpt)
|
1406
|
+
self.path[idx] = newsegment
|
1407
|
+
modified = True
|
1408
|
+
if modified:
|
1409
|
+
self.modify_element(True)
|
1410
|
+
|
1411
|
+
def linear_all(self):
|
1412
|
+
"""
|
1413
|
+
Convert all segments of the path to a line
|
1414
|
+
"""
|
1415
|
+
if self.element is None or self.nodes is None:
|
1416
|
+
return
|
1417
|
+
modified = False
|
1418
|
+
if self.node_type == "polyline":
|
1419
|
+
# Not valid for a polyline Could make a path now but that might be more than the user expected...
|
1420
|
+
return
|
1421
|
+
for idx, segment in enumerate(self.path):
|
1422
|
+
if isinstance(segment, (Close, Move, Line)):
|
1423
|
+
continue
|
1424
|
+
startpt = Point(segment.start.x, segment.start.y)
|
1425
|
+
endpt = Point(segment.end.x, segment.end.y)
|
1426
|
+
newsegment = Line(start=startpt, end=endpt)
|
1427
|
+
self.path[idx] = newsegment
|
1428
|
+
modified = True
|
1429
|
+
|
1430
|
+
if modified:
|
1431
|
+
self.modify_element(True)
|
1432
|
+
|
1433
|
+
def convert_to_curve(self):
|
1434
|
+
"""
|
1435
|
+
Convert all selected segments to a circular bezier
|
1436
|
+
"""
|
1437
|
+
if self.element is None or self.nodes is None:
|
1438
|
+
return
|
1439
|
+
modified = False
|
1440
|
+
if self.node_type == "polyline":
|
1441
|
+
# Not valid for a polyline Could make a path now but that might be more than the user expected...
|
1442
|
+
return
|
1443
|
+
for entry in self.nodes:
|
1444
|
+
if entry["selected"] and entry["type"] == "point":
|
1445
|
+
idx = entry["pathindex"]
|
1446
|
+
if entry["segment"] is None or entry["segment"].start is None:
|
1447
|
+
continue
|
1448
|
+
startpt = Point(entry["segment"].start.x, entry["segment"].start.y)
|
1449
|
+
endpt = Point(entry["segment"].end.x, entry["segment"].end.y)
|
1450
|
+
if entry["segtype"] == "L":
|
1451
|
+
ctrl1pt = Point(
|
1452
|
+
startpt.x + 0.25 * (endpt.x - startpt.x),
|
1453
|
+
startpt.y + 0.25 * (endpt.y - startpt.y),
|
1454
|
+
)
|
1455
|
+
ctrl2pt = Point(
|
1456
|
+
startpt.x + 0.75 * (endpt.x - startpt.x),
|
1457
|
+
startpt.y + 0.75 * (endpt.y - startpt.y),
|
1458
|
+
)
|
1459
|
+
elif entry["segtype"] == "Q":
|
1460
|
+
ctrl1pt = Point(
|
1461
|
+
entry["segment"].control.x, entry["segment"].control.y
|
1462
|
+
)
|
1463
|
+
ctrl2pt = Point(endpt.x, endpt.y)
|
1464
|
+
elif entry["segtype"] == "A":
|
1465
|
+
ctrl1pt = Point(
|
1466
|
+
startpt.x + 0.25 * (endpt.x - startpt.x),
|
1467
|
+
startpt.y + 0.25 * (endpt.y - startpt.y),
|
1468
|
+
)
|
1469
|
+
ctrl2pt = Point(
|
1470
|
+
startpt.x + 0.75 * (endpt.x - startpt.x),
|
1471
|
+
startpt.y + 0.75 * (endpt.y - startpt.y),
|
1472
|
+
)
|
1473
|
+
else:
|
1474
|
+
continue
|
1475
|
+
|
1476
|
+
newsegment = CubicBezier(
|
1477
|
+
start=startpt, end=endpt, control1=ctrl1pt, control2=ctrl2pt
|
1478
|
+
)
|
1479
|
+
self.path[idx] = newsegment
|
1480
|
+
modified = True
|
1481
|
+
if modified:
|
1482
|
+
self.modify_element(True)
|
1483
|
+
|
1484
|
+
def break_path(self):
|
1485
|
+
"""
|
1486
|
+
Break a path at the selected (point) nodes
|
1487
|
+
"""
|
1488
|
+
if self.element is None or self.nodes is None:
|
1489
|
+
return
|
1490
|
+
# Stub for breaking the path
|
1491
|
+
modified = False
|
1492
|
+
if self.node_type == "polyline":
|
1493
|
+
# Not valid for a polyline Could make a path now but that might be more than the user expected...
|
1494
|
+
return
|
1495
|
+
for idx in range(len(self.nodes) - 1, -1, -1):
|
1496
|
+
entry = self.nodes[idx]
|
1497
|
+
if entry["selected"] and entry["type"] == "point":
|
1498
|
+
idx = entry["pathindex"]
|
1499
|
+
seg = entry["segment"]
|
1500
|
+
if isinstance(seg, (Move, Close)):
|
1501
|
+
continue
|
1502
|
+
# Is this the last point? Then no use to break the path
|
1503
|
+
nextseg = None
|
1504
|
+
if idx in (0, len(self.path) - 1):
|
1505
|
+
# Don't break at the first or last point
|
1506
|
+
continue
|
1507
|
+
nextseg = self.path[idx + 1]
|
1508
|
+
if isinstance(nextseg, (Move, Close)):
|
1509
|
+
# Not at end of subpath
|
1510
|
+
continue
|
1511
|
+
prevseg = self.path[idx - 1]
|
1512
|
+
if isinstance(prevseg, (Move, Close)):
|
1513
|
+
# We could still be at the end point of the first segment...
|
1514
|
+
if entry["point"] == seg.start:
|
1515
|
+
# Not at start of subpath
|
1516
|
+
continue
|
1517
|
+
newseg = Move(
|
1518
|
+
start=Point(seg.end.x, seg.end.y),
|
1519
|
+
end=Point(nextseg.start.x, nextseg.start.y),
|
1520
|
+
)
|
1521
|
+
self.path.insert(idx + 1, newseg)
|
1522
|
+
# Now let's validate whether the 'right' path still has a
|
1523
|
+
# close segment at its end. That will be removed as this would
|
1524
|
+
# create an unwanted behaviour
|
1525
|
+
prevseg = None
|
1526
|
+
is_closed = False
|
1527
|
+
for sidx in range(idx + 1, len(self.path), 1):
|
1528
|
+
seg = self.path[sidx]
|
1529
|
+
if isinstance(seg, Move) and prevseg is None:
|
1530
|
+
# Not the one at the very beginning!
|
1531
|
+
continue
|
1532
|
+
if isinstance(seg, Move):
|
1533
|
+
# Ready
|
1534
|
+
break
|
1535
|
+
if isinstance(seg, Close):
|
1536
|
+
# Ready
|
1537
|
+
is_closed = True
|
1538
|
+
break
|
1539
|
+
lastidx = sidx
|
1540
|
+
prevseg = seg
|
1541
|
+
if is_closed:
|
1542
|
+
# it's enough just to delete it...
|
1543
|
+
del self.path[lastidx + 1]
|
1544
|
+
|
1545
|
+
modified = True
|
1546
|
+
if modified:
|
1547
|
+
self.modify_element(True)
|
1548
|
+
|
1549
|
+
def join_path(self):
|
1550
|
+
"""
|
1551
|
+
Join two selected (point) nodes if they are on different subpath
|
1552
|
+
"""
|
1553
|
+
if self.element is None or self.nodes is None:
|
1554
|
+
return
|
1555
|
+
modified = False
|
1556
|
+
if self.node_type == "polyline":
|
1557
|
+
# Not valid for a polyline
|
1558
|
+
return
|
1559
|
+
for idx in range(len(self.nodes) - 1, -1, -1):
|
1560
|
+
entry = self.nodes[idx]
|
1561
|
+
if entry["selected"] and entry["type"] == "point":
|
1562
|
+
idx = entry["pathindex"]
|
1563
|
+
seg = entry["segment"]
|
1564
|
+
prevseg = None
|
1565
|
+
nextseg = None
|
1566
|
+
if idx > 0:
|
1567
|
+
prevseg = self.path[idx - 1]
|
1568
|
+
if idx < len(self.path) - 1:
|
1569
|
+
nextseg = self.path[idx + 1]
|
1570
|
+
if isinstance(seg, (Move, Close)):
|
1571
|
+
# Beginning of path
|
1572
|
+
if prevseg is None:
|
1573
|
+
# Very beginning?! Ignore...
|
1574
|
+
continue
|
1575
|
+
if nextseg is None:
|
1576
|
+
continue
|
1577
|
+
if isinstance(nextseg, (Move, Close)):
|
1578
|
+
# Two consecutive moves? Ignore....
|
1579
|
+
continue
|
1580
|
+
nextseg.start.x = seg.start.x
|
1581
|
+
nextseg.start.y = seg.start.y
|
1582
|
+
del self.path[idx]
|
1583
|
+
modified = True
|
1584
|
+
else:
|
1585
|
+
# Let's look at the next segment
|
1586
|
+
if nextseg is None:
|
1587
|
+
continue
|
1588
|
+
if not isinstance(nextseg, Move):
|
1589
|
+
continue
|
1590
|
+
seg.end.x = nextseg.end.x
|
1591
|
+
seg.end.y = nextseg.end.y
|
1592
|
+
del self.path[idx + 1]
|
1593
|
+
modified = True
|
1594
|
+
|
1595
|
+
if modified:
|
1596
|
+
self.modify_element(True)
|
1597
|
+
|
1598
|
+
def insert_midpoint(self):
|
1599
|
+
"""
|
1600
|
+
Insert a point in the middle of a selected segment
|
1601
|
+
"""
|
1602
|
+
if self.element is None or self.nodes is None:
|
1603
|
+
return
|
1604
|
+
modified = False
|
1605
|
+
# Move backwards as len will change
|
1606
|
+
for idx in range(len(self.nodes) - 1, -1, -1):
|
1607
|
+
entry = self.nodes[idx]
|
1608
|
+
if entry["selected"] and entry["type"] == "point":
|
1609
|
+
if self.node_type == "polyline":
|
1610
|
+
pt1 = self.shape.points[idx]
|
1611
|
+
if idx == 0:
|
1612
|
+
# Very first point? Mirror first segment and take midpoint
|
1613
|
+
pt2 = Point(
|
1614
|
+
self.shape.points[idx + 1].x,
|
1615
|
+
self.shape.points[idx + 1].y,
|
1616
|
+
)
|
1617
|
+
pt2.x = pt1.x - (pt2.x - pt1.x)
|
1618
|
+
pt2.y = pt1.y - (pt2.y - pt1.y)
|
1619
|
+
pt2.x = (pt1.x + pt2.x) / 2
|
1620
|
+
pt2.y = (pt1.y + pt2.y) / 2
|
1621
|
+
self.shape.points.insert(0, pt2)
|
1622
|
+
else:
|
1623
|
+
pt2 = Point(
|
1624
|
+
self.shape.points[idx - 1].x,
|
1625
|
+
self.shape.points[idx - 1].y,
|
1626
|
+
)
|
1627
|
+
pt2.x = (pt1.x + pt2.x) / 2
|
1628
|
+
pt2.y = (pt1.y + pt2.y) / 2
|
1629
|
+
# Mid point
|
1630
|
+
self.shape.points.insert(idx, pt2)
|
1631
|
+
modified = True
|
1632
|
+
else:
|
1633
|
+
# Path
|
1634
|
+
idx = entry["pathindex"]
|
1635
|
+
if entry["segment"] is None:
|
1636
|
+
continue
|
1637
|
+
segment = entry["segment"]
|
1638
|
+
|
1639
|
+
# def pt_info(pt):
|
1640
|
+
# return f"({pt.x:.0f}, {pt.y:.0f})"
|
1641
|
+
|
1642
|
+
if entry["segtype"] == "L":
|
1643
|
+
# Line
|
1644
|
+
mid_x = (segment.start.x + segment.end.x) / 2
|
1645
|
+
mid_y = (segment.start.y + segment.end.y) / 2
|
1646
|
+
newsegment = Line(
|
1647
|
+
start=Point(mid_x, mid_y),
|
1648
|
+
end=Point(segment.end.x, segment.end.y),
|
1649
|
+
)
|
1650
|
+
self.path.insert(idx + 1, newsegment)
|
1651
|
+
# path.insert may change the start and end point
|
1652
|
+
# of the segement to make sure it maintains a
|
1653
|
+
# contiguous path, so we need to set it again...
|
1654
|
+
newsegment.start.x = mid_x
|
1655
|
+
newsegment.start.y = mid_y
|
1656
|
+
segment.end.x = mid_x
|
1657
|
+
segment.end.y = mid_y
|
1658
|
+
modified = True
|
1659
|
+
elif entry["segtype"] == "C":
|
1660
|
+
midpoint = segment.point(0.5)
|
1661
|
+
mid_x = midpoint.x
|
1662
|
+
mid_y = midpoint.y
|
1663
|
+
newsegment = CubicBezier(
|
1664
|
+
start=Point(mid_x, mid_y),
|
1665
|
+
end=Point(segment.end.x, segment.end.y),
|
1666
|
+
control1=Point(mid_x, mid_y),
|
1667
|
+
control2=Point(segment.control2.x, segment.control2.y),
|
1668
|
+
)
|
1669
|
+
self.path.insert(idx + 1, newsegment)
|
1670
|
+
segment.end.x = mid_x
|
1671
|
+
segment.end.y = mid_y
|
1672
|
+
segment.control2.x = mid_x
|
1673
|
+
segment.control2.y = mid_y
|
1674
|
+
newsegment.start.x = mid_x
|
1675
|
+
newsegment.start.y = mid_y
|
1676
|
+
modified = True
|
1677
|
+
elif entry["segtype"] == "A":
|
1678
|
+
midpoint = segment.point(0.5)
|
1679
|
+
mid_x = midpoint.x
|
1680
|
+
mid_y = midpoint.y
|
1681
|
+
# newsegment = Arc(
|
1682
|
+
# start=Point(mid_x, mid_y),
|
1683
|
+
# end=Point(segment.end.x, segment.end.y),
|
1684
|
+
# control=Point(segment.center.x, segment.center.y),
|
1685
|
+
# )
|
1686
|
+
newsegment = copy(segment)
|
1687
|
+
newsegment.start.x = mid_x
|
1688
|
+
newsegment.start.y = mid_y
|
1689
|
+
self.path.insert(idx + 1, newsegment)
|
1690
|
+
segment.end.x = mid_x
|
1691
|
+
segment.end.y = mid_y
|
1692
|
+
newsegment.start.x = mid_x
|
1693
|
+
newsegment.start.y = mid_y
|
1694
|
+
modified = True
|
1695
|
+
elif entry["segtype"] == "Q":
|
1696
|
+
midpoint = segment.point(0.5)
|
1697
|
+
mid_x = midpoint.x
|
1698
|
+
mid_y = midpoint.y
|
1699
|
+
newsegment = QuadraticBezier(
|
1700
|
+
start=Point(mid_x, mid_y),
|
1701
|
+
end=Point(segment.end.x, segment.end.y),
|
1702
|
+
control=Point(segment.control.x, segment.control.y),
|
1703
|
+
)
|
1704
|
+
self.path.insert(idx + 1, newsegment)
|
1705
|
+
segment.end.x = mid_x
|
1706
|
+
segment.end.y = mid_y
|
1707
|
+
segment.control.x = mid_x
|
1708
|
+
segment.control.y = mid_y
|
1709
|
+
newsegment.start.x = mid_x
|
1710
|
+
newsegment.start.y = mid_y
|
1711
|
+
modified = True
|
1712
|
+
elif entry["segtype"] == "M":
|
1713
|
+
# Very first point? Mirror first segment and take midpoint
|
1714
|
+
nextseg = entry["next"]
|
1715
|
+
if nextseg is None:
|
1716
|
+
continue
|
1717
|
+
p1x = nextseg.start.x
|
1718
|
+
p1y = nextseg.start.y
|
1719
|
+
p2x = nextseg.end.x
|
1720
|
+
p2y = nextseg.end.y
|
1721
|
+
p2x = p1x - (p2x - p1x)
|
1722
|
+
p2y = p1y - (p2y - p1y)
|
1723
|
+
pt1 = Point((p1x + p2x) / 2, (p1y + p2y) / 2)
|
1724
|
+
pt2 = copy(nextseg.start)
|
1725
|
+
newsegment = Line(start=pt1, end=pt2)
|
1726
|
+
self.path.insert(idx + 1, newsegment)
|
1727
|
+
segment.end = pt1
|
1728
|
+
newsegment.start.x = pt1.x
|
1729
|
+
newsegment.start.y = pt1.y
|
1730
|
+
# We need to step forward to assess whether there is a close segment
|
1731
|
+
for idx2 in range(idx + 1, len(self.path)):
|
1732
|
+
if isinstance(self.path[idx2], Move):
|
1733
|
+
break
|
1734
|
+
if isinstance(self.path[idx2], Close):
|
1735
|
+
# Adjust the close segment to that it points again
|
1736
|
+
# to the first move end
|
1737
|
+
self.path[idx2].end = Point(pt1.x, pt1.y)
|
1738
|
+
break
|
1739
|
+
|
1740
|
+
modified = True
|
1741
|
+
|
1742
|
+
if modified:
|
1743
|
+
self.modify_element(True)
|
1744
|
+
|
1745
|
+
def append_line(self):
|
1746
|
+
"""
|
1747
|
+
Append a point to the selected element, works all the time and does not require a valid selection
|
1748
|
+
"""
|
1749
|
+
if self.element is None or self.nodes is None:
|
1750
|
+
return
|
1751
|
+
modified = False
|
1752
|
+
if self.node_type == "polyline":
|
1753
|
+
idx = len(self.shape.points) - 1
|
1754
|
+
pt1 = self.shape.points[idx - 1]
|
1755
|
+
pt2 = self.shape.points[idx]
|
1756
|
+
newpt = Point(pt2.x + (pt2.x - pt1.x), pt2.y + (pt2.y - pt1.y))
|
1757
|
+
self.shape.points.append(newpt)
|
1758
|
+
modified = True
|
1759
|
+
else:
|
1760
|
+
# path
|
1761
|
+
try:
|
1762
|
+
valididx = len(self.path) - 1
|
1763
|
+
except AttributeError:
|
1764
|
+
# Shape
|
1765
|
+
return
|
1766
|
+
while valididx >= 0 and isinstance(self.path[valididx], (Close, Move)):
|
1767
|
+
valididx -= 1
|
1768
|
+
if valididx >= 0:
|
1769
|
+
seg = self.path[valididx]
|
1770
|
+
pt1 = seg.start
|
1771
|
+
pt2 = seg.end
|
1772
|
+
newpt = Point(pt2.x + (pt2.x - pt1.x), pt2.y + (pt2.y - pt1.y))
|
1773
|
+
newsegment = Line(start=Point(seg.end.x, seg.end.y), end=newpt)
|
1774
|
+
if valididx < len(self.path) - 1:
|
1775
|
+
if self.path[valididx + 1].end == self.path[valididx + 1].start:
|
1776
|
+
self.path[valididx + 1].end.x = newpt.x
|
1777
|
+
self.path[valididx + 1].end.y = newpt.y
|
1778
|
+
self.path[valididx + 1].start.x = newpt.x
|
1779
|
+
self.path[valididx + 1].start.y = newpt.y
|
1780
|
+
|
1781
|
+
self.path.insert(valididx + 1, newsegment)
|
1782
|
+
newsegment.start.x = seg.end.x
|
1783
|
+
newsegment.start.y = seg.end.y
|
1784
|
+
modified = True
|
1785
|
+
|
1786
|
+
if modified:
|
1787
|
+
self.modify_element(True)
|
1788
|
+
|
1789
|
+
@property
|
1790
|
+
def anyselected(self):
|
1791
|
+
if self.nodes:
|
1792
|
+
for entry in self.nodes:
|
1793
|
+
if entry["selected"]:
|
1794
|
+
return True
|
1795
|
+
return False
|
1796
|
+
|
1797
|
+
def event(
|
1798
|
+
self,
|
1799
|
+
window_pos=None,
|
1800
|
+
space_pos=None,
|
1801
|
+
event_type=None,
|
1802
|
+
nearest_snap=None,
|
1803
|
+
modifiers=None,
|
1804
|
+
keycode=None,
|
1805
|
+
**kwargs,
|
1806
|
+
):
|
1807
|
+
"""
|
1808
|
+
The routine dealing with propagated scene events
|
1809
|
+
|
1810
|
+
Args:
|
1811
|
+
window_pos (tuple): The coordinates of the mouse position in window coordinates
|
1812
|
+
space_pos (tuple): The coordinates of the mouse position in scene coordinates
|
1813
|
+
event_type (string): [description]. Defaults to None.
|
1814
|
+
nearest_snap (tuple, optional): If set the coordinates of the nearest snap point in scene coordinates.
|
1815
|
+
modifiers (string): If available provides a list of modifier keys that were pressed (shift, alt, ctrl).
|
1816
|
+
keycode (string): if available the keycode that was pressed
|
1817
|
+
|
1818
|
+
Returns:
|
1819
|
+
Indicator how to proceed with this event after its execution (consume, chain etc.)
|
1820
|
+
"""
|
1821
|
+
if self.scene.pane.active_tool != "edit":
|
1822
|
+
return RESPONSE_CHAIN
|
1823
|
+
# print (f"event: {event_type}, modifiers: '{modifiers}', keycode: '{keycode}'")
|
1824
|
+
offset = 5
|
1825
|
+
s = math.sqrt(abs(self.scene.widget_root.scene_widget.matrix.determinant))
|
1826
|
+
offset /= s
|
1827
|
+
elements = self.scene.context.elements
|
1828
|
+
if event_type in ("leftdown", "leftclick"):
|
1829
|
+
self.pen = wx.Pen()
|
1830
|
+
self.pen.SetColour(wx.Colour(swizzlecolor(elements.default_stroke)))
|
1831
|
+
self.pen.SetWidth(25)
|
1832
|
+
self.scene.pane.tool_active = True
|
1833
|
+
self.scene.pane.modif_active = True
|
1834
|
+
|
1835
|
+
self.scene.context.signal("statusmsg", self.message)
|
1836
|
+
self.move_type = "node"
|
1837
|
+
|
1838
|
+
xp = space_pos[0]
|
1839
|
+
yp = space_pos[1]
|
1840
|
+
if self.nodes:
|
1841
|
+
w = offset * 4
|
1842
|
+
h = offset * 4
|
1843
|
+
node = self.element
|
1844
|
+
for i, entry in enumerate(self.nodes):
|
1845
|
+
pt = entry["point"]
|
1846
|
+
ptx, pty = node.matrix.point_in_matrix_space(pt)
|
1847
|
+
x = ptx - 2 * offset
|
1848
|
+
y = pty - 2 * offset
|
1849
|
+
if x <= xp <= x + w and y <= yp <= y + h:
|
1850
|
+
self.selected_index = i
|
1851
|
+
if entry["type"] == "control":
|
1852
|
+
# We select the corresponding segment
|
1853
|
+
for entry2 in self.nodes:
|
1854
|
+
entry2["selected"] = False
|
1855
|
+
orgnode = None
|
1856
|
+
for j in range(0, 3):
|
1857
|
+
k = i - j - 1
|
1858
|
+
if k >= 0 and self.nodes[k]["type"] == "point":
|
1859
|
+
orgnode = self.nodes[k]
|
1860
|
+
break
|
1861
|
+
if orgnode is not None:
|
1862
|
+
orgnode["selected"] = True
|
1863
|
+
entry["selected"] = True
|
1864
|
+
else:
|
1865
|
+
# Shift-Key Pressed?
|
1866
|
+
if "shift" not in modifiers:
|
1867
|
+
self.clear_selection()
|
1868
|
+
entry["selected"] = True
|
1869
|
+
else:
|
1870
|
+
entry["selected"] = not entry["selected"]
|
1871
|
+
break
|
1872
|
+
else: # For-else == icky
|
1873
|
+
self.selected_index = None
|
1874
|
+
self.enable_rules()
|
1875
|
+
if self.selected_index is None:
|
1876
|
+
if event_type == "leftclick":
|
1877
|
+
# Have we clicked outside the bbox? Then we call it a day...
|
1878
|
+
outside = False
|
1879
|
+
if not self.element:
|
1880
|
+
# Element is required.
|
1881
|
+
return RESPONSE_CONSUME
|
1882
|
+
bb = self.element.bbox()
|
1883
|
+
if bb is None:
|
1884
|
+
return RESPONSE_CONSUME
|
1885
|
+
if space_pos[0] < bb[0] or space_pos[0] > bb[2]:
|
1886
|
+
outside = True
|
1887
|
+
if space_pos[1] < bb[1] or space_pos[1] > bb[3]:
|
1888
|
+
outside = True
|
1889
|
+
if outside:
|
1890
|
+
self.done()
|
1891
|
+
return RESPONSE_CONSUME
|
1892
|
+
else:
|
1893
|
+
# Clear selection
|
1894
|
+
self.clear_selection()
|
1895
|
+
self.scene.request_refresh()
|
1896
|
+
else:
|
1897
|
+
# Fine we start a selection rectangle to select multiple nodes
|
1898
|
+
self.move_type = "selection"
|
1899
|
+
self.p1 = complex(space_pos[0], space_pos[1])
|
1900
|
+
else:
|
1901
|
+
self.scene.request_refresh()
|
1902
|
+
return RESPONSE_CONSUME
|
1903
|
+
elif event_type == "rightdown":
|
1904
|
+
# We stop
|
1905
|
+
self.done()
|
1906
|
+
return RESPONSE_CONSUME
|
1907
|
+
elif event_type == "move":
|
1908
|
+
if self.move_type == "selection":
|
1909
|
+
if self.p1 is not None:
|
1910
|
+
self.p2 = complex(space_pos[0], space_pos[1])
|
1911
|
+
self.scene.request_refresh()
|
1912
|
+
else:
|
1913
|
+
if self.selected_index is None or self.selected_index < 0:
|
1914
|
+
self.scene.request_refresh()
|
1915
|
+
return RESPONSE_CONSUME
|
1916
|
+
current = self.nodes[self.selected_index]
|
1917
|
+
pt = current["point"]
|
1918
|
+
if nearest_snap is None:
|
1919
|
+
spt = Point(space_pos[0], space_pos[1])
|
1920
|
+
else:
|
1921
|
+
spt = Point(nearest_snap[0], nearest_snap[1])
|
1922
|
+
|
1923
|
+
m = self.element.matrix.point_in_inverse_space(spt)
|
1924
|
+
# Special treatment for the virtual midpoint:
|
1925
|
+
if current["type"] == "midpoint" and self.node_type == "path":
|
1926
|
+
self.scene.context.signal(
|
1927
|
+
"statusmsg",
|
1928
|
+
_(
|
1929
|
+
"Drag to change the curve shape (ctrl to affect the other side)"
|
1930
|
+
),
|
1931
|
+
)
|
1932
|
+
idx = self.selected_index
|
1933
|
+
newpt = Point(m[0], m[1])
|
1934
|
+
change2nd = bool("ctrl" in modifiers)
|
1935
|
+
self.revise_bezier_to_point(
|
1936
|
+
current["segment"], newpt, change_2nd_control=change2nd
|
1937
|
+
)
|
1938
|
+
self.modify_element(False)
|
1939
|
+
self.calculate_points(self.element)
|
1940
|
+
self.selected_index = idx
|
1941
|
+
self.nodes[idx]["selected"] = True
|
1942
|
+
orgnode = None
|
1943
|
+
for j in range(0, 3):
|
1944
|
+
k = idx - j - 1
|
1945
|
+
if k >= 0 and self.nodes[k]["type"] == "point":
|
1946
|
+
orgnode = self.nodes[k]
|
1947
|
+
break
|
1948
|
+
if orgnode is not None:
|
1949
|
+
orgnode["selected"] = True
|
1950
|
+
self.scene.request_refresh()
|
1951
|
+
return RESPONSE_CONSUME
|
1952
|
+
pt.x = m[0]
|
1953
|
+
pt.y = m[1]
|
1954
|
+
if self.node_type == "path":
|
1955
|
+
current["point"] = pt
|
1956
|
+
# We need to adjust the start-point of the next segment
|
1957
|
+
# unless it's a closed path then we need to adjust the
|
1958
|
+
# very first - need to be mindful of closed subpaths
|
1959
|
+
if current["segtype"] == "M":
|
1960
|
+
# We changed the end, let's check whether the last segment in
|
1961
|
+
# the subpath is a Close then we need to change this .end as well
|
1962
|
+
for nidx in range(self.selected_index + 1, len(self.path), 1):
|
1963
|
+
nextseg = self.path[nidx]
|
1964
|
+
if isinstance(nextseg, Move):
|
1965
|
+
break
|
1966
|
+
if isinstance(nextseg, Close):
|
1967
|
+
nextseg.end.x = m[0]
|
1968
|
+
nextseg.end.y = m[1]
|
1969
|
+
break
|
1970
|
+
nextseg = current["next"]
|
1971
|
+
if nextseg is not None and nextseg.start is not None:
|
1972
|
+
nextseg.start.x = m[0]
|
1973
|
+
nextseg.start.y = m[1]
|
1974
|
+
|
1975
|
+
if isinstance(current["segment"], CubicBezier):
|
1976
|
+
self.adjust_midpoint(self.selected_index)
|
1977
|
+
elif isinstance(current["segment"], Move):
|
1978
|
+
if nextseg is not None and isinstance(nextseg, CubicBezier):
|
1979
|
+
self.adjust_midpoint(self.selected_index + 1)
|
1980
|
+
|
1981
|
+
# self.debug_path()
|
1982
|
+
self.modify_element(False)
|
1983
|
+
return RESPONSE_CONSUME
|
1984
|
+
elif event_type == "key_down":
|
1985
|
+
if not self.scene.pane.tool_active:
|
1986
|
+
return RESPONSE_CHAIN
|
1987
|
+
# print (f"event: {event_type}, modifiers: '{modifiers}', keycode: '{keycode}'")
|
1988
|
+
return RESPONSE_CONSUME
|
1989
|
+
elif event_type == "key_up":
|
1990
|
+
if not self.scene.pane.tool_active:
|
1991
|
+
return RESPONSE_CHAIN
|
1992
|
+
# print (f"event: {event_type}, modifiers: '{modifiers}', keycode: '{keycode}'")
|
1993
|
+
if modifiers == "escape":
|
1994
|
+
self.done()
|
1995
|
+
return RESPONSE_CONSUME
|
1996
|
+
# print(f"Key: '{keycode}'")
|
1997
|
+
# if self.selected_index is not None:
|
1998
|
+
# entry = self.nodes[self.selected_index]
|
1999
|
+
# else:
|
2000
|
+
# entry = None
|
2001
|
+
self.perform_action(modifiers)
|
2002
|
+
|
2003
|
+
return RESPONSE_CONSUME
|
2004
|
+
|
2005
|
+
elif event_type == "lost":
|
2006
|
+
if self.scene.pane.tool_active:
|
2007
|
+
self.done()
|
2008
|
+
return RESPONSE_CONSUME
|
2009
|
+
else:
|
2010
|
+
return RESPONSE_CHAIN
|
2011
|
+
elif event_type == "leftup":
|
2012
|
+
if (
|
2013
|
+
self.move_type == "selection"
|
2014
|
+
and self.p1 is not None
|
2015
|
+
and self.p2 is not None
|
2016
|
+
):
|
2017
|
+
if "shift" not in modifiers:
|
2018
|
+
self.clear_selection()
|
2019
|
+
x0 = min(self.p1.real, self.p2.real)
|
2020
|
+
y0 = min(self.p1.imag, self.p2.imag)
|
2021
|
+
x1 = max(self.p1.real, self.p2.real)
|
2022
|
+
y1 = max(self.p1.imag, self.p2.imag)
|
2023
|
+
dx = self.p1.real - self.p2.real
|
2024
|
+
dy = self.p1.imag - self.p2.imag
|
2025
|
+
if abs(dx) < 1e-10 or abs(dy) < 1e-10:
|
2026
|
+
return RESPONSE_CONSUME
|
2027
|
+
# We select all points (not controls) inside
|
2028
|
+
if self.element:
|
2029
|
+
for entry in self.nodes:
|
2030
|
+
pt = entry["point"]
|
2031
|
+
if (
|
2032
|
+
entry["type"] == "point"
|
2033
|
+
and x0 <= pt.x <= x1
|
2034
|
+
and y0 <= pt.y <= y1
|
2035
|
+
):
|
2036
|
+
entry["selected"] = True
|
2037
|
+
self.scene.request_refresh()
|
2038
|
+
self.enable_rules()
|
2039
|
+
self.p1 = None
|
2040
|
+
self.p2 = None
|
2041
|
+
return RESPONSE_CONSUME
|
2042
|
+
return RESPONSE_DROP
|
2043
|
+
|
2044
|
+
def perform_action(self, code):
|
2045
|
+
"""
|
2046
|
+
Translates a keycode into a command to execute
|
2047
|
+
"""
|
2048
|
+
# print(f"Perform action called with {code}")
|
2049
|
+
if self.element is None or self.nodes is None:
|
2050
|
+
return
|
2051
|
+
if code in self.commands:
|
2052
|
+
action = self.commands[code]
|
2053
|
+
# print(f"Execute {action[1]}")
|
2054
|
+
action[0]()
|
2055
|
+
# else:
|
2056
|
+
# print (f"Did not find {code}")
|
2057
|
+
|
2058
|
+
def _tool_change(self):
|
2059
|
+
selected_node = None
|
2060
|
+
elements = self.scene.context.elements.elem_branch
|
2061
|
+
for node in elements.flat(emphasized=True):
|
2062
|
+
if node.type in ("elem path", "elem polyline"):
|
2063
|
+
selected_node = node
|
2064
|
+
break
|
2065
|
+
self.scene.pane.suppress_selection = selected_node is not None
|
2066
|
+
if selected_node is None:
|
2067
|
+
self.done()
|
2068
|
+
else:
|
2069
|
+
self.calculate_points(selected_node)
|
2070
|
+
self.enable_rules()
|
2071
|
+
self.scene.request_refresh()
|
2072
|
+
|
2073
|
+
def signal(self, signal, *args, **kwargs):
|
2074
|
+
"""
|
2075
|
+
Signal routine for stuff that's passed along within a scene,
|
2076
|
+
does not receive global signals
|
2077
|
+
"""
|
2078
|
+
# print(f"Signal: {signal}")
|
2079
|
+
if signal == "tool_changed":
|
2080
|
+
if len(args) > 0 and len(args[0]) > 1 and args[0][1] == "edit":
|
2081
|
+
self._tool_change()
|
2082
|
+
return
|
2083
|
+
elif signal == "rebuild_tree":
|
2084
|
+
self._tool_change()
|
2085
|
+
elif signal == "emphasized":
|
2086
|
+
self._tool_change()
|
2087
|
+
if self.element is None:
|
2088
|
+
return
|