meerk40t 0.9.3001__py2.py3-none-any.whl → 0.9.7020__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- meerk40t/__init__.py +1 -1
- meerk40t/balormk/balor_params.py +167 -167
- meerk40t/balormk/clone_loader.py +457 -457
- meerk40t/balormk/controller.py +1566 -1512
- meerk40t/balormk/cylindermod.py +64 -0
- meerk40t/balormk/device.py +966 -1959
- meerk40t/balormk/driver.py +778 -591
- meerk40t/balormk/galvo_commands.py +1194 -0
- meerk40t/balormk/gui/balorconfig.py +237 -111
- meerk40t/balormk/gui/balorcontroller.py +191 -184
- meerk40t/balormk/gui/baloroperationproperties.py +116 -115
- meerk40t/balormk/gui/corscene.py +845 -0
- meerk40t/balormk/gui/gui.py +179 -147
- meerk40t/balormk/livelightjob.py +466 -382
- meerk40t/balormk/mock_connection.py +131 -109
- meerk40t/balormk/plugin.py +133 -135
- meerk40t/balormk/usb_connection.py +306 -301
- meerk40t/camera/__init__.py +1 -1
- meerk40t/camera/camera.py +514 -397
- meerk40t/camera/gui/camerapanel.py +1241 -1095
- meerk40t/camera/gui/gui.py +58 -58
- meerk40t/camera/plugin.py +441 -399
- meerk40t/ch341/__init__.py +27 -27
- meerk40t/ch341/ch341device.py +628 -628
- meerk40t/ch341/libusb.py +595 -589
- meerk40t/ch341/mock.py +171 -171
- meerk40t/ch341/windriver.py +157 -157
- meerk40t/constants.py +13 -0
- meerk40t/core/__init__.py +1 -1
- meerk40t/core/bindalias.py +550 -539
- meerk40t/core/core.py +47 -47
- meerk40t/core/cutcode/cubiccut.py +73 -73
- meerk40t/core/cutcode/cutcode.py +315 -312
- meerk40t/core/cutcode/cutgroup.py +141 -137
- meerk40t/core/cutcode/cutobject.py +192 -185
- meerk40t/core/cutcode/dwellcut.py +37 -37
- meerk40t/core/cutcode/gotocut.py +29 -29
- meerk40t/core/cutcode/homecut.py +29 -29
- meerk40t/core/cutcode/inputcut.py +34 -34
- meerk40t/core/cutcode/linecut.py +33 -33
- meerk40t/core/cutcode/outputcut.py +34 -34
- meerk40t/core/cutcode/plotcut.py +335 -335
- meerk40t/core/cutcode/quadcut.py +61 -61
- meerk40t/core/cutcode/rastercut.py +168 -148
- meerk40t/core/cutcode/waitcut.py +34 -34
- meerk40t/core/cutplan.py +1843 -1316
- meerk40t/core/drivers.py +330 -329
- meerk40t/core/elements/align.py +801 -669
- meerk40t/core/elements/branches.py +1858 -1507
- meerk40t/core/elements/clipboard.py +229 -219
- meerk40t/core/elements/element_treeops.py +4595 -2837
- meerk40t/core/elements/element_types.py +125 -105
- meerk40t/core/elements/elements.py +4315 -3617
- meerk40t/core/elements/files.py +117 -64
- meerk40t/core/elements/geometry.py +473 -224
- meerk40t/core/elements/grid.py +467 -316
- meerk40t/core/elements/materials.py +158 -94
- meerk40t/core/elements/notes.py +50 -38
- meerk40t/core/elements/offset_clpr.py +934 -912
- meerk40t/core/elements/offset_mk.py +963 -955
- meerk40t/core/elements/penbox.py +339 -267
- meerk40t/core/elements/placements.py +300 -83
- meerk40t/core/elements/render.py +785 -687
- meerk40t/core/elements/shapes.py +2618 -2092
- meerk40t/core/elements/testcases.py +105 -0
- meerk40t/core/elements/trace.py +651 -563
- meerk40t/core/elements/tree_commands.py +415 -409
- meerk40t/core/elements/undo_redo.py +116 -58
- meerk40t/core/elements/wordlist.py +319 -200
- meerk40t/core/exceptions.py +9 -9
- meerk40t/core/laserjob.py +220 -220
- meerk40t/core/logging.py +63 -63
- meerk40t/core/node/blobnode.py +83 -86
- meerk40t/core/node/bootstrap.py +105 -103
- meerk40t/core/node/branch_elems.py +40 -31
- meerk40t/core/node/branch_ops.py +45 -38
- meerk40t/core/node/branch_regmark.py +48 -41
- meerk40t/core/node/cutnode.py +29 -32
- meerk40t/core/node/effect_hatch.py +375 -257
- meerk40t/core/node/effect_warp.py +398 -0
- meerk40t/core/node/effect_wobble.py +441 -309
- meerk40t/core/node/elem_ellipse.py +404 -309
- meerk40t/core/node/elem_image.py +1082 -801
- meerk40t/core/node/elem_line.py +358 -292
- meerk40t/core/node/elem_path.py +259 -201
- meerk40t/core/node/elem_point.py +129 -102
- meerk40t/core/node/elem_polyline.py +310 -246
- meerk40t/core/node/elem_rect.py +376 -286
- meerk40t/core/node/elem_text.py +445 -418
- meerk40t/core/node/filenode.py +59 -40
- meerk40t/core/node/groupnode.py +138 -74
- meerk40t/core/node/image_processed.py +777 -766
- meerk40t/core/node/image_raster.py +156 -113
- meerk40t/core/node/layernode.py +31 -31
- meerk40t/core/node/mixins.py +135 -107
- meerk40t/core/node/node.py +1427 -1304
- meerk40t/core/node/nutils.py +117 -114
- meerk40t/core/node/op_cut.py +463 -335
- meerk40t/core/node/op_dots.py +296 -251
- meerk40t/core/node/op_engrave.py +414 -311
- meerk40t/core/node/op_image.py +755 -369
- meerk40t/core/node/op_raster.py +787 -522
- meerk40t/core/node/place_current.py +37 -40
- meerk40t/core/node/place_point.py +329 -126
- meerk40t/core/node/refnode.py +58 -47
- meerk40t/core/node/rootnode.py +225 -219
- meerk40t/core/node/util_console.py +48 -48
- meerk40t/core/node/util_goto.py +84 -65
- meerk40t/core/node/util_home.py +61 -61
- meerk40t/core/node/util_input.py +102 -102
- meerk40t/core/node/util_output.py +102 -102
- meerk40t/core/node/util_wait.py +65 -65
- meerk40t/core/parameters.py +709 -707
- meerk40t/core/planner.py +875 -785
- meerk40t/core/plotplanner.py +656 -652
- meerk40t/core/space.py +120 -113
- meerk40t/core/spoolers.py +706 -705
- meerk40t/core/svg_io.py +1836 -1549
- meerk40t/core/treeop.py +534 -445
- meerk40t/core/undos.py +278 -124
- meerk40t/core/units.py +784 -680
- meerk40t/core/view.py +393 -322
- meerk40t/core/webhelp.py +62 -62
- meerk40t/core/wordlist.py +513 -504
- meerk40t/cylinder/cylinder.py +247 -0
- meerk40t/cylinder/gui/cylindersettings.py +41 -0
- meerk40t/cylinder/gui/gui.py +24 -0
- meerk40t/device/__init__.py +1 -1
- meerk40t/device/basedevice.py +322 -123
- meerk40t/device/devicechoices.py +50 -0
- meerk40t/device/dummydevice.py +163 -128
- meerk40t/device/gui/defaultactions.py +618 -602
- meerk40t/device/gui/effectspanel.py +114 -0
- meerk40t/device/gui/formatterpanel.py +253 -290
- meerk40t/device/gui/warningpanel.py +337 -260
- meerk40t/device/mixins.py +13 -13
- meerk40t/dxf/__init__.py +1 -1
- meerk40t/dxf/dxf_io.py +766 -554
- meerk40t/dxf/plugin.py +47 -35
- meerk40t/external_plugins.py +79 -79
- meerk40t/external_plugins_build.py +28 -28
- meerk40t/extra/cag.py +112 -116
- meerk40t/extra/coolant.py +403 -0
- meerk40t/extra/encode_detect.py +204 -0
- meerk40t/extra/ezd.py +1165 -1165
- meerk40t/extra/hershey.py +834 -340
- meerk40t/extra/imageactions.py +322 -316
- meerk40t/extra/inkscape.py +628 -622
- meerk40t/extra/lbrn.py +424 -424
- meerk40t/extra/outerworld.py +283 -0
- meerk40t/extra/param_functions.py +1542 -1556
- meerk40t/extra/potrace.py +257 -253
- meerk40t/extra/serial_exchange.py +118 -0
- meerk40t/extra/updater.py +602 -453
- meerk40t/extra/vectrace.py +147 -146
- meerk40t/extra/winsleep.py +83 -83
- meerk40t/extra/xcs_reader.py +597 -0
- meerk40t/fill/fills.py +781 -335
- meerk40t/fill/patternfill.py +1061 -1061
- meerk40t/fill/patterns.py +614 -567
- meerk40t/grbl/control.py +87 -87
- meerk40t/grbl/controller.py +990 -903
- meerk40t/grbl/device.py +1084 -768
- meerk40t/grbl/driver.py +989 -771
- meerk40t/grbl/emulator.py +532 -497
- meerk40t/grbl/gcodejob.py +783 -767
- meerk40t/grbl/gui/grblconfiguration.py +373 -298
- meerk40t/grbl/gui/grblcontroller.py +485 -271
- meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
- meerk40t/grbl/gui/grbloperationconfig.py +105 -0
- meerk40t/grbl/gui/gui.py +147 -116
- meerk40t/grbl/interpreter.py +44 -44
- meerk40t/grbl/loader.py +22 -22
- meerk40t/grbl/mock_connection.py +56 -56
- meerk40t/grbl/plugin.py +294 -264
- meerk40t/grbl/serial_connection.py +93 -88
- meerk40t/grbl/tcp_connection.py +81 -79
- meerk40t/grbl/ws_connection.py +112 -0
- meerk40t/gui/__init__.py +1 -1
- meerk40t/gui/about.py +2042 -296
- meerk40t/gui/alignment.py +1644 -1608
- meerk40t/gui/autoexec.py +199 -0
- meerk40t/gui/basicops.py +791 -670
- meerk40t/gui/bufferview.py +77 -71
- meerk40t/gui/busy.py +232 -133
- meerk40t/gui/choicepropertypanel.py +1662 -1469
- meerk40t/gui/consolepanel.py +706 -542
- meerk40t/gui/devicepanel.py +687 -581
- meerk40t/gui/dialogoptions.py +110 -107
- meerk40t/gui/executejob.py +316 -306
- meerk40t/gui/fonts.py +90 -90
- meerk40t/gui/functionwrapper.py +252 -0
- meerk40t/gui/gui_mixins.py +729 -0
- meerk40t/gui/guicolors.py +205 -182
- meerk40t/gui/help_assets/help_assets.py +218 -201
- meerk40t/gui/helper.py +154 -0
- meerk40t/gui/hersheymanager.py +1440 -846
- meerk40t/gui/icons.py +3422 -2747
- meerk40t/gui/imagesplitter.py +555 -508
- meerk40t/gui/keymap.py +354 -344
- meerk40t/gui/laserpanel.py +897 -806
- meerk40t/gui/laserrender.py +1470 -1232
- meerk40t/gui/lasertoolpanel.py +805 -793
- meerk40t/gui/magnetoptions.py +436 -0
- meerk40t/gui/materialmanager.py +2944 -0
- meerk40t/gui/materialtest.py +1722 -1694
- meerk40t/gui/mkdebug.py +646 -359
- meerk40t/gui/mwindow.py +163 -140
- meerk40t/gui/navigationpanels.py +2605 -2467
- meerk40t/gui/notes.py +143 -142
- meerk40t/gui/opassignment.py +414 -410
- meerk40t/gui/operation_info.py +310 -299
- meerk40t/gui/plugin.py +500 -328
- meerk40t/gui/position.py +714 -669
- meerk40t/gui/preferences.py +901 -650
- meerk40t/gui/propertypanels/attributes.py +1461 -1131
- meerk40t/gui/propertypanels/blobproperty.py +117 -114
- meerk40t/gui/propertypanels/consoleproperty.py +83 -80
- meerk40t/gui/propertypanels/gotoproperty.py +77 -0
- meerk40t/gui/propertypanels/groupproperties.py +223 -217
- meerk40t/gui/propertypanels/hatchproperty.py +489 -469
- meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
- meerk40t/gui/propertypanels/inputproperty.py +59 -58
- meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
- meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
- meerk40t/gui/propertypanels/outputproperty.py +59 -58
- meerk40t/gui/propertypanels/pathproperty.py +389 -380
- meerk40t/gui/propertypanels/placementproperty.py +1214 -383
- meerk40t/gui/propertypanels/pointproperty.py +140 -136
- meerk40t/gui/propertypanels/propertywindow.py +313 -181
- meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
- meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
- meerk40t/gui/propertypanels/textproperty.py +770 -755
- meerk40t/gui/propertypanels/waitproperty.py +56 -55
- meerk40t/gui/propertypanels/warpproperty.py +121 -0
- meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
- meerk40t/gui/ribbon.py +2471 -2210
- meerk40t/gui/scene/scene.py +1100 -1051
- meerk40t/gui/scene/sceneconst.py +22 -22
- meerk40t/gui/scene/scenepanel.py +439 -349
- meerk40t/gui/scene/scenespacewidget.py +365 -365
- meerk40t/gui/scene/widget.py +518 -505
- meerk40t/gui/scenewidgets/affinemover.py +215 -215
- meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
- meerk40t/gui/scenewidgets/bedwidget.py +120 -97
- meerk40t/gui/scenewidgets/elementswidget.py +137 -107
- meerk40t/gui/scenewidgets/gridwidget.py +785 -745
- meerk40t/gui/scenewidgets/guidewidget.py +765 -765
- meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
- meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
- meerk40t/gui/scenewidgets/nodeselector.py +28 -28
- meerk40t/gui/scenewidgets/rectselectwidget.py +592 -346
- meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
- meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
- meerk40t/gui/scenewidgets/selectionwidget.py +2958 -2756
- meerk40t/gui/simpleui.py +362 -333
- meerk40t/gui/simulation.py +2451 -2094
- meerk40t/gui/snapoptions.py +208 -203
- meerk40t/gui/spoolerpanel.py +1227 -1180
- meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
- meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
- meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
- meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
- meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
- meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
- meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
- meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
- meerk40t/gui/themes.py +200 -78
- meerk40t/gui/tips.py +590 -0
- meerk40t/gui/toolwidgets/circlebrush.py +35 -35
- meerk40t/gui/toolwidgets/toolcircle.py +248 -242
- meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
- meerk40t/gui/toolwidgets/tooldraw.py +97 -90
- meerk40t/gui/toolwidgets/toolellipse.py +219 -212
- meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
- meerk40t/gui/toolwidgets/toolline.py +39 -144
- meerk40t/gui/toolwidgets/toollinetext.py +79 -236
- meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
- meerk40t/gui/toolwidgets/toolmeasure.py +163 -216
- meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
- meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
- meerk40t/gui/toolwidgets/toolparameter.py +754 -668
- meerk40t/gui/toolwidgets/toolplacement.py +108 -108
- meerk40t/gui/toolwidgets/toolpoint.py +68 -59
- meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
- meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
- meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
- meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
- meerk40t/gui/toolwidgets/toolrect.py +211 -207
- meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
- meerk40t/gui/toolwidgets/toolribbon.py +598 -113
- meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
- meerk40t/gui/toolwidgets/tooltext.py +98 -89
- meerk40t/gui/toolwidgets/toolvector.py +213 -204
- meerk40t/gui/toolwidgets/toolwidget.py +39 -39
- meerk40t/gui/usbconnect.py +98 -91
- meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
- meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
- meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
- meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
- meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
- meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
- meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
- meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
- meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
- meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
- meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
- meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
- meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
- meerk40t/gui/wordlisteditor.py +985 -931
- meerk40t/gui/wxmeerk40t.py +1447 -1169
- meerk40t/gui/wxmmain.py +5644 -4112
- meerk40t/gui/wxmribbon.py +1591 -1076
- meerk40t/gui/wxmscene.py +1631 -1453
- meerk40t/gui/wxmtree.py +2416 -2089
- meerk40t/gui/wxutils.py +1769 -1099
- meerk40t/gui/zmatrix.py +102 -102
- meerk40t/image/__init__.py +1 -1
- meerk40t/image/dither.py +429 -0
- meerk40t/image/imagetools.py +2793 -2269
- meerk40t/internal_plugins.py +150 -130
- meerk40t/kernel/__init__.py +63 -12
- meerk40t/kernel/channel.py +259 -212
- meerk40t/kernel/context.py +538 -538
- meerk40t/kernel/exceptions.py +41 -41
- meerk40t/kernel/functions.py +463 -414
- meerk40t/kernel/jobs.py +100 -100
- meerk40t/kernel/kernel.py +3828 -3571
- meerk40t/kernel/lifecycles.py +71 -71
- meerk40t/kernel/module.py +49 -49
- meerk40t/kernel/service.py +147 -147
- meerk40t/kernel/settings.py +383 -343
- meerk40t/lihuiyu/controller.py +883 -876
- meerk40t/lihuiyu/device.py +1181 -1069
- meerk40t/lihuiyu/driver.py +1466 -1372
- meerk40t/lihuiyu/gui/gui.py +127 -106
- meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
- meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
- meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
- meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
- meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
- meerk40t/lihuiyu/interpreter.py +53 -53
- meerk40t/lihuiyu/laserspeed.py +450 -450
- meerk40t/lihuiyu/loader.py +90 -90
- meerk40t/lihuiyu/parser.py +404 -404
- meerk40t/lihuiyu/plugin.py +101 -102
- meerk40t/lihuiyu/tcp_connection.py +111 -109
- meerk40t/main.py +231 -165
- meerk40t/moshi/builder.py +788 -781
- meerk40t/moshi/controller.py +505 -499
- meerk40t/moshi/device.py +495 -442
- meerk40t/moshi/driver.py +862 -696
- meerk40t/moshi/gui/gui.py +78 -76
- meerk40t/moshi/gui/moshicontrollergui.py +538 -522
- meerk40t/moshi/gui/moshidrivergui.py +87 -75
- meerk40t/moshi/plugin.py +43 -43
- meerk40t/network/console_server.py +140 -57
- meerk40t/network/kernelserver.py +10 -9
- meerk40t/network/tcp_server.py +142 -140
- meerk40t/network/udp_server.py +103 -77
- meerk40t/network/web_server.py +404 -0
- meerk40t/newly/controller.py +1158 -1144
- meerk40t/newly/device.py +874 -732
- meerk40t/newly/driver.py +540 -412
- meerk40t/newly/gui/gui.py +219 -188
- meerk40t/newly/gui/newlyconfig.py +116 -101
- meerk40t/newly/gui/newlycontroller.py +193 -186
- meerk40t/newly/gui/operationproperties.py +51 -51
- meerk40t/newly/mock_connection.py +82 -82
- meerk40t/newly/newly_params.py +56 -56
- meerk40t/newly/plugin.py +1214 -1246
- meerk40t/newly/usb_connection.py +322 -322
- meerk40t/rotary/gui/gui.py +52 -46
- meerk40t/rotary/gui/rotarysettings.py +240 -232
- meerk40t/rotary/rotary.py +202 -98
- meerk40t/ruida/control.py +291 -91
- meerk40t/ruida/controller.py +138 -1088
- meerk40t/ruida/device.py +676 -231
- meerk40t/ruida/driver.py +534 -472
- meerk40t/ruida/emulator.py +1494 -1491
- meerk40t/ruida/exceptions.py +4 -4
- meerk40t/ruida/gui/gui.py +71 -76
- meerk40t/ruida/gui/ruidaconfig.py +239 -72
- meerk40t/ruida/gui/ruidacontroller.py +187 -184
- meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
- meerk40t/ruida/loader.py +54 -52
- meerk40t/ruida/mock_connection.py +57 -109
- meerk40t/ruida/plugin.py +124 -87
- meerk40t/ruida/rdjob.py +2084 -945
- meerk40t/ruida/serial_connection.py +116 -0
- meerk40t/ruida/tcp_connection.py +146 -0
- meerk40t/ruida/udp_connection.py +73 -0
- meerk40t/svgelements.py +9671 -9669
- meerk40t/tools/driver_to_path.py +584 -579
- meerk40t/tools/geomstr.py +5583 -4680
- meerk40t/tools/jhfparser.py +357 -292
- meerk40t/tools/kerftest.py +904 -890
- meerk40t/tools/livinghinges.py +1168 -1033
- meerk40t/tools/pathtools.py +987 -949
- meerk40t/tools/pmatrix.py +234 -0
- meerk40t/tools/pointfinder.py +942 -942
- meerk40t/tools/polybool.py +941 -940
- meerk40t/tools/rasterplotter.py +1660 -547
- meerk40t/tools/shxparser.py +1047 -901
- meerk40t/tools/ttfparser.py +726 -446
- meerk40t/tools/zinglplotter.py +595 -593
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/LICENSE +21 -21
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/METADATA +150 -139
- meerk40t-0.9.7020.dist-info/RECORD +446 -0
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/WHEEL +1 -1
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/top_level.txt +0 -1
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/zip-safe +1 -1
- meerk40t/balormk/elementlightjob.py +0 -159
- meerk40t-0.9.3001.dist-info/RECORD +0 -437
- test/bootstrap.py +0 -63
- test/test_cli.py +0 -12
- test/test_core_cutcode.py +0 -418
- test/test_core_elements.py +0 -144
- test/test_core_plotplanner.py +0 -397
- test/test_core_viewports.py +0 -312
- test/test_drivers_grbl.py +0 -108
- test/test_drivers_lihuiyu.py +0 -443
- test/test_drivers_newly.py +0 -113
- test/test_element_degenerate_points.py +0 -43
- test/test_elements_classify.py +0 -97
- test/test_elements_penbox.py +0 -22
- test/test_file_svg.py +0 -176
- test/test_fill.py +0 -155
- test/test_geomstr.py +0 -1523
- test/test_geomstr_nodes.py +0 -18
- test/test_imagetools_actualize.py +0 -306
- test/test_imagetools_wizard.py +0 -258
- test/test_kernel.py +0 -200
- test/test_laser_speeds.py +0 -3303
- test/test_length.py +0 -57
- test/test_lifecycle.py +0 -66
- test/test_operations.py +0 -251
- test/test_operations_hatch.py +0 -57
- test/test_ruida.py +0 -19
- test/test_spooler.py +0 -22
- test/test_tools_rasterplotter.py +0 -29
- test/test_wobble.py +0 -133
- test/test_zingl.py +0 -124
- {test → meerk40t/cylinder}/__init__.py +0 -0
- /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/entry_points.txt +0 -0
meerk40t/core/svg_io.py
CHANGED
@@ -1,1549 +1,1836 @@
|
|
1
|
-
"""
|
2
|
-
This extension governs SVG loading and saving, registering both the load and the save values for SVG.
|
3
|
-
"""
|
4
|
-
|
5
|
-
import ast
|
6
|
-
import gzip
|
7
|
-
import math
|
8
|
-
import os
|
9
|
-
from base64 import b64encode
|
10
|
-
from io import BytesIO
|
11
|
-
from xml.etree.ElementTree import Element, ElementTree, ParseError, SubElement
|
12
|
-
|
13
|
-
from meerk40t.core.exceptions import BadFileError
|
14
|
-
from meerk40t.core.node.node import Fillrule, Linecap, Linejoin
|
15
|
-
|
16
|
-
from ..svgelements import (
|
17
|
-
SVG,
|
18
|
-
SVG_ATTR_CENTER_X,
|
19
|
-
SVG_ATTR_CENTER_Y,
|
20
|
-
SVG_ATTR_DATA,
|
21
|
-
SVG_ATTR_FILL,
|
22
|
-
SVG_ATTR_FILL_OPACITY,
|
23
|
-
SVG_ATTR_FONT_FAMILY,
|
24
|
-
SVG_ATTR_FONT_SIZE,
|
25
|
-
SVG_ATTR_FONT_STRETCH,
|
26
|
-
SVG_ATTR_FONT_STYLE,
|
27
|
-
SVG_ATTR_FONT_VARIANT,
|
28
|
-
SVG_ATTR_HEIGHT,
|
29
|
-
SVG_ATTR_ID,
|
30
|
-
SVG_ATTR_POINTS,
|
31
|
-
SVG_ATTR_RADIUS_X,
|
32
|
-
SVG_ATTR_RADIUS_Y,
|
33
|
-
SVG_ATTR_STROKE,
|
34
|
-
SVG_ATTR_STROKE_OPACITY,
|
35
|
-
SVG_ATTR_STROKE_WIDTH,
|
36
|
-
SVG_ATTR_TAG,
|
37
|
-
SVG_ATTR_TEXT_ALIGNMENT_BASELINE,
|
38
|
-
SVG_ATTR_TEXT_ANCHOR,
|
39
|
-
SVG_ATTR_TEXT_DOMINANT_BASELINE,
|
40
|
-
SVG_ATTR_TRANSFORM,
|
41
|
-
SVG_ATTR_VECTOR_EFFECT,
|
42
|
-
SVG_ATTR_VERSION,
|
43
|
-
SVG_ATTR_VIEWBOX,
|
44
|
-
SVG_ATTR_WIDTH,
|
45
|
-
SVG_ATTR_X,
|
46
|
-
SVG_ATTR_X1,
|
47
|
-
SVG_ATTR_X2,
|
48
|
-
SVG_ATTR_XMLNS,
|
49
|
-
SVG_ATTR_XMLNS_EV,
|
50
|
-
SVG_ATTR_XMLNS_LINK,
|
51
|
-
SVG_ATTR_Y,
|
52
|
-
SVG_ATTR_Y1,
|
53
|
-
SVG_ATTR_Y2,
|
54
|
-
SVG_NAME_TAG,
|
55
|
-
SVG_RULE_EVENODD,
|
56
|
-
SVG_RULE_NONZERO,
|
57
|
-
SVG_TAG_ELLIPSE,
|
58
|
-
SVG_TAG_GROUP,
|
59
|
-
SVG_TAG_IMAGE,
|
60
|
-
SVG_TAG_LINE,
|
61
|
-
SVG_TAG_PATH,
|
62
|
-
SVG_TAG_POLYLINE,
|
63
|
-
SVG_TAG_RECT,
|
64
|
-
SVG_TAG_TEXT,
|
65
|
-
SVG_VALUE_NON_SCALING_STROKE,
|
66
|
-
SVG_VALUE_NONE,
|
67
|
-
SVG_VALUE_VERSION,
|
68
|
-
SVG_VALUE_XLINK,
|
69
|
-
SVG_VALUE_XMLNS,
|
70
|
-
SVG_VALUE_XMLNS_EV,
|
71
|
-
Circle,
|
72
|
-
Color,
|
73
|
-
Ellipse,
|
74
|
-
Group,
|
75
|
-
Matrix,
|
76
|
-
Path,
|
77
|
-
Point,
|
78
|
-
Polygon,
|
79
|
-
Polyline,
|
80
|
-
Rect,
|
81
|
-
SimpleLine,
|
82
|
-
SVGImage,
|
83
|
-
SVGText,
|
84
|
-
Use,
|
85
|
-
)
|
86
|
-
from .units import DEFAULT_PPI, NATIVE_UNIT_PER_INCH, Length
|
87
|
-
|
88
|
-
SVG_ATTR_STROKE_JOIN = "stroke-linejoin"
|
89
|
-
SVG_ATTR_STROKE_CAP = "stroke-linecap"
|
90
|
-
SVG_ATTR_FILL_RULE = "fill-rule"
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
"
|
100
|
-
"
|
101
|
-
"
|
102
|
-
"
|
103
|
-
"
|
104
|
-
|
105
|
-
"
|
106
|
-
|
107
|
-
|
108
|
-
"
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
@
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
root
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
)
|
217
|
-
|
218
|
-
|
219
|
-
if
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
if
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
if
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
subelement
|
285
|
-
|
286
|
-
|
287
|
-
)
|
288
|
-
subelement.set(
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
subelement.set(
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
)
|
310
|
-
|
311
|
-
|
312
|
-
subelement
|
313
|
-
subelement.set(
|
314
|
-
t = c.matrix
|
315
|
-
if not t.is_identity():
|
316
|
-
subelement.set(
|
317
|
-
"transform",
|
318
|
-
f"matrix({t.a}, {t.b}, {t.c}, {t.d}, {t.e}, {t.f})",
|
319
|
-
)
|
320
|
-
elif c.type == "elem
|
321
|
-
subelement = SubElement(xml_tree,
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
"
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
subelement.set(
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
if
|
392
|
-
subelement.set(
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
if
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
if
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
"
|
439
|
-
)
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
###############
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
if
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
subelement.set(
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
"""
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
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
|
-
@param
|
871
|
-
@param
|
872
|
-
@
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
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
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
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
|
-
@param
|
1072
|
-
@
|
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
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
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
|
-
try:
|
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
|
-
|
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
|
-
element
|
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
|
-
|
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
|
-
|
1416
|
-
|
1417
|
-
|
1418
|
-
|
1419
|
-
|
1420
|
-
|
1421
|
-
|
1422
|
-
|
1423
|
-
|
1424
|
-
|
1425
|
-
|
1426
|
-
|
1427
|
-
|
1428
|
-
|
1429
|
-
|
1430
|
-
|
1431
|
-
|
1432
|
-
|
1433
|
-
|
1434
|
-
|
1435
|
-
|
1436
|
-
|
1437
|
-
|
1438
|
-
|
1439
|
-
|
1440
|
-
|
1441
|
-
|
1442
|
-
|
1443
|
-
|
1444
|
-
|
1445
|
-
|
1446
|
-
|
1447
|
-
|
1448
|
-
|
1449
|
-
|
1450
|
-
|
1451
|
-
|
1452
|
-
|
1453
|
-
|
1454
|
-
|
1455
|
-
|
1456
|
-
|
1457
|
-
|
1458
|
-
|
1459
|
-
|
1460
|
-
|
1461
|
-
|
1462
|
-
|
1463
|
-
|
1464
|
-
|
1465
|
-
|
1466
|
-
|
1467
|
-
|
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
|
-
|
1530
|
-
|
1531
|
-
|
1532
|
-
|
1533
|
-
|
1534
|
-
|
1535
|
-
|
1536
|
-
|
1537
|
-
|
1538
|
-
|
1539
|
-
|
1540
|
-
|
1541
|
-
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
1545
|
-
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1
|
+
"""
|
2
|
+
This extension governs SVG loading and saving, registering both the load and the save values for SVG.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import ast
|
6
|
+
import gzip
|
7
|
+
import math
|
8
|
+
import os
|
9
|
+
from base64 import b64encode
|
10
|
+
from io import BytesIO
|
11
|
+
from xml.etree.ElementTree import Element, ElementTree, ParseError, SubElement
|
12
|
+
|
13
|
+
from meerk40t.core.exceptions import BadFileError
|
14
|
+
from meerk40t.core.node.node import Fillrule, Linecap, Linejoin
|
15
|
+
|
16
|
+
from ..svgelements import (
|
17
|
+
SVG,
|
18
|
+
SVG_ATTR_CENTER_X,
|
19
|
+
SVG_ATTR_CENTER_Y,
|
20
|
+
SVG_ATTR_DATA,
|
21
|
+
SVG_ATTR_FILL,
|
22
|
+
SVG_ATTR_FILL_OPACITY,
|
23
|
+
SVG_ATTR_FONT_FAMILY,
|
24
|
+
SVG_ATTR_FONT_SIZE,
|
25
|
+
SVG_ATTR_FONT_STRETCH,
|
26
|
+
SVG_ATTR_FONT_STYLE,
|
27
|
+
SVG_ATTR_FONT_VARIANT,
|
28
|
+
SVG_ATTR_HEIGHT,
|
29
|
+
SVG_ATTR_ID,
|
30
|
+
SVG_ATTR_POINTS,
|
31
|
+
SVG_ATTR_RADIUS_X,
|
32
|
+
SVG_ATTR_RADIUS_Y,
|
33
|
+
SVG_ATTR_STROKE,
|
34
|
+
SVG_ATTR_STROKE_OPACITY,
|
35
|
+
SVG_ATTR_STROKE_WIDTH,
|
36
|
+
SVG_ATTR_TAG,
|
37
|
+
SVG_ATTR_TEXT_ALIGNMENT_BASELINE,
|
38
|
+
SVG_ATTR_TEXT_ANCHOR,
|
39
|
+
SVG_ATTR_TEXT_DOMINANT_BASELINE,
|
40
|
+
SVG_ATTR_TRANSFORM,
|
41
|
+
SVG_ATTR_VECTOR_EFFECT,
|
42
|
+
SVG_ATTR_VERSION,
|
43
|
+
SVG_ATTR_VIEWBOX,
|
44
|
+
SVG_ATTR_WIDTH,
|
45
|
+
SVG_ATTR_X,
|
46
|
+
SVG_ATTR_X1,
|
47
|
+
SVG_ATTR_X2,
|
48
|
+
SVG_ATTR_XMLNS,
|
49
|
+
SVG_ATTR_XMLNS_EV,
|
50
|
+
SVG_ATTR_XMLNS_LINK,
|
51
|
+
SVG_ATTR_Y,
|
52
|
+
SVG_ATTR_Y1,
|
53
|
+
SVG_ATTR_Y2,
|
54
|
+
SVG_NAME_TAG,
|
55
|
+
SVG_RULE_EVENODD,
|
56
|
+
SVG_RULE_NONZERO,
|
57
|
+
SVG_TAG_ELLIPSE,
|
58
|
+
SVG_TAG_GROUP,
|
59
|
+
SVG_TAG_IMAGE,
|
60
|
+
SVG_TAG_LINE,
|
61
|
+
SVG_TAG_PATH,
|
62
|
+
SVG_TAG_POLYLINE,
|
63
|
+
SVG_TAG_RECT,
|
64
|
+
SVG_TAG_TEXT,
|
65
|
+
SVG_VALUE_NON_SCALING_STROKE,
|
66
|
+
SVG_VALUE_NONE,
|
67
|
+
SVG_VALUE_VERSION,
|
68
|
+
SVG_VALUE_XLINK,
|
69
|
+
SVG_VALUE_XMLNS,
|
70
|
+
SVG_VALUE_XMLNS_EV,
|
71
|
+
Circle,
|
72
|
+
Color,
|
73
|
+
Ellipse,
|
74
|
+
Group,
|
75
|
+
Matrix,
|
76
|
+
Path,
|
77
|
+
Point,
|
78
|
+
Polygon,
|
79
|
+
Polyline,
|
80
|
+
Rect,
|
81
|
+
SimpleLine,
|
82
|
+
SVGImage,
|
83
|
+
SVGText,
|
84
|
+
Use,
|
85
|
+
)
|
86
|
+
from .units import DEFAULT_PPI, NATIVE_UNIT_PER_INCH, Length
|
87
|
+
|
88
|
+
SVG_ATTR_STROKE_JOIN = "stroke-linejoin"
|
89
|
+
SVG_ATTR_STROKE_CAP = "stroke-linecap"
|
90
|
+
SVG_ATTR_FILL_RULE = "fill-rule"
|
91
|
+
SVG_ATTR_STROKE_DASH = "stroke-dasharray"
|
92
|
+
|
93
|
+
|
94
|
+
def plugin(kernel, lifecycle=None):
|
95
|
+
if lifecycle == "register":
|
96
|
+
_ = kernel.translation
|
97
|
+
choices = [
|
98
|
+
{
|
99
|
+
"attr": "svg_viewport_bed",
|
100
|
+
"object": kernel.elements,
|
101
|
+
"default": True,
|
102
|
+
"type": bool,
|
103
|
+
"label": _("SVG Viewport is Bed"),
|
104
|
+
"tip": _(
|
105
|
+
"SVG files can be saved without real physical units.\n"
|
106
|
+
"This setting uses the SVG viewport dimensions to scale the rest of the elements in the file."
|
107
|
+
),
|
108
|
+
"page": "Input/Output",
|
109
|
+
"section": "Input",
|
110
|
+
},
|
111
|
+
{
|
112
|
+
"attr": "load_hidden_to_regmarks",
|
113
|
+
"object": kernel.elements,
|
114
|
+
"default": True,
|
115
|
+
"type": bool,
|
116
|
+
"label": _("Load hidden objects to regmarks"),
|
117
|
+
"tip": _(
|
118
|
+
"Ticked: When loading a file invisible elements will be loaded to the regmarks branch."
|
119
|
+
)
|
120
|
+
+ "\n"
|
121
|
+
+ _(
|
122
|
+
"Unticked: Invisible elements will be loaded as regular elements and will be hidden."
|
123
|
+
),
|
124
|
+
"page": "Input/Output",
|
125
|
+
"section": "Input",
|
126
|
+
},
|
127
|
+
|
128
|
+
]
|
129
|
+
kernel.register_choices("preferences", choices)
|
130
|
+
# The order is relevant as both loaders support SVG
|
131
|
+
# By definition the very first matching loader is used as a default
|
132
|
+
# so that needs to be the full loader
|
133
|
+
kernel.register("load/SVGLoader", SVGLoader)
|
134
|
+
kernel.register("load/SVGPlainLoader", SVGLoaderPlain)
|
135
|
+
kernel.register("save/SVGWriter", SVGWriter)
|
136
|
+
|
137
|
+
|
138
|
+
MEERK40T_NAMESPACE = "https://github.com/meerk40t/meerk40t/wiki/Namespace"
|
139
|
+
MEERK40T_XMLS_ID = "meerk40t"
|
140
|
+
|
141
|
+
|
142
|
+
def capstr(linecap):
|
143
|
+
"""
|
144
|
+
Given the mk enum values for linecap, returns the svg string.
|
145
|
+
@param linecap:
|
146
|
+
@return:
|
147
|
+
"""
|
148
|
+
if linecap == Linecap.CAP_BUTT:
|
149
|
+
return "butt"
|
150
|
+
elif linecap == Linecap.CAP_SQUARE:
|
151
|
+
return "square"
|
152
|
+
else:
|
153
|
+
return "round"
|
154
|
+
|
155
|
+
|
156
|
+
def joinstr(linejoin):
|
157
|
+
"""
|
158
|
+
Given the mk enum value for linejoin, returns the svg string.
|
159
|
+
|
160
|
+
@param linejoin:
|
161
|
+
@return:
|
162
|
+
"""
|
163
|
+
if linejoin == Linejoin.JOIN_ARCS:
|
164
|
+
return "arcs"
|
165
|
+
elif linejoin == Linejoin.JOIN_BEVEL:
|
166
|
+
return "bevel"
|
167
|
+
elif linejoin == Linejoin.JOIN_MITER_CLIP:
|
168
|
+
return "miter-clip"
|
169
|
+
elif linejoin == Linejoin.JOIN_ROUND:
|
170
|
+
return "round"
|
171
|
+
else:
|
172
|
+
return "miter"
|
173
|
+
|
174
|
+
|
175
|
+
def rulestr(fillrule):
|
176
|
+
"""
|
177
|
+
Given the mk enum value for fillrule, returns the svg string.
|
178
|
+
|
179
|
+
@param fillrule:
|
180
|
+
@return:
|
181
|
+
"""
|
182
|
+
return "evenodd" if fillrule == Fillrule.FILLRULE_EVENODD else "nonzero"
|
183
|
+
|
184
|
+
|
185
|
+
class SVGWriter:
|
186
|
+
@staticmethod
|
187
|
+
def save_types():
|
188
|
+
yield "Scalable Vector Graphics", "svg", "image/svg+xml", "default"
|
189
|
+
yield "SVG-Plain (no extensions)", "svg", "image/svg+xml", "plain"
|
190
|
+
yield "SVG-Compressed", "svgz", "image/svg+xml", "compressed"
|
191
|
+
|
192
|
+
@staticmethod
|
193
|
+
def save(context, f, version="default"):
|
194
|
+
# print (f"Version was set to '{version}'")
|
195
|
+
root = Element(SVG_NAME_TAG)
|
196
|
+
root.set(SVG_ATTR_VERSION, SVG_VALUE_VERSION)
|
197
|
+
root.set(SVG_ATTR_XMLNS, SVG_VALUE_XMLNS)
|
198
|
+
root.set(SVG_ATTR_XMLNS_LINK, SVG_VALUE_XLINK)
|
199
|
+
root.set(SVG_ATTR_XMLNS_EV, SVG_VALUE_XMLNS_EV)
|
200
|
+
if version != "plain":
|
201
|
+
root.set(
|
202
|
+
"xmlns:" + MEERK40T_XMLS_ID,
|
203
|
+
MEERK40T_NAMESPACE,
|
204
|
+
)
|
205
|
+
scene_width = Length(context.device.view.width)
|
206
|
+
scene_height = Length(context.device.view.height)
|
207
|
+
root.set(SVG_ATTR_WIDTH, scene_width.length_mm)
|
208
|
+
root.set(SVG_ATTR_HEIGHT, scene_height.length_mm)
|
209
|
+
viewbox = f"{0} {0} {int(float(scene_width))} {int(float(scene_height))}"
|
210
|
+
root.set(SVG_ATTR_VIEWBOX, viewbox)
|
211
|
+
elements = context.elements
|
212
|
+
elements.validate_ids()
|
213
|
+
# If we want to write labels then we need to establish the inkscape namespace
|
214
|
+
has_labels = False
|
215
|
+
for n in elements.elems_nodes():
|
216
|
+
if hasattr(n, "label") and n.label is not None and n.label != "":
|
217
|
+
has_labels = True
|
218
|
+
break
|
219
|
+
if n.type == "file":
|
220
|
+
has_labels = True
|
221
|
+
break
|
222
|
+
if not has_labels:
|
223
|
+
for n in elements.regmarks_nodes():
|
224
|
+
if hasattr(n, "label") and n.label is not None and n.label != "":
|
225
|
+
has_labels = True
|
226
|
+
break
|
227
|
+
if has_labels:
|
228
|
+
root.set(
|
229
|
+
"xmlns:inkscape",
|
230
|
+
"http://www.inkscape.org/namespaces/inkscape",
|
231
|
+
)
|
232
|
+
if version != "plain":
|
233
|
+
# If there is a note set then we save the note with the project.
|
234
|
+
if elements.note is not None:
|
235
|
+
subelement = SubElement(root, "note")
|
236
|
+
subelement.set(SVG_TAG_TEXT, str(elements.note))
|
237
|
+
if elements.last_file_autoexec is not None:
|
238
|
+
subelement = SubElement(root, "autoexec")
|
239
|
+
subelement.set("autoexec", str(elements.last_file_autoexec))
|
240
|
+
subelement.set("autoexec-active", str(elements.last_file_autoexec_active))
|
241
|
+
|
242
|
+
SVGWriter._write_tree(root, elements._tree, version)
|
243
|
+
|
244
|
+
SVGWriter._pretty_print(root)
|
245
|
+
tree = ElementTree(root)
|
246
|
+
if f.lower().endswith("svgz"):
|
247
|
+
f = gzip.open(f, "wb")
|
248
|
+
tree.write(f)
|
249
|
+
|
250
|
+
@staticmethod
|
251
|
+
def _write_tree(xml_tree, node_tree, version):
|
252
|
+
# print (f"Write_tree with {version}")
|
253
|
+
for node in node_tree.children:
|
254
|
+
if version != "plain" and node.type == "branch ops":
|
255
|
+
SVGWriter._write_operations(xml_tree, node, version)
|
256
|
+
if node.type == "branch elems":
|
257
|
+
SVGWriter._write_elements(xml_tree, node, version)
|
258
|
+
elif node.type == "branch reg":
|
259
|
+
SVGWriter._write_regmarks(xml_tree, node, version)
|
260
|
+
|
261
|
+
@staticmethod
|
262
|
+
def _write_elements(xml_tree, elem_tree, version):
|
263
|
+
"""
|
264
|
+
Write the elements branch part of the tree to disk.
|
265
|
+
|
266
|
+
@param xml_tree:
|
267
|
+
@param elem_tree:
|
268
|
+
@return:
|
269
|
+
"""
|
270
|
+
for c in elem_tree.children:
|
271
|
+
SVGWriter._write_element(xml_tree, c, version)
|
272
|
+
|
273
|
+
@staticmethod
|
274
|
+
def _write_element(xml_tree, c, version):
|
275
|
+
def single_file_node():
|
276
|
+
# do we have more than one element on the top level hierarchy?
|
277
|
+
# If no then return True
|
278
|
+
flag = True
|
279
|
+
if len(c.children) > 1:
|
280
|
+
flag = False
|
281
|
+
return flag
|
282
|
+
|
283
|
+
if c.type == "elem ellipse":
|
284
|
+
subelement = SubElement(xml_tree, SVG_TAG_ELLIPSE)
|
285
|
+
subelement.set(SVG_ATTR_CENTER_X, str(c.cx))
|
286
|
+
subelement.set(SVG_ATTR_CENTER_Y, str(c.cy))
|
287
|
+
subelement.set(SVG_ATTR_RADIUS_X, str(c.rx))
|
288
|
+
subelement.set(SVG_ATTR_RADIUS_Y, str(c.ry))
|
289
|
+
t = Matrix(c.matrix)
|
290
|
+
if not t.is_identity():
|
291
|
+
subelement.set(
|
292
|
+
"transform",
|
293
|
+
f"matrix({t.a}, {t.b}, {t.c}, {t.d}, {t.e}, {t.f})",
|
294
|
+
)
|
295
|
+
elif c.type in ("elem image", "image raster"):
|
296
|
+
subelement = SubElement(xml_tree, SVG_TAG_IMAGE)
|
297
|
+
stream = BytesIO()
|
298
|
+
try:
|
299
|
+
c.image.save(stream, format="PNG", dpi=(c.dpi, c.dpi))
|
300
|
+
except OSError:
|
301
|
+
# Edge condition if the original image was CMYK and never touched it can't encode to PNG
|
302
|
+
c.image.convert("RGBA").save(stream, format="PNG", dpi=(c.dpi, c.dpi))
|
303
|
+
subelement.set(
|
304
|
+
"xlink:href",
|
305
|
+
f"data:image/png;base64,{b64encode(stream.getvalue()).decode('utf8')}",
|
306
|
+
)
|
307
|
+
ref = c.keyhole_reference
|
308
|
+
if ref is not None:
|
309
|
+
subelement.set("keyhole_reference", ref)
|
310
|
+
subelement.set(SVG_ATTR_X, "0")
|
311
|
+
subelement.set(SVG_ATTR_Y, "0")
|
312
|
+
subelement.set(SVG_ATTR_WIDTH, str(c.image.width))
|
313
|
+
subelement.set(SVG_ATTR_HEIGHT, str(c.image.height))
|
314
|
+
t = c.matrix
|
315
|
+
if not t.is_identity():
|
316
|
+
subelement.set(
|
317
|
+
"transform",
|
318
|
+
f"matrix({t.a}, {t.b}, {t.c}, {t.d}, {t.e}, {t.f})",
|
319
|
+
)
|
320
|
+
elif c.type == "elem line":
|
321
|
+
subelement = SubElement(xml_tree, SVG_TAG_LINE)
|
322
|
+
subelement.set(SVG_ATTR_X1, str(c.x1))
|
323
|
+
subelement.set(SVG_ATTR_Y1, str(c.y1))
|
324
|
+
subelement.set(SVG_ATTR_X2, str(c.x2))
|
325
|
+
subelement.set(SVG_ATTR_Y2, str(c.y2))
|
326
|
+
t = c.matrix
|
327
|
+
if not t.is_identity():
|
328
|
+
subelement.set(
|
329
|
+
"transform",
|
330
|
+
f"matrix({t.a}, {t.b}, {t.c}, {t.d}, {t.e}, {t.f})",
|
331
|
+
)
|
332
|
+
elif c.type == "elem path":
|
333
|
+
element = c.geometry.as_path()
|
334
|
+
subelement = SubElement(xml_tree, SVG_TAG_PATH)
|
335
|
+
subelement.set(SVG_ATTR_DATA, element.d(transformed=False))
|
336
|
+
t = c.matrix
|
337
|
+
if not t.is_identity():
|
338
|
+
subelement.set(
|
339
|
+
"transform",
|
340
|
+
f"matrix({t.a}, {t.b}, {t.c}, {t.d}, {t.e}, {t.f})",
|
341
|
+
)
|
342
|
+
elif c.type == "elem point":
|
343
|
+
subelement = SubElement(xml_tree, "element")
|
344
|
+
t = c.matrix
|
345
|
+
if not t.is_identity():
|
346
|
+
subelement.set(
|
347
|
+
"transform",
|
348
|
+
f"matrix({t.a}, {t.b}, {t.c}, {t.d}, {t.e}, {t.f})",
|
349
|
+
)
|
350
|
+
subelement.set("x", str(c.x))
|
351
|
+
subelement.set("y", str(c.y))
|
352
|
+
elif c.type == "elem polyline":
|
353
|
+
subelement = SubElement(xml_tree, SVG_TAG_POLYLINE)
|
354
|
+
points = list(c.geometry.as_points())
|
355
|
+
subelement.set(
|
356
|
+
SVG_ATTR_POINTS,
|
357
|
+
" ".join([f"{e.real} {e.imag}" for e in points]),
|
358
|
+
)
|
359
|
+
t = c.matrix
|
360
|
+
if not t.is_identity():
|
361
|
+
subelement.set(
|
362
|
+
"transform",
|
363
|
+
f"matrix({t.a}, {t.b}, {t.c}, {t.d}, {t.e}, {t.f})",
|
364
|
+
)
|
365
|
+
elif c.type == "elem rect":
|
366
|
+
subelement = SubElement(xml_tree, SVG_TAG_RECT)
|
367
|
+
subelement.set(SVG_ATTR_X, str(c.x))
|
368
|
+
subelement.set(SVG_ATTR_Y, str(c.y))
|
369
|
+
subelement.set(SVG_ATTR_RADIUS_X, str(c.rx))
|
370
|
+
subelement.set(SVG_ATTR_RADIUS_Y, str(c.ry))
|
371
|
+
subelement.set(SVG_ATTR_WIDTH, str(c.width))
|
372
|
+
subelement.set(SVG_ATTR_HEIGHT, str(c.height))
|
373
|
+
t = c.matrix
|
374
|
+
if not t.is_identity():
|
375
|
+
subelement.set(
|
376
|
+
"transform",
|
377
|
+
f"matrix({t.a}, {t.b}, {t.c}, {t.d}, {t.e}, {t.f})",
|
378
|
+
)
|
379
|
+
elif c.type == "elem text":
|
380
|
+
subelement = SubElement(xml_tree, SVG_TAG_TEXT)
|
381
|
+
subelement.text = c.text
|
382
|
+
t = c.matrix
|
383
|
+
if not t.is_identity():
|
384
|
+
subelement.set(
|
385
|
+
SVG_ATTR_TRANSFORM,
|
386
|
+
f"matrix({t.a}, {t.b}, {t.c}, {t.d}, {t.e}, {t.f})",
|
387
|
+
)
|
388
|
+
# Font features are covered by the `font` value shorthand
|
389
|
+
if c.font_family:
|
390
|
+
subelement.set(SVG_ATTR_FONT_FAMILY, str(c.font_family))
|
391
|
+
if c.font_style:
|
392
|
+
subelement.set(SVG_ATTR_FONT_STYLE, str(c.font_style))
|
393
|
+
if c.font_variant:
|
394
|
+
subelement.set(SVG_ATTR_FONT_VARIANT, str(c.font_variant))
|
395
|
+
if c.font_stretch:
|
396
|
+
subelement.set(SVG_ATTR_FONT_STRETCH, str(c.font_stretch))
|
397
|
+
if c.font_size:
|
398
|
+
subelement.set(SVG_ATTR_FONT_SIZE, str(c.font_size))
|
399
|
+
if c.line_height:
|
400
|
+
subelement.set("line_height", str(c.line_height))
|
401
|
+
if c.anchor:
|
402
|
+
subelement.set(SVG_ATTR_TEXT_ANCHOR, str(c.anchor))
|
403
|
+
if c.baseline:
|
404
|
+
subelement.set(SVG_ATTR_TEXT_DOMINANT_BASELINE, str(c.baseline))
|
405
|
+
decor = ""
|
406
|
+
if c.underline:
|
407
|
+
decor += " underline"
|
408
|
+
if c.overline:
|
409
|
+
decor += " overline"
|
410
|
+
if c.strikethrough:
|
411
|
+
decor += " line-through"
|
412
|
+
decor = decor.strip()
|
413
|
+
if decor:
|
414
|
+
subelement.set("text-decoration", decor)
|
415
|
+
elif c.type == "group":
|
416
|
+
# This is a structural group node of elements. Recurse call to write values.
|
417
|
+
group_element = SubElement(xml_tree, SVG_TAG_GROUP)
|
418
|
+
if hasattr(c, "label") and c.label is not None and c.label != "":
|
419
|
+
group_element.set("inkscape:label", str(c.label))
|
420
|
+
if hasattr(c, "label_display") and c.label_display is not None:
|
421
|
+
group_element.set("label_display", str(c.label_display))
|
422
|
+
SVGWriter._write_elements(group_element, c, version)
|
423
|
+
return
|
424
|
+
elif c.type.startswith("effect"):
|
425
|
+
# This is a structural group node of elements. Recurse call to write values.
|
426
|
+
group_element = SubElement(xml_tree, SVG_TAG_GROUP)
|
427
|
+
SVGWriter._write_custom(group_element, c)
|
428
|
+
SVGWriter._write_elements(group_element, c, version)
|
429
|
+
return
|
430
|
+
elif c.type == "file":
|
431
|
+
# This is a structural group node of elements. Recurse call to write values.
|
432
|
+
# is this the only file node? If yes then no need to generate an additional group
|
433
|
+
if single_file_node():
|
434
|
+
SVGWriter._write_elements(xml_tree, c, version)
|
435
|
+
else:
|
436
|
+
group_element = SubElement(xml_tree, SVG_TAG_GROUP)
|
437
|
+
if hasattr(c, "name") and c.name is not None and c.name != "":
|
438
|
+
group_element.set("inkscape:label", str(c.name))
|
439
|
+
SVGWriter._write_elements(group_element, c, version)
|
440
|
+
return
|
441
|
+
else:
|
442
|
+
if version == "plain":
|
443
|
+
# Plain does not save custom.
|
444
|
+
return
|
445
|
+
# This is a non-standard element. Save custom.
|
446
|
+
subelement = SubElement(xml_tree, "element")
|
447
|
+
SVGWriter._write_custom(subelement, c)
|
448
|
+
return
|
449
|
+
|
450
|
+
###############
|
451
|
+
# GENERIC SAVING STANDARD ELEMENT
|
452
|
+
###############
|
453
|
+
for key, value in c.__dict__.items():
|
454
|
+
if (
|
455
|
+
not key.startswith("_")
|
456
|
+
and key
|
457
|
+
not in (
|
458
|
+
"settings",
|
459
|
+
"attributes",
|
460
|
+
"linecap",
|
461
|
+
"linejoin",
|
462
|
+
"fillrule",
|
463
|
+
"stroke_width",
|
464
|
+
"stroke_dash",
|
465
|
+
)
|
466
|
+
and value is not None
|
467
|
+
and isinstance(value, (str, int, float, complex, list, tuple, dict))
|
468
|
+
):
|
469
|
+
subelement.set(key, str(value))
|
470
|
+
|
471
|
+
# ###########################
|
472
|
+
# # SAVE SVG STROKE-SCALING
|
473
|
+
# ###########################
|
474
|
+
# if hasattr(c, "stroke_scaled"):
|
475
|
+
# if not c.stroke_scaled:
|
476
|
+
# subelement.set(SVG_ATTR_VECTOR_EFFECT, SVG_VALUE_NON_SCALING_STROKE)
|
477
|
+
|
478
|
+
###############
|
479
|
+
# SAVE CAP/JOIN/FILL-RULE
|
480
|
+
###############
|
481
|
+
if hasattr(c, "linecap"):
|
482
|
+
subelement.set(SVG_ATTR_STROKE_CAP, capstr(c.linecap))
|
483
|
+
if hasattr(c, "linejoin"):
|
484
|
+
subelement.set(SVG_ATTR_STROKE_JOIN, joinstr(c.linejoin))
|
485
|
+
if hasattr(c, "fillrule"):
|
486
|
+
subelement.set(SVG_ATTR_FILL_RULE, rulestr(c.fillrule))
|
487
|
+
if hasattr(c, "stroke_dash") and c.stroke_dash:
|
488
|
+
subelement.set(SVG_ATTR_STROKE_DASH, c.stroke_dash)
|
489
|
+
|
490
|
+
###############
|
491
|
+
# SAVE LABEL
|
492
|
+
###############
|
493
|
+
if hasattr(c, "label") and c.label is not None and c.label != "":
|
494
|
+
subelement.set("inkscape:label", c.label)
|
495
|
+
|
496
|
+
###############
|
497
|
+
# SAVE STROKE
|
498
|
+
###############
|
499
|
+
stroke = c.stroke if hasattr(c, "stroke") else None
|
500
|
+
if stroke is not None:
|
501
|
+
stroke_opacity = stroke.opacity
|
502
|
+
stroke = (
|
503
|
+
str(abs(stroke))
|
504
|
+
if stroke is not None and stroke.value is not None
|
505
|
+
else SVG_VALUE_NONE
|
506
|
+
)
|
507
|
+
subelement.set(SVG_ATTR_STROKE, stroke)
|
508
|
+
if stroke_opacity != 1.0 and stroke_opacity is not None:
|
509
|
+
subelement.set(SVG_ATTR_STROKE_OPACITY, str(stroke_opacity))
|
510
|
+
try:
|
511
|
+
factor = 1.0
|
512
|
+
try:
|
513
|
+
if c.stroke_scaled:
|
514
|
+
factor = c.stroke_factor
|
515
|
+
except AttributeError:
|
516
|
+
pass
|
517
|
+
if c.matrix.determinant == 0:
|
518
|
+
c_m_d = 1
|
519
|
+
else:
|
520
|
+
c_m_d = math.sqrt(abs(c.matrix.determinant))
|
521
|
+
if c.stroke_width is not None:
|
522
|
+
stroke_width = str(factor * c.stroke_width / c_m_d)
|
523
|
+
subelement.set(SVG_ATTR_STROKE_WIDTH, stroke_width)
|
524
|
+
except AttributeError:
|
525
|
+
pass
|
526
|
+
|
527
|
+
###############
|
528
|
+
# SAVE FILL
|
529
|
+
###############
|
530
|
+
fill = c.fill if hasattr(c, "fill") else None
|
531
|
+
if fill is not None:
|
532
|
+
fill_opacity = fill.opacity
|
533
|
+
fill = (
|
534
|
+
str(abs(fill))
|
535
|
+
if fill is not None and fill.value is not None
|
536
|
+
else SVG_VALUE_NONE
|
537
|
+
)
|
538
|
+
subelement.set(SVG_ATTR_FILL, fill)
|
539
|
+
if fill_opacity != 1.0 and fill_opacity is not None:
|
540
|
+
subelement.set(SVG_ATTR_FILL_OPACITY, str(fill_opacity))
|
541
|
+
else:
|
542
|
+
subelement.set(SVG_ATTR_FILL, SVG_VALUE_NONE)
|
543
|
+
|
544
|
+
if hasattr(c, "hidden") and c.hidden:
|
545
|
+
subelement.set("visibility", "hidden")
|
546
|
+
|
547
|
+
subelement.set(SVG_ATTR_ID, str(c.id))
|
548
|
+
if hasattr(c, "bounds"):
|
549
|
+
bb = c.bounds
|
550
|
+
bbstr = f"{bb[0]}, {bb[1]}, {bb[2]}, {bb[3]}"
|
551
|
+
subelement.set("bounds", bbstr)
|
552
|
+
if hasattr(c, "paint_bounds"):
|
553
|
+
bb = c.paint_bounds
|
554
|
+
bbstr = f"{bb[0]}, {bb[1]}, {bb[2]}, {bb[3]}"
|
555
|
+
subelement.set("paint_bounds", bbstr)
|
556
|
+
|
557
|
+
@staticmethod
|
558
|
+
def _write_operations(xml_tree, op_tree, version):
|
559
|
+
"""
|
560
|
+
Write the operations branch part of the tree to disk.
|
561
|
+
|
562
|
+
@param xml_tree:
|
563
|
+
@param op_tree:
|
564
|
+
@return:
|
565
|
+
"""
|
566
|
+
for c in op_tree.children:
|
567
|
+
SVGWriter._write_operation(xml_tree, c, version)
|
568
|
+
|
569
|
+
@staticmethod
|
570
|
+
def _write_regmarks(xml_tree, reg_tree, version):
|
571
|
+
if len(reg_tree.children):
|
572
|
+
regmark = SubElement(xml_tree, SVG_TAG_GROUP)
|
573
|
+
regmark.set("id", "regmarks")
|
574
|
+
regmark.set("visibility", "hidden")
|
575
|
+
SVGWriter._write_elements(regmark, reg_tree, version)
|
576
|
+
|
577
|
+
@staticmethod
|
578
|
+
def _write_operation(xml_tree, node, version):
|
579
|
+
"""
|
580
|
+
Write an individual operation. This is any node directly under `branch ops`
|
581
|
+
|
582
|
+
@param xml_tree:
|
583
|
+
@param node:
|
584
|
+
@return:
|
585
|
+
"""
|
586
|
+
# All operations are groups.
|
587
|
+
subelement = SubElement(xml_tree, SVG_TAG_GROUP)
|
588
|
+
subelement.set("type", str(node.type))
|
589
|
+
|
590
|
+
if node.label is not None:
|
591
|
+
subelement.set("label", str(node.label))
|
592
|
+
|
593
|
+
# We might end up with items in settings that have an unwanted equivalent in the node.dict
|
594
|
+
# as the settings instance is read and initiated on svg load...
|
595
|
+
for key, value in node.__dict__.items():
|
596
|
+
if not key or key.startswith("_"):
|
597
|
+
continue
|
598
|
+
if key in (
|
599
|
+
"references",
|
600
|
+
"tag",
|
601
|
+
"type",
|
602
|
+
"draw",
|
603
|
+
"stroke_width",
|
604
|
+
"matrix",
|
605
|
+
"settings",
|
606
|
+
):
|
607
|
+
continue
|
608
|
+
if hasattr(node, "settings"):
|
609
|
+
if key in node.settings:
|
610
|
+
settings_value = node.settings[key]
|
611
|
+
if settings_value != value:
|
612
|
+
# print (f"Needed to fix {key}: node-value: {value}, settings-value: {settings_value}")
|
613
|
+
node.settings[key] = value
|
614
|
+
|
615
|
+
saved_attributes = []
|
616
|
+
if hasattr(node, "settings"):
|
617
|
+
try:
|
618
|
+
for key, value in node.settings.items():
|
619
|
+
saved_attributes.append(key)
|
620
|
+
if not key:
|
621
|
+
# If key is None, do not save.
|
622
|
+
continue
|
623
|
+
if key.startswith("_"):
|
624
|
+
continue
|
625
|
+
if value is None:
|
626
|
+
continue
|
627
|
+
if key in ("references", "tag", "type"):
|
628
|
+
# References key from previous loaded version (filter out, rebuild)
|
629
|
+
continue
|
630
|
+
subelement.set(key, str(value))
|
631
|
+
except AttributeError:
|
632
|
+
pass
|
633
|
+
# Node does not have settings, write object dict
|
634
|
+
for key, value in node.__dict__.items():
|
635
|
+
if not key or key.startswith("_") or key in saved_attributes or value is None:
|
636
|
+
continue
|
637
|
+
if key in (
|
638
|
+
"references",
|
639
|
+
"tag",
|
640
|
+
"type",
|
641
|
+
"draw",
|
642
|
+
"stroke_width",
|
643
|
+
"matrix",
|
644
|
+
"settings",
|
645
|
+
):
|
646
|
+
# References key from previous loaded version (filter out, rebuild)
|
647
|
+
continue
|
648
|
+
subelement.set(key, str(value))
|
649
|
+
|
650
|
+
# Store current node reference values.
|
651
|
+
SVGWriter._write_references(subelement, node)
|
652
|
+
subelement.set(SVG_ATTR_ID, str(node.id))
|
653
|
+
|
654
|
+
for c in node.children:
|
655
|
+
# Recurse all non-ref nodes
|
656
|
+
if c.type == "reference":
|
657
|
+
continue
|
658
|
+
SVGWriter._write_operation(subelement, c, version)
|
659
|
+
|
660
|
+
@staticmethod
|
661
|
+
def _write_references(subelement, node):
|
662
|
+
contains = list()
|
663
|
+
for c in node.children:
|
664
|
+
if c.type == "reference":
|
665
|
+
c = c.node # Contain direct reference not reference node reference.
|
666
|
+
contains.append(c.id)
|
667
|
+
if contains:
|
668
|
+
subelement.set("references", " ".join(contains))
|
669
|
+
|
670
|
+
@staticmethod
|
671
|
+
def _write_custom(subelement, node):
|
672
|
+
subelement.set("type", node.type)
|
673
|
+
for key, value in node.__dict__.items():
|
674
|
+
if not key:
|
675
|
+
# If key is None, do not save.
|
676
|
+
continue
|
677
|
+
if key.startswith("_"):
|
678
|
+
continue
|
679
|
+
if value is None:
|
680
|
+
continue
|
681
|
+
if key in ("references", "tag", "type", "draw", "stroke_width", "matrix"):
|
682
|
+
# References key from previous loaded version (filter out, rebuild)
|
683
|
+
continue
|
684
|
+
subelement.set(key, str(value))
|
685
|
+
SVGWriter._write_references(subelement, node)
|
686
|
+
subelement.set(SVG_ATTR_ID, str(node.id))
|
687
|
+
|
688
|
+
@staticmethod
|
689
|
+
def _pretty_print(current, parent=None, index=-1, depth=0):
|
690
|
+
for i, node in enumerate(current):
|
691
|
+
SVGWriter._pretty_print(node, current, i, depth + 1)
|
692
|
+
if parent is not None:
|
693
|
+
if index == 0:
|
694
|
+
parent.text = "\n" + ("\t" * depth)
|
695
|
+
else:
|
696
|
+
parent[index - 1].tail = "\n" + ("\t" * depth)
|
697
|
+
if index == len(parent) - 1:
|
698
|
+
current.tail = "\n" + ("\t" * (depth - 1))
|
699
|
+
|
700
|
+
|
701
|
+
class SVGProcessor:
|
702
|
+
"""
|
703
|
+
SVGProcessor is the parser for svg objects. We employ svgelements to do the actual parsing of the file and convert
|
704
|
+
the parsed objects into mk nodes, operations, elements, and regmarks.
|
705
|
+
|
706
|
+
Special care is taken to load MK specific objects like `note` and `operations`
|
707
|
+
"""
|
708
|
+
|
709
|
+
def __init__(self, elements, load_operations, load_hidden_to_regmarks = True, reuse_operations=True):
|
710
|
+
self.elements = elements
|
711
|
+
|
712
|
+
self.operation_list = list()
|
713
|
+
self.element_list = list()
|
714
|
+
self.regmark_list = list()
|
715
|
+
self.load_hidden_to_regmarks = load_hidden_to_regmarks
|
716
|
+
|
717
|
+
self.reverse = False
|
718
|
+
self.requires_classification = True
|
719
|
+
self.operations_generated = False
|
720
|
+
self.pathname = None
|
721
|
+
self.load_operations = load_operations
|
722
|
+
self.reuse_operations = reuse_operations
|
723
|
+
self.mk_params = list(
|
724
|
+
self.elements.kernel.lookup_all("registered_mk_svg_parameters")
|
725
|
+
)
|
726
|
+
# Append barcode from external plugin
|
727
|
+
self.mk_params.append("mkbcparam")
|
728
|
+
|
729
|
+
# Setting this is bringing as much benefit as anticipated
|
730
|
+
# Both the time to load the file (unexpectedly) and the time
|
731
|
+
# for the first emphasis when all the nonpopulated bounding
|
732
|
+
# boxes will be calculated are benefiting from this precalculation:
|
733
|
+
# (All values as average over three consecutive loads)
|
734
|
+
# | Load | First Select
|
735
|
+
# File | Old | Precalc | Speedup | Old | Precalc | Speedup
|
736
|
+
# Star Wars Calendar | 10,3 | 4,8 | 115% | 3,4 | 1,0 | 243%
|
737
|
+
# Element Classific | 1,7 | 1,1 | 59% | 0,6 | 0,4 | 54%
|
738
|
+
# Egyptian Bark | 72,1 | 43,9 | 64% | 34,6 | 20,1 | 72%
|
739
|
+
self.precalc_bbox = True
|
740
|
+
|
741
|
+
def process(self, svg, pathname):
|
742
|
+
"""
|
743
|
+
Process sends the data to parse and deals with creating the file_node, setting the operations, classifying
|
744
|
+
either directly from the data within the file or automatically.
|
745
|
+
|
746
|
+
@param svg:
|
747
|
+
@param pathname:
|
748
|
+
@return:
|
749
|
+
"""
|
750
|
+
retain_op_list = [
|
751
|
+
child
|
752
|
+
for child in list(self.elements.ops())
|
753
|
+
if child._children is not None and len(child._children) > 0
|
754
|
+
]
|
755
|
+
self.pathname = pathname
|
756
|
+
|
757
|
+
context_node = self.elements.elem_branch
|
758
|
+
file_node = context_node.add(type="file", filepath=pathname)
|
759
|
+
file_node.focus()
|
760
|
+
|
761
|
+
self.parse(svg, file_node, self.element_list, branch="elements")
|
762
|
+
|
763
|
+
if self.load_operations and self.operations_generated:
|
764
|
+
# print ("Will replace all operations...")
|
765
|
+
self.requires_classification = False
|
766
|
+
for child in list(self.elements.op_branch.children):
|
767
|
+
if child in retain_op_list:
|
768
|
+
continue
|
769
|
+
if not hasattr(child, "_ref_load"):
|
770
|
+
child.remove_all_children(fast=True, destroy=True)
|
771
|
+
child.remove_node(fast=True, destroy=True)
|
772
|
+
# Hint for translate check: _("File loaded")
|
773
|
+
self.elements.undo.mark("File loaded")
|
774
|
+
for op in self.elements.op_branch.flat():
|
775
|
+
try:
|
776
|
+
refs = op._ref_load
|
777
|
+
del op._ref_load
|
778
|
+
except AttributeError:
|
779
|
+
continue
|
780
|
+
if refs is None:
|
781
|
+
continue
|
782
|
+
|
783
|
+
for ref in refs.split(" "):
|
784
|
+
for e in self.element_list:
|
785
|
+
if e.id == ref:
|
786
|
+
op.add_reference(e)
|
787
|
+
|
788
|
+
if self.requires_classification and self.elements.classify_new:
|
789
|
+
self.elements.classify(self.element_list)
|
790
|
+
|
791
|
+
def check_for_bound_information(self, node, element):
|
792
|
+
# Do we have existing boundary information?
|
793
|
+
if "bounds" not in element.values:
|
794
|
+
return False
|
795
|
+
bbstr = element.values["bounds"]
|
796
|
+
if not bbstr:
|
797
|
+
return False
|
798
|
+
bb_info = bbstr.split(",")
|
799
|
+
if len(bb_info) == 4:
|
800
|
+
bbox = [0, 0, 0, 0]
|
801
|
+
try:
|
802
|
+
for idx in range(4):
|
803
|
+
val = float(bb_info[idx])
|
804
|
+
bbox[idx] = val
|
805
|
+
except ValueError:
|
806
|
+
return False
|
807
|
+
node._bounds = list(bbox)
|
808
|
+
node._bounds_dirty = False
|
809
|
+
if "paint_bounds" in element.values:
|
810
|
+
try:
|
811
|
+
bbstr = element.values["paint_bounds"]
|
812
|
+
bb_info = bbstr.split(",")
|
813
|
+
if len(bb_info) == 4:
|
814
|
+
for idx in range(4):
|
815
|
+
val = float(bb_info[idx])
|
816
|
+
bbox[idx] = val
|
817
|
+
except Exception:
|
818
|
+
# Whatever it was, we don't continue...
|
819
|
+
pass
|
820
|
+
node._paint_bounds = list(bbox)
|
821
|
+
node._paint_bounds_dirty = False
|
822
|
+
return True
|
823
|
+
|
824
|
+
def check_for_mk_path_attributes(self, node, element):
|
825
|
+
"""
|
826
|
+
Checks for some mk special parameters starting with mk. Especially mkparam, and uses this property to fill in
|
827
|
+
the functional_parameter attribute for the node.
|
828
|
+
|
829
|
+
@param node:
|
830
|
+
@param element:
|
831
|
+
@return:
|
832
|
+
"""
|
833
|
+
for prop in element.values:
|
834
|
+
lc = element.values.get(prop)
|
835
|
+
if prop.startswith("mk"):
|
836
|
+
# print (f"Property: {prop} = [{type(lc).__name__}] {lc}")
|
837
|
+
if lc is not None:
|
838
|
+
setattr(node, prop, lc)
|
839
|
+
# This needs to be done as some node types are not based on Parameters
|
840
|
+
# and hence would not convert the stringified tuple
|
841
|
+
if prop == "mkparam" and hasattr(node, "functional_parameter"):
|
842
|
+
try:
|
843
|
+
value = ast.literal_eval(lc)
|
844
|
+
node.functional_parameter = value
|
845
|
+
except (ValueError, SyntaxError):
|
846
|
+
pass
|
847
|
+
elif prop in self.mk_params:
|
848
|
+
try:
|
849
|
+
value = ast.literal_eval(lc)
|
850
|
+
setattr(node, prop, value)
|
851
|
+
except (ValueError, SyntaxError):
|
852
|
+
pass
|
853
|
+
|
854
|
+
def check_for_label_display(self, node, element):
|
855
|
+
"""
|
856
|
+
Called for all nodes to check whether the label_display needs to be set
|
857
|
+
@param node:
|
858
|
+
@param element:
|
859
|
+
@return:
|
860
|
+
"""
|
861
|
+
lc = element.values.get("label_display")
|
862
|
+
if lc is not None and hasattr(node, "label_display"):
|
863
|
+
d_val = bool(ast.literal_eval(lc))
|
864
|
+
node.label_display = d_val
|
865
|
+
|
866
|
+
def check_for_fill_attributes(self, node, element):
|
867
|
+
"""
|
868
|
+
Called for paths and poly lines. This checks for an attribute of `fill-rule` in the SVG and sets the MK equal.
|
869
|
+
|
870
|
+
@param node:
|
871
|
+
@param element:
|
872
|
+
@return:
|
873
|
+
"""
|
874
|
+
lc = element.values.get(SVG_ATTR_FILL_RULE)
|
875
|
+
# SVG default is nonzero
|
876
|
+
nlc = Fillrule.FILLRULE_NONZERO
|
877
|
+
if lc is not None:
|
878
|
+
lc = lc.lower()
|
879
|
+
if lc == SVG_RULE_EVENODD:
|
880
|
+
nlc = Fillrule.FILLRULE_EVENODD
|
881
|
+
elif lc == SVG_RULE_NONZERO:
|
882
|
+
nlc = Fillrule.FILLRULE_NONZERO
|
883
|
+
node.fillrule = nlc
|
884
|
+
|
885
|
+
def check_for_line_attributes(self, node, element):
|
886
|
+
"""
|
887
|
+
Called for many element types. This checks for the stroke-cap and line-join attributes in the svgelements
|
888
|
+
primitive and sets the node with the mk equal
|
889
|
+
|
890
|
+
@param node:
|
891
|
+
@param element:
|
892
|
+
@return:
|
893
|
+
"""
|
894
|
+
lc = element.values.get(SVG_ATTR_STROKE_CAP)
|
895
|
+
# SVG default is butt
|
896
|
+
nlc = Linecap.CAP_BUTT
|
897
|
+
if lc is not None:
|
898
|
+
if lc == "butt":
|
899
|
+
nlc = Linecap.CAP_BUTT
|
900
|
+
elif lc == "round":
|
901
|
+
nlc = Linecap.CAP_ROUND
|
902
|
+
elif lc == "square":
|
903
|
+
nlc = Linecap.CAP_SQUARE
|
904
|
+
node.linecap = nlc
|
905
|
+
lj = element.values.get(SVG_ATTR_STROKE_JOIN)
|
906
|
+
# SVG default is miter
|
907
|
+
nlj = Linejoin.JOIN_MITER
|
908
|
+
if lj is not None:
|
909
|
+
nlj = Linejoin.JOIN_MITER
|
910
|
+
if lj == "arcs":
|
911
|
+
nlj = Linejoin.JOIN_ARCS
|
912
|
+
elif lj == "bevel":
|
913
|
+
nlj = Linejoin.JOIN_BEVEL
|
914
|
+
elif lj == "miter":
|
915
|
+
nlj = Linejoin.JOIN_MITER
|
916
|
+
elif lj == "miter-clip":
|
917
|
+
nlj = Linejoin.JOIN_MITER_CLIP
|
918
|
+
elif lj == "round":
|
919
|
+
nlj = Linejoin.JOIN_ROUND
|
920
|
+
node.linejoin = nlj
|
921
|
+
lj = element.values.get(SVG_ATTR_STROKE_DASH)
|
922
|
+
if lj not in (None, "", "none"):
|
923
|
+
node.stroke_dash = lj
|
924
|
+
|
925
|
+
@staticmethod
|
926
|
+
def is_dot(element):
|
927
|
+
"""
|
928
|
+
Check for the degenerate shape dots. This could be a Path that consisting of a Move + Close, Move, or Move any
|
929
|
+
path-segment that has a distance of 0 units. It could be a simple line to the same spot. It could be a polyline
|
930
|
+
which has a single point.
|
931
|
+
|
932
|
+
We avoid doing any calculations without checking the degenerate nature of the would-be dot first.
|
933
|
+
|
934
|
+
@param element:
|
935
|
+
@return:
|
936
|
+
"""
|
937
|
+
if isinstance(element, Path):
|
938
|
+
if len(element) > 2 or element.length(error=1, min_depth=1) > 0:
|
939
|
+
return False, None
|
940
|
+
return True, abs(element).first_point
|
941
|
+
elif isinstance(element, SimpleLine):
|
942
|
+
if element.length() == 0:
|
943
|
+
return True, abs(Path(element)).first_point
|
944
|
+
elif isinstance(element, (Polyline, Polygon)):
|
945
|
+
if len(element) > 1:
|
946
|
+
return False, None
|
947
|
+
if element.length() == 0:
|
948
|
+
return True, abs(Path(element)).first_point
|
949
|
+
return False, None
|
950
|
+
|
951
|
+
def get_tag_label(self, element):
|
952
|
+
"""
|
953
|
+
Gets the tag label from the element. This is usually an inkscape label.
|
954
|
+
|
955
|
+
Let's see whether we can get the label from an inkscape save
|
956
|
+
We only want the 'label' attribute from the current tag, so
|
957
|
+
we look at element.values["attributes"]
|
958
|
+
|
959
|
+
@param element:
|
960
|
+
@return:
|
961
|
+
"""
|
962
|
+
|
963
|
+
if "attributes" in element.values:
|
964
|
+
local_dict = element.values["attributes"]
|
965
|
+
else:
|
966
|
+
local_dict = element.values
|
967
|
+
if local_dict is None:
|
968
|
+
return None
|
969
|
+
ink_tag = "inkscape:label"
|
970
|
+
inkscape = element.values.get("inkscape")
|
971
|
+
if inkscape:
|
972
|
+
ink_tag = "{" + inkscape + "}label"
|
973
|
+
tag_label = local_dict.get(ink_tag)
|
974
|
+
if tag_label:
|
975
|
+
return tag_label
|
976
|
+
return local_dict.get("label")
|
977
|
+
|
978
|
+
def _parse_text(self, element, ident, label, lock, context_node, e_list, set_hidden):
|
979
|
+
"""
|
980
|
+
Parses an SVGText object, into an `elem text` node.
|
981
|
+
|
982
|
+
@param element:
|
983
|
+
@param ident:
|
984
|
+
@param label:
|
985
|
+
@param lock:
|
986
|
+
@param context_node:
|
987
|
+
@param e_list:
|
988
|
+
@return:
|
989
|
+
"""
|
990
|
+
|
991
|
+
if element.text is None:
|
992
|
+
return
|
993
|
+
|
994
|
+
decor = element.values.get("text-decoration", "").lower()
|
995
|
+
node = context_node.add(
|
996
|
+
id=ident,
|
997
|
+
text=element.text,
|
998
|
+
x=element.x,
|
999
|
+
y=element.y,
|
1000
|
+
font=element.values.get("font"),
|
1001
|
+
anchor=element.values.get(SVG_ATTR_TEXT_ANCHOR),
|
1002
|
+
baseline=element.values.get(
|
1003
|
+
SVG_ATTR_TEXT_ALIGNMENT_BASELINE,
|
1004
|
+
element.values.get(SVG_ATTR_TEXT_DOMINANT_BASELINE, "baseline"),
|
1005
|
+
),
|
1006
|
+
matrix=element.transform,
|
1007
|
+
fill=element.fill,
|
1008
|
+
stroke=element.stroke,
|
1009
|
+
stroke_width=element.stroke_width,
|
1010
|
+
stroke_scale=bool(
|
1011
|
+
SVG_VALUE_NON_SCALING_STROKE
|
1012
|
+
not in element.values.get(SVG_ATTR_VECTOR_EFFECT, "")
|
1013
|
+
),
|
1014
|
+
underline="underline" in decor,
|
1015
|
+
strikethrough="line-through" in decor,
|
1016
|
+
overline="overline" in decor,
|
1017
|
+
texttransform=element.values.get("text-transform"),
|
1018
|
+
type="elem text",
|
1019
|
+
label=label,
|
1020
|
+
settings=element.values,
|
1021
|
+
hidden=set_hidden,
|
1022
|
+
)
|
1023
|
+
self.check_for_label_display(node, element)
|
1024
|
+
self.check_for_bound_information(node, element)
|
1025
|
+
e_list.append(node)
|
1026
|
+
|
1027
|
+
def _parse_path(self, element, ident, label, lock, context_node, e_list, set_hidden):
|
1028
|
+
"""
|
1029
|
+
Parses an SVG Path object.
|
1030
|
+
|
1031
|
+
There were a few versions of meerk40t where Path was used to store other save nodes. But, there is not
|
1032
|
+
enough information to reconstruct those elements.
|
1033
|
+
|
1034
|
+
@param element:
|
1035
|
+
@param ident:
|
1036
|
+
@param label:
|
1037
|
+
@param lock:
|
1038
|
+
@param context_node:
|
1039
|
+
@param e_list:
|
1040
|
+
@return:
|
1041
|
+
"""
|
1042
|
+
if len(element) < 0:
|
1043
|
+
return
|
1044
|
+
|
1045
|
+
if element.values.get("type") == "elem polyline":
|
1046
|
+
# Type is polyline we should restore the node type if we have sufficient info to do so.
|
1047
|
+
pass
|
1048
|
+
if element.values.get("type") == "elem ellipse":
|
1049
|
+
# There is not enough info to reconstruct this.
|
1050
|
+
pass
|
1051
|
+
if element.values.get("type") == "elem rect":
|
1052
|
+
# There is not enough info to reconstruct this.
|
1053
|
+
pass
|
1054
|
+
if element.values.get("type") == "elem line":
|
1055
|
+
pass
|
1056
|
+
element.approximate_arcs_with_cubics()
|
1057
|
+
node = context_node.add(
|
1058
|
+
path=element, type="elem path", id=ident, label=label, lock=lock, hidden=set_hidden
|
1059
|
+
)
|
1060
|
+
self.check_for_label_display(node, element)
|
1061
|
+
self.check_for_line_attributes(node, element)
|
1062
|
+
self.check_for_fill_attributes(node, element)
|
1063
|
+
self.check_for_mk_path_attributes(node, element)
|
1064
|
+
self.check_for_bound_information(node, element)
|
1065
|
+
e_list.append(node)
|
1066
|
+
|
1067
|
+
def _parse_polyline(self, element, ident, label, lock, context_node, e_list, set_hidden):
|
1068
|
+
"""
|
1069
|
+
Parses svg Polyline and Polygon objects into `elem polyline` nodes.
|
1070
|
+
|
1071
|
+
@param element:
|
1072
|
+
@param ident:
|
1073
|
+
@param label:
|
1074
|
+
@param lock:
|
1075
|
+
@param context_node:
|
1076
|
+
@param e_list:
|
1077
|
+
@return:
|
1078
|
+
"""
|
1079
|
+
if element.is_degenerate():
|
1080
|
+
return
|
1081
|
+
node = context_node.add(
|
1082
|
+
shape=element,
|
1083
|
+
type="elem polyline",
|
1084
|
+
id=ident,
|
1085
|
+
label=label,
|
1086
|
+
lock=lock,
|
1087
|
+
hidden=set_hidden,
|
1088
|
+
)
|
1089
|
+
self.check_for_label_display(node, element)
|
1090
|
+
self.check_for_line_attributes(node, element)
|
1091
|
+
self.check_for_fill_attributes(node, element)
|
1092
|
+
self.check_for_mk_path_attributes(node, element)
|
1093
|
+
if not self.check_for_bound_information(node, element) and self.precalc_bbox:
|
1094
|
+
# bounds will be done here, paintbounds won't...
|
1095
|
+
if element.transform.is_identity():
|
1096
|
+
points = element.points
|
1097
|
+
else:
|
1098
|
+
points = list(
|
1099
|
+
map(element.transform.point_in_matrix_space, element.points)
|
1100
|
+
)
|
1101
|
+
xmin = min(p.x for p in points if p is not None)
|
1102
|
+
ymin = min(p.y for p in points if p is not None)
|
1103
|
+
xmax = max(p.x for p in points if p is not None)
|
1104
|
+
ymax = max(p.y for p in points if p is not None)
|
1105
|
+
node._bounds = [
|
1106
|
+
xmin,
|
1107
|
+
ymin,
|
1108
|
+
xmax,
|
1109
|
+
ymax,
|
1110
|
+
]
|
1111
|
+
node._bounds_dirty = False
|
1112
|
+
node.revalidate_points()
|
1113
|
+
node._points_dirty = False
|
1114
|
+
e_list.append(node)
|
1115
|
+
|
1116
|
+
def _parse_ellipse(self, element, ident, label, lock, context_node, e_list, set_hidden):
|
1117
|
+
"""
|
1118
|
+
Parses the SVG Circle, and Ellipse nodes into `elem ellipse` nodes.
|
1119
|
+
|
1120
|
+
@param element:
|
1121
|
+
@param ident:
|
1122
|
+
@param label:
|
1123
|
+
@param lock:
|
1124
|
+
@param context_node:
|
1125
|
+
@param e_list:
|
1126
|
+
@return:
|
1127
|
+
"""
|
1128
|
+
if element.is_degenerate():
|
1129
|
+
return
|
1130
|
+
node = context_node.add(
|
1131
|
+
shape=element,
|
1132
|
+
type="elem ellipse",
|
1133
|
+
id=ident,
|
1134
|
+
label=label,
|
1135
|
+
lock=lock,
|
1136
|
+
hidden=set_hidden,
|
1137
|
+
)
|
1138
|
+
self.check_for_label_display(node, element)
|
1139
|
+
self.check_for_line_attributes(node, element)
|
1140
|
+
self.check_for_mk_path_attributes(node, element)
|
1141
|
+
self.check_for_bound_information(node, element)
|
1142
|
+
e_list.append(node)
|
1143
|
+
|
1144
|
+
def _parse_rect(self, element, ident, label, lock, context_node, e_list, set_hidden):
|
1145
|
+
"""
|
1146
|
+
Parse SVG Rect objects into `elem rect` objects.
|
1147
|
+
|
1148
|
+
@param element:
|
1149
|
+
@param ident:
|
1150
|
+
@param label:
|
1151
|
+
@param lock:
|
1152
|
+
@param context_node:
|
1153
|
+
@param e_list:
|
1154
|
+
@return:
|
1155
|
+
"""
|
1156
|
+
if element.is_degenerate():
|
1157
|
+
return
|
1158
|
+
node = context_node.add(
|
1159
|
+
shape=element,
|
1160
|
+
type="elem rect",
|
1161
|
+
id=ident,
|
1162
|
+
label=label,
|
1163
|
+
lock=lock,
|
1164
|
+
hidden=set_hidden,
|
1165
|
+
)
|
1166
|
+
self.check_for_label_display(node, element)
|
1167
|
+
self.check_for_line_attributes(node, element)
|
1168
|
+
self.check_for_mk_path_attributes(node, element)
|
1169
|
+
if not self.check_for_bound_information(node, element) and self.precalc_bbox:
|
1170
|
+
# bounds will be done here, paintbounds won't...
|
1171
|
+
points = (
|
1172
|
+
Point(element.x, element.y),
|
1173
|
+
Point(element.x + element.width, element.y),
|
1174
|
+
Point(element.x + element.width, element.y + element.height),
|
1175
|
+
Point(element.x, element.y + element.height),
|
1176
|
+
)
|
1177
|
+
if not element.transform.is_identity():
|
1178
|
+
points = list(map(element.transform.point_in_matrix_space, points))
|
1179
|
+
xmin = min(p.x for p in points)
|
1180
|
+
ymin = min(p.y for p in points)
|
1181
|
+
xmax = max(p.x for p in points)
|
1182
|
+
ymax = max(p.y for p in points)
|
1183
|
+
node._bounds = [
|
1184
|
+
xmin,
|
1185
|
+
ymin,
|
1186
|
+
xmax,
|
1187
|
+
ymax,
|
1188
|
+
]
|
1189
|
+
node._bounds_dirty = False
|
1190
|
+
node.revalidate_points()
|
1191
|
+
node._points_dirty = False
|
1192
|
+
e_list.append(node)
|
1193
|
+
|
1194
|
+
def _parse_line(self, element, ident, label, lock, context_node, e_list, set_hidden):
|
1195
|
+
"""
|
1196
|
+
Parse SVG Line objects into `elem line`
|
1197
|
+
|
1198
|
+
@param element:
|
1199
|
+
@param ident:
|
1200
|
+
@param label:
|
1201
|
+
@param lock:
|
1202
|
+
@param context_node:
|
1203
|
+
@param e_list:
|
1204
|
+
@return:
|
1205
|
+
"""
|
1206
|
+
if element.is_degenerate():
|
1207
|
+
return
|
1208
|
+
node = context_node.add(
|
1209
|
+
shape=element,
|
1210
|
+
type="elem line",
|
1211
|
+
id=ident,
|
1212
|
+
label=label,
|
1213
|
+
lock=lock,
|
1214
|
+
hidden=set_hidden,
|
1215
|
+
)
|
1216
|
+
self.check_for_label_display(node, element)
|
1217
|
+
self.check_for_line_attributes(node, element)
|
1218
|
+
self.check_for_mk_path_attributes(node, element)
|
1219
|
+
if not self.check_for_bound_information(node, element) and self.precalc_bbox:
|
1220
|
+
# bounds will be done here, paintbounds won't...
|
1221
|
+
points = (
|
1222
|
+
Point(element.x1, element.y1),
|
1223
|
+
Point(element.x2, element.y2),
|
1224
|
+
)
|
1225
|
+
if not element.transform.is_identity():
|
1226
|
+
points = list(map(element.transform.point_in_matrix_space, points))
|
1227
|
+
xmin = min(p.x for p in points)
|
1228
|
+
ymin = min(p.y for p in points)
|
1229
|
+
xmax = max(p.x for p in points)
|
1230
|
+
ymax = max(p.y for p in points)
|
1231
|
+
node._bounds = [
|
1232
|
+
xmin,
|
1233
|
+
ymin,
|
1234
|
+
xmax,
|
1235
|
+
ymax,
|
1236
|
+
]
|
1237
|
+
node._bounds_dirty = False
|
1238
|
+
node.revalidate_points()
|
1239
|
+
node._points_dirty = False
|
1240
|
+
e_list.append(node)
|
1241
|
+
|
1242
|
+
def _parse_image(self, element, ident, label, lock, context_node, e_list, set_hidden):
|
1243
|
+
"""
|
1244
|
+
Parse SVG Image objects into either `image raster` or `elem image` objects, potentially other classes.
|
1245
|
+
|
1246
|
+
@param element:
|
1247
|
+
@param ident:
|
1248
|
+
@param label:
|
1249
|
+
@param lock:
|
1250
|
+
@param context_node:
|
1251
|
+
@param e_list:
|
1252
|
+
@return:
|
1253
|
+
"""
|
1254
|
+
try:
|
1255
|
+
element.load(os.path.dirname(self.pathname))
|
1256
|
+
if element.image is not None:
|
1257
|
+
try:
|
1258
|
+
from PIL import ImageOps
|
1259
|
+
|
1260
|
+
element.image = ImageOps.exif_transpose(element.image)
|
1261
|
+
except ImportError:
|
1262
|
+
pass
|
1263
|
+
try:
|
1264
|
+
operations = ast.literal_eval(element.values["operations"])
|
1265
|
+
except (ValueError, SyntaxError, KeyError):
|
1266
|
+
operations = None
|
1267
|
+
|
1268
|
+
if element.image is not None:
|
1269
|
+
try:
|
1270
|
+
dpi = element.image.info["dpi"]
|
1271
|
+
except KeyError:
|
1272
|
+
dpi = None
|
1273
|
+
_dpi = 500
|
1274
|
+
if (
|
1275
|
+
isinstance(dpi, tuple)
|
1276
|
+
and len(dpi) >= 2
|
1277
|
+
and dpi[0] != 0
|
1278
|
+
and dpi[1] != 0
|
1279
|
+
):
|
1280
|
+
_dpi = round((float(dpi[0]) + float(dpi[1])) / 2, 0)
|
1281
|
+
_overscan = None
|
1282
|
+
try:
|
1283
|
+
_overscan = str(element.values.get("overscan"))
|
1284
|
+
except (ValueError, TypeError):
|
1285
|
+
pass
|
1286
|
+
_direction = None
|
1287
|
+
try:
|
1288
|
+
_direction = int(element.values.get("direction"))
|
1289
|
+
except (ValueError, TypeError):
|
1290
|
+
pass
|
1291
|
+
_invert = None
|
1292
|
+
try:
|
1293
|
+
_invert = bool(element.values.get("invert") == "True")
|
1294
|
+
except (ValueError, TypeError):
|
1295
|
+
pass
|
1296
|
+
_dither = None
|
1297
|
+
try:
|
1298
|
+
_dither = bool(element.values.get("dither") == "True")
|
1299
|
+
except (ValueError, TypeError):
|
1300
|
+
pass
|
1301
|
+
_dither_type = None
|
1302
|
+
try:
|
1303
|
+
_dither_type = element.values.get("dither_type")
|
1304
|
+
except (ValueError, TypeError):
|
1305
|
+
pass
|
1306
|
+
_keyhole = None
|
1307
|
+
try:
|
1308
|
+
_keyhole = element.values.get("keyhole_reference")
|
1309
|
+
except (ValueError, TypeError):
|
1310
|
+
pass
|
1311
|
+
|
1312
|
+
_red = None
|
1313
|
+
try:
|
1314
|
+
_red = float(element.values.get("red"))
|
1315
|
+
except (ValueError, TypeError):
|
1316
|
+
pass
|
1317
|
+
_green = None
|
1318
|
+
try:
|
1319
|
+
_green = float(element.values.get("green"))
|
1320
|
+
except (ValueError, TypeError):
|
1321
|
+
pass
|
1322
|
+
_blue = None
|
1323
|
+
try:
|
1324
|
+
_blue = float(element.values.get("blue"))
|
1325
|
+
except (ValueError, TypeError):
|
1326
|
+
pass
|
1327
|
+
_lightness = None
|
1328
|
+
try:
|
1329
|
+
_lightness = float(element.values.get("lightness"))
|
1330
|
+
except (ValueError, TypeError):
|
1331
|
+
pass
|
1332
|
+
_is_depthmap = False
|
1333
|
+
try:
|
1334
|
+
_is_depthmap = bool(element.values.get("is_depthmap") == "True")
|
1335
|
+
except (ValueError, TypeError):
|
1336
|
+
pass
|
1337
|
+
_depth_resolution = 256
|
1338
|
+
try:
|
1339
|
+
_depth_resolution = int(element.values.get("depth_resolution"))
|
1340
|
+
if _depth_resolution <= 1 or _depth_resolution > 256:
|
1341
|
+
_depth_resolution = 256
|
1342
|
+
except (ValueError, TypeError):
|
1343
|
+
pass
|
1344
|
+
node = context_node.add(
|
1345
|
+
image=element.image,
|
1346
|
+
matrix=element.transform,
|
1347
|
+
type="elem image",
|
1348
|
+
id=ident,
|
1349
|
+
overscan=_overscan,
|
1350
|
+
direction=_direction,
|
1351
|
+
dpi=_dpi,
|
1352
|
+
invert=_invert,
|
1353
|
+
dither=_dither,
|
1354
|
+
dither_type=_dither_type,
|
1355
|
+
red=_red,
|
1356
|
+
green=_green,
|
1357
|
+
blue=_blue,
|
1358
|
+
lightness=_lightness,
|
1359
|
+
label=label,
|
1360
|
+
operations=operations,
|
1361
|
+
lock=lock,
|
1362
|
+
is_depthmap=_is_depthmap,
|
1363
|
+
depth_resolution=_depth_resolution,
|
1364
|
+
keyhole_reference=_keyhole,
|
1365
|
+
hidden=set_hidden,
|
1366
|
+
)
|
1367
|
+
self.check_for_label_display(node, element)
|
1368
|
+
self.check_for_bound_information(node, element)
|
1369
|
+
e_list.append(node)
|
1370
|
+
except OSError:
|
1371
|
+
pass
|
1372
|
+
|
1373
|
+
def _parse_element(self, element, ident, label, lock, context_node, e_list):
|
1374
|
+
"""
|
1375
|
+
SVGElement is type. Generic or unknown node type. These nodes do not have children, these are used in
|
1376
|
+
meerk40t contain notes and operations. Element type="elem point", and other points will also load with
|
1377
|
+
this code.
|
1378
|
+
|
1379
|
+
@param element:
|
1380
|
+
@param ident:
|
1381
|
+
@param label:
|
1382
|
+
@param lock:
|
1383
|
+
@param context_node:
|
1384
|
+
@param e_list:
|
1385
|
+
@return:
|
1386
|
+
"""
|
1387
|
+
|
1388
|
+
# Fix: we have mixed capitalisaton in full_ns and tag --> adjust
|
1389
|
+
tag = element.values.get(SVG_ATTR_TAG).lower()
|
1390
|
+
if tag is not None:
|
1391
|
+
# We remove the name space.
|
1392
|
+
full_ns = f"{{{MEERK40T_NAMESPACE.lower()}}}"
|
1393
|
+
if full_ns in tag:
|
1394
|
+
tag = tag.replace(full_ns, "")
|
1395
|
+
|
1396
|
+
# Check if note-type
|
1397
|
+
if tag == "note":
|
1398
|
+
self.elements.note = element.values.get(SVG_TAG_TEXT)
|
1399
|
+
self.elements.signal("note", self.pathname)
|
1400
|
+
return
|
1401
|
+
|
1402
|
+
# Check if note-type
|
1403
|
+
if tag == "autoexec":
|
1404
|
+
self.elements.last_file_autoexec = element.values.get("autoexec")
|
1405
|
+
s = element.values.get("autoexec-active")
|
1406
|
+
self.elements.last_file_autoexec_active = bool(s in ("1", "True"))
|
1407
|
+
self.elements.signal("autoexec", self.pathname)
|
1408
|
+
return
|
1409
|
+
|
1410
|
+
node_type = element.values.get("type")
|
1411
|
+
if node_type is None:
|
1412
|
+
# Type is not given. Abort.
|
1413
|
+
return
|
1414
|
+
|
1415
|
+
if node_type == "op":
|
1416
|
+
# Meerk40t 0.7.x fallback node types.
|
1417
|
+
op_type = element.values.get("operation")
|
1418
|
+
if op_type is None:
|
1419
|
+
return
|
1420
|
+
node_type = f"op {op_type.lower()}"
|
1421
|
+
element.values["attributes"]["type"] = node_type
|
1422
|
+
|
1423
|
+
node_id = element.values.get("id")
|
1424
|
+
|
1425
|
+
# Get node dictionary.
|
1426
|
+
try:
|
1427
|
+
attrs = element.values["attributes"]
|
1428
|
+
except KeyError:
|
1429
|
+
attrs = element.values
|
1430
|
+
|
1431
|
+
# If type exists in the dictionary, delete it to avoid double attribute issues.
|
1432
|
+
try:
|
1433
|
+
del attrs["type"]
|
1434
|
+
except KeyError:
|
1435
|
+
pass
|
1436
|
+
|
1437
|
+
# Set dictionary types with proper classes.
|
1438
|
+
if "lock" in attrs:
|
1439
|
+
attrs["lock"] = lock
|
1440
|
+
if "transform" in element.values:
|
1441
|
+
# Uses chained transforms from primary context.
|
1442
|
+
attrs["matrix"] = Matrix(element.values["transform"])
|
1443
|
+
if "fill" in attrs:
|
1444
|
+
attrs["fill"] = Color(attrs["fill"])
|
1445
|
+
if "stroke" in attrs:
|
1446
|
+
attrs["stroke"] = Color(attrs["stroke"])
|
1447
|
+
|
1448
|
+
if tag == "operation":
|
1449
|
+
# Operation type node.
|
1450
|
+
if not self.load_operations:
|
1451
|
+
# We don't do that.
|
1452
|
+
return
|
1453
|
+
|
1454
|
+
self.operations_generated = True
|
1455
|
+
|
1456
|
+
try:
|
1457
|
+
if node_type == "op hatch":
|
1458
|
+
# Special fallback operation, op hatch is an op engrave with an effect hatch within it.
|
1459
|
+
node_type = "op engrave"
|
1460
|
+
op = self.elements.op_branch.add(type=node_type, **attrs)
|
1461
|
+
effect = op.add(type="effect hatch", **attrs)
|
1462
|
+
else:
|
1463
|
+
op = self.elements.op_branch.add(type=node_type, **attrs)
|
1464
|
+
op._ref_load = element.values.get("references")
|
1465
|
+
|
1466
|
+
if op is None or not hasattr(op, "type") or op.type is None:
|
1467
|
+
return
|
1468
|
+
if hasattr(op, "validate"):
|
1469
|
+
op.validate()
|
1470
|
+
|
1471
|
+
op.id = node_id
|
1472
|
+
self.operation_list.append(op)
|
1473
|
+
except AttributeError:
|
1474
|
+
# This operation is invalid.
|
1475
|
+
return
|
1476
|
+
except ValueError:
|
1477
|
+
# This operation type failed to bootstrap.
|
1478
|
+
return
|
1479
|
+
|
1480
|
+
elif tag == "element":
|
1481
|
+
# Check if SVGElement: element
|
1482
|
+
if "settings" in attrs:
|
1483
|
+
del attrs[
|
1484
|
+
"settings"
|
1485
|
+
] # If settings was set, delete it, or it will mess things up
|
1486
|
+
elem = context_node.add(type=node_type, **attrs)
|
1487
|
+
# This could be an elem point
|
1488
|
+
self.check_for_label_display(elem, element)
|
1489
|
+
self.check_for_bound_information(elem, element)
|
1490
|
+
try:
|
1491
|
+
elem.validate()
|
1492
|
+
except AttributeError:
|
1493
|
+
pass
|
1494
|
+
elem.id = node_id
|
1495
|
+
e_list.append(elem)
|
1496
|
+
|
1497
|
+
def parse(self, element, context_node, e_list, branch=None, uselabel=None):
|
1498
|
+
"""
|
1499
|
+
Parse does the bulk of the work. Given an element, here the base case is an SVG itself, we parse such that
|
1500
|
+
any groups will call and check all children recursively, updating the context_node, and passing each element
|
1501
|
+
to this same function.
|
1502
|
+
|
1503
|
+
|
1504
|
+
@param element: Element to parse.
|
1505
|
+
@param context_node: Current context parent we're writing to.
|
1506
|
+
@param e_list: elements list of all the nodes added by this function.
|
1507
|
+
@param branch: Branch we are currently adding elements to.
|
1508
|
+
@param uselabel:
|
1509
|
+
@return:
|
1510
|
+
"""
|
1511
|
+
set_hidden = False
|
1512
|
+
display = ""
|
1513
|
+
if "display" in element.values:
|
1514
|
+
display = element.values.get("display").lower()
|
1515
|
+
if display == "none":
|
1516
|
+
if branch not in ("elements", "regmarks"):
|
1517
|
+
return
|
1518
|
+
if element.values.get("visibility") == "hidden" or display == "none":
|
1519
|
+
|
1520
|
+
if self.load_hidden_to_regmarks:
|
1521
|
+
if branch != "regmarks":
|
1522
|
+
self.parse(
|
1523
|
+
element,
|
1524
|
+
self.elements.reg_branch,
|
1525
|
+
self.regmark_list,
|
1526
|
+
branch="regmarks",
|
1527
|
+
)
|
1528
|
+
return
|
1529
|
+
else:
|
1530
|
+
set_hidden = True
|
1531
|
+
|
1532
|
+
ident = element.id
|
1533
|
+
|
1534
|
+
_label = uselabel if uselabel else self.get_tag_label(element)
|
1535
|
+
_lock = None
|
1536
|
+
try:
|
1537
|
+
_lock = bool(element.values.get("lock") == "True")
|
1538
|
+
except (ValueError, TypeError):
|
1539
|
+
pass
|
1540
|
+
|
1541
|
+
is_dot, dot_point = SVGProcessor.is_dot(element)
|
1542
|
+
if is_dot:
|
1543
|
+
node = context_node.add(
|
1544
|
+
point=dot_point,
|
1545
|
+
type="elem point",
|
1546
|
+
matrix=Matrix(),
|
1547
|
+
fill=element.fill,
|
1548
|
+
stroke=element.stroke,
|
1549
|
+
label=_label,
|
1550
|
+
lock=_lock,
|
1551
|
+
hidden=set_hidden,
|
1552
|
+
)
|
1553
|
+
self.check_for_label_display(node, element)
|
1554
|
+
self.check_for_bound_information(node, element)
|
1555
|
+
e_list.append(node)
|
1556
|
+
elif isinstance(element, SVGText):
|
1557
|
+
self._parse_text(element, ident, _label, _lock, context_node, e_list, set_hidden)
|
1558
|
+
elif isinstance(element, Path):
|
1559
|
+
self._parse_path(element, ident, _label, _lock, context_node, e_list, set_hidden)
|
1560
|
+
elif isinstance(element, (Polygon, Polyline)):
|
1561
|
+
self._parse_polyline(element, ident, _label, _lock, context_node, e_list, set_hidden)
|
1562
|
+
elif isinstance(element, (Circle, Ellipse)):
|
1563
|
+
self._parse_ellipse(element, ident, _label, _lock, context_node, e_list, set_hidden)
|
1564
|
+
elif isinstance(element, Rect):
|
1565
|
+
self._parse_rect(element, ident, _label, _lock, context_node, e_list, set_hidden)
|
1566
|
+
elif isinstance(element, SimpleLine):
|
1567
|
+
self._parse_line(element, ident, _label, _lock, context_node, e_list, set_hidden)
|
1568
|
+
elif isinstance(element, SVGImage):
|
1569
|
+
self._parse_image(element, ident, _label, _lock, context_node, e_list, set_hidden)
|
1570
|
+
elif isinstance(element, SVG):
|
1571
|
+
# SVG is type of group, it must be processed before Group. Nothing special is done with the type.
|
1572
|
+
if self.reverse:
|
1573
|
+
for child in reversed(element):
|
1574
|
+
self.parse(child, context_node, e_list, branch=branch)
|
1575
|
+
else:
|
1576
|
+
for child in element:
|
1577
|
+
self.parse(child, context_node, e_list, branch=branch)
|
1578
|
+
elif isinstance(element, Group):
|
1579
|
+
if branch != "regmarks" and (_label == "regmarks" or ident == "regmarks"):
|
1580
|
+
# Recurse at same level within regmarks.
|
1581
|
+
self.parse(
|
1582
|
+
element,
|
1583
|
+
self.elements.reg_branch,
|
1584
|
+
self.regmark_list,
|
1585
|
+
branch="regmarks",
|
1586
|
+
)
|
1587
|
+
return
|
1588
|
+
|
1589
|
+
# Load group with specific group attributes (if needed)
|
1590
|
+
e_dict = dict(element.values["attributes"])
|
1591
|
+
e_type = e_dict.get("type", "group")
|
1592
|
+
if branch != "operations" and (
|
1593
|
+
e_type.startswith("op ")
|
1594
|
+
or e_type.startswith("place ")
|
1595
|
+
or e_type.startswith("util ")
|
1596
|
+
):
|
1597
|
+
# This is an operations, but we are not in operations context.
|
1598
|
+
if not self.load_operations:
|
1599
|
+
# We don't do that.
|
1600
|
+
return
|
1601
|
+
self.operations_generated = True
|
1602
|
+
self.parse(
|
1603
|
+
element,
|
1604
|
+
self.elements.op_branch,
|
1605
|
+
self.operation_list,
|
1606
|
+
branch="operations",
|
1607
|
+
)
|
1608
|
+
return
|
1609
|
+
if "stroke" in e_dict:
|
1610
|
+
e_dict["stroke"] = Color(e_dict.get("stroke"))
|
1611
|
+
if "fill" in e_dict:
|
1612
|
+
e_dict["fill"] = Color(e_dict.get("fill"))
|
1613
|
+
for attr in ("type", "id", "label"):
|
1614
|
+
if attr in e_dict:
|
1615
|
+
del e_dict[attr]
|
1616
|
+
|
1617
|
+
#
|
1618
|
+
already = False
|
1619
|
+
if self.reuse_operations:
|
1620
|
+
# No need to create another operation, if we do
|
1621
|
+
# have an identical operation in place
|
1622
|
+
if e_type.startswith("op "):
|
1623
|
+
# It needs to be non-empty to be used!
|
1624
|
+
for testop in self.elements.ops():
|
1625
|
+
if len(testop.children) == 0:
|
1626
|
+
continue
|
1627
|
+
if e_type != testop.type:
|
1628
|
+
continue
|
1629
|
+
differs = False
|
1630
|
+
for check_attr, check_default in (
|
1631
|
+
("id", None),
|
1632
|
+
("power", "1000"),
|
1633
|
+
("speed", None),
|
1634
|
+
("passes", "0"),
|
1635
|
+
("color", None),
|
1636
|
+
):
|
1637
|
+
if not hasattr(testop, check_attr):
|
1638
|
+
if check_attr in e_dict:
|
1639
|
+
differs = True
|
1640
|
+
break
|
1641
|
+
continue
|
1642
|
+
test_val = getattr(testop, check_attr, check_default)
|
1643
|
+
if test_val is None:
|
1644
|
+
test_val = ""
|
1645
|
+
else:
|
1646
|
+
test_val = str(test_val)
|
1647
|
+
if check_attr == "id":
|
1648
|
+
eop_val = ident
|
1649
|
+
else:
|
1650
|
+
if check_attr not in e_dict:
|
1651
|
+
eop_val = check_default
|
1652
|
+
else:
|
1653
|
+
eop_val = e_dict[check_attr]
|
1654
|
+
if eop_val is None:
|
1655
|
+
eop_val = ""
|
1656
|
+
if test_val != eop_val:
|
1657
|
+
differs = True
|
1658
|
+
# print (f"{testop.type}.{check_attr}: {eop_val} != {test_val}")
|
1659
|
+
break
|
1660
|
+
if differs:
|
1661
|
+
continue
|
1662
|
+
context_node = testop
|
1663
|
+
already = True
|
1664
|
+
break
|
1665
|
+
|
1666
|
+
if not already:
|
1667
|
+
context_node = context_node.add(
|
1668
|
+
type=e_type, id=ident, label=_label, **e_dict
|
1669
|
+
)
|
1670
|
+
self.check_for_label_display(context_node, element)
|
1671
|
+
self.check_for_bound_information(context_node, element)
|
1672
|
+
context_node._ref_load = element.values.get("references")
|
1673
|
+
e_list.append(context_node)
|
1674
|
+
if hasattr(context_node, "validate"):
|
1675
|
+
context_node.validate()
|
1676
|
+
|
1677
|
+
# recurse to children
|
1678
|
+
if self.reverse:
|
1679
|
+
for child in reversed(element):
|
1680
|
+
self.parse(child, context_node, e_list, branch=branch)
|
1681
|
+
else:
|
1682
|
+
for child in element:
|
1683
|
+
self.parse(child, context_node, e_list, branch=branch)
|
1684
|
+
elif isinstance(element, Use):
|
1685
|
+
# recurse to children, but do not subgroup elements.
|
1686
|
+
# We still use the original label
|
1687
|
+
if self.reverse:
|
1688
|
+
for child in reversed(element):
|
1689
|
+
self.parse(
|
1690
|
+
child, context_node, e_list, branch=branch, uselabel=_label
|
1691
|
+
)
|
1692
|
+
else:
|
1693
|
+
for child in element:
|
1694
|
+
self.parse(
|
1695
|
+
child, context_node, e_list, branch=branch, uselabel=_label
|
1696
|
+
)
|
1697
|
+
else:
|
1698
|
+
self._parse_element(element, ident, _label, _lock, context_node, e_list)
|
1699
|
+
|
1700
|
+
def cleanup(self):
|
1701
|
+
# Make a couple of structural fixes that would be to cumbersome to integrate at parse level
|
1702
|
+
# 1) Fix regmark grouping.
|
1703
|
+
# Regmarks nodes are saved under a group with visibility=False set
|
1704
|
+
# So let's flatten this top group
|
1705
|
+
if len(self.regmark_list) > 0:
|
1706
|
+
# We need to add another filenode under regmarks and move all elements to it
|
1707
|
+
context_node = self.elements.reg_branch
|
1708
|
+
file_node = context_node.add(type="file", filepath=self.pathname)
|
1709
|
+
for node in self.regmark_list:
|
1710
|
+
if node._parent is context_node:
|
1711
|
+
if node.type == "group" and (node.id == "regmarks" or node.label == "regmarks"):
|
1712
|
+
for n in list(node.children):
|
1713
|
+
file_node.append_child(n)
|
1714
|
+
node.remove_node() # Removing group/file node.
|
1715
|
+
else:
|
1716
|
+
file_node.append_child(node)
|
1717
|
+
|
1718
|
+
regmark = self.elements.reg_branch
|
1719
|
+
for c in regmark.children:
|
1720
|
+
if c.type == "group" and (c.id == "regmarks" or c.label == "regmarks"):
|
1721
|
+
for n in list(c.children):
|
1722
|
+
c.insert_sibling(n)
|
1723
|
+
c.remove_node() # Removing group/file node.
|
1724
|
+
|
1725
|
+
needs_update = False
|
1726
|
+
for c in self.elements.flat():
|
1727
|
+
# All nodes including regmarks and elements
|
1728
|
+
if c.type == "elem image" and c.keyhole_reference is not None:
|
1729
|
+
refnode = self.elements.find_node(c.keyhole_reference)
|
1730
|
+
if refnode is None or not hasattr(refnode, "as_geometry"):
|
1731
|
+
# Invalid -> remove
|
1732
|
+
c.keyhole_reference = None
|
1733
|
+
else:
|
1734
|
+
try:
|
1735
|
+
self.elements.register_keyhole(refnode, c)
|
1736
|
+
needs_update = True
|
1737
|
+
except ValueError as e:
|
1738
|
+
c.keyhole_reference = None
|
1739
|
+
|
1740
|
+
if needs_update:
|
1741
|
+
self.elements.process_keyhole_updates(None)
|
1742
|
+
|
1743
|
+
class SVGLoader:
|
1744
|
+
"""
|
1745
|
+
SVG loader - loading elements, regmarks and operations
|
1746
|
+
"""
|
1747
|
+
|
1748
|
+
@staticmethod
|
1749
|
+
def load_types():
|
1750
|
+
yield "Scalable Vector Graphics", ("svg", "svgz"), "image/svg+xml"
|
1751
|
+
|
1752
|
+
@staticmethod
|
1753
|
+
def load(context, elements_service, pathname, **kwargs):
|
1754
|
+
ppi = float(kwargs["svg_ppi"]) if "svg_ppi" in kwargs else DEFAULT_PPI
|
1755
|
+
if ppi == 0:
|
1756
|
+
ppi = DEFAULT_PPI
|
1757
|
+
scale_factor = NATIVE_UNIT_PER_INCH / ppi
|
1758
|
+
source = pathname
|
1759
|
+
if pathname.lower().endswith("svgz"):
|
1760
|
+
source = gzip.open(pathname, "rb")
|
1761
|
+
try:
|
1762
|
+
if context.elements.svg_viewport_bed:
|
1763
|
+
width = Length(amount=context.device.view.unit_width).length_mm
|
1764
|
+
height = Length(amount=context.device.view.unit_height).length_mm
|
1765
|
+
else:
|
1766
|
+
width = None
|
1767
|
+
height = None
|
1768
|
+
# The color attribute of SVG.parse decides which default color
|
1769
|
+
# a stroke / fill will get if the attribute "currentColor" is
|
1770
|
+
# set - we opt for "black"
|
1771
|
+
svg = SVG.parse(
|
1772
|
+
source=source,
|
1773
|
+
reify=False,
|
1774
|
+
width=width,
|
1775
|
+
height=height,
|
1776
|
+
ppi=ppi,
|
1777
|
+
color="black",
|
1778
|
+
parse_display_none=True,
|
1779
|
+
transform=f"scale({scale_factor})",
|
1780
|
+
)
|
1781
|
+
except ParseError as e:
|
1782
|
+
raise BadFileError(str(e)) from e
|
1783
|
+
reuse = elements_service.reuse_operations_on_load
|
1784
|
+
to_regmarks = elements_service.load_hidden_to_regmarks
|
1785
|
+
elements_service._loading_cleared = True
|
1786
|
+
svg_processor = SVGProcessor(elements_service, load_operations=True, reuse_operations=reuse, load_hidden_to_regmarks=to_regmarks)
|
1787
|
+
svg_processor.process(svg, pathname)
|
1788
|
+
svg_processor.cleanup()
|
1789
|
+
return True
|
1790
|
+
|
1791
|
+
|
1792
|
+
class SVGLoaderPlain:
|
1793
|
+
"""
|
1794
|
+
SVG loader but without loading the operations branch
|
1795
|
+
"""
|
1796
|
+
|
1797
|
+
@staticmethod
|
1798
|
+
def load_types():
|
1799
|
+
yield "SVG (elements only)", ("svg", "svgz"), "image/svg+xml"
|
1800
|
+
|
1801
|
+
@staticmethod
|
1802
|
+
def load(context, elements_service, pathname, **kwargs):
|
1803
|
+
ppi = float(kwargs["svg_ppi"]) if "svg_ppi" in kwargs else DEFAULT_PPI
|
1804
|
+
if ppi == 0:
|
1805
|
+
ppi = DEFAULT_PPI
|
1806
|
+
scale_factor = NATIVE_UNIT_PER_INCH / ppi
|
1807
|
+
source = pathname
|
1808
|
+
if pathname.lower().endswith("svgz"):
|
1809
|
+
source = gzip.open(pathname, "rb")
|
1810
|
+
try:
|
1811
|
+
if context.elements.svg_viewport_bed:
|
1812
|
+
width = Length(amount=context.device.view.unit_width).length_mm
|
1813
|
+
height = Length(amount=context.device.view.unit_height).length_mm
|
1814
|
+
else:
|
1815
|
+
width = None
|
1816
|
+
height = None
|
1817
|
+
# The color attribute of SVG.parse decides which default color
|
1818
|
+
# a stroke / fill will get if the attribute "currentColor" is
|
1819
|
+
# set - we opt for "black"
|
1820
|
+
svg = SVG.parse(
|
1821
|
+
source=source,
|
1822
|
+
reify=False,
|
1823
|
+
width=width,
|
1824
|
+
height=height,
|
1825
|
+
ppi=ppi,
|
1826
|
+
color="black",
|
1827
|
+
transform=f"scale({scale_factor})",
|
1828
|
+
)
|
1829
|
+
except ParseError as e:
|
1830
|
+
raise BadFileError(str(e)) from e
|
1831
|
+
elements_service._loading_cleared = True
|
1832
|
+
to_regmarks = elements_service.load_hidden_to_regmarks
|
1833
|
+
svg_processor = SVGProcessor(elements_service, load_operations=False, load_hidden_to_regmarks=to_regmarks)
|
1834
|
+
svg_processor.process(svg, pathname)
|
1835
|
+
svg_processor.cleanup()
|
1836
|
+
return True
|