summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 8cd8b6e)
raw | patch | inline | side by side (parent: 8cd8b6e)
author | JazzyNico <nicoduf@yahoo.fr> | |
Fri, 8 Oct 2010 10:44:19 +0000 (12:44 +0200) | ||
committer | JazzyNico <nicoduf@yahoo.fr> | |
Fri, 8 Oct 2010 10:44:19 +0000 (12:44 +0200) |
13 files changed:
po/POTFILES.in | patch | blob | history | |
po/inkscape.pot | patch | blob | history | |
share/extensions/Makefile.am | patch | blob | history | |
share/extensions/gcodetools.py | [new file with mode: 0644] | patch | blob |
share/extensions/gcodetools_all_in_one.inx | [new file with mode: 0644] | patch | blob |
share/extensions/gcodetools_area.inx | [new file with mode: 0644] | patch | blob |
share/extensions/gcodetools_check_for_updates.inx | [new file with mode: 0644] | patch | blob |
share/extensions/gcodetools_dxf_points.inx | [new file with mode: 0644] | patch | blob |
share/extensions/gcodetools_engraving.inx | [new file with mode: 0644] | patch | blob |
share/extensions/gcodetools_lathe.inx | [new file with mode: 0644] | patch | blob |
share/extensions/gcodetools_orientation_points.inx | [new file with mode: 0644] | patch | blob |
share/extensions/gcodetools_path_to_gcode.inx | [new file with mode: 0644] | patch | blob |
share/extensions/gcodetools_tools_library.inx | [new file with mode: 0644] | patch | blob |
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 83da4321b0fe5f85b499ad81212d25375e9efa45..6bd2da7568b15b49dc78f5a9ee470fabc041bd69 100644 (file)
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
share/extensions/embedimage.py
share/extensions/export_gimp_palette.py
share/extensions/extractimage.py
+share/extensions/gcodetools.py
share/extensions/guides_creator.py
share/extensions/inkex.py
share/extensions/markers_strokepaint.py
[type: gettext/xml] share/extensions/fractalize.inx
[type: gettext/xml] share/extensions/funcplot.inx
[type: gettext/xml] share/extensions/gears.inx
+[type: gettext/xml] share/extensions/gcodetools_all_in_one.inx
+[type: gettext/xml] share/extensions/gcodetools_area.inx
+[type: gettext/xml] share/extensions/gcodetools_check_for_updates.inx
+[type: gettext/xml] share/extensions/gcodetools_dxf_points.inx
+[type: gettext/xml] share/extensions/gcodetools_engraving.inx
+[type: gettext/xml] share/extensions/gcodetools_lathe.inx
+[type: gettext/xml] share/extensions/gcodetools_orientation_points.inx
+[type: gettext/xml] share/extensions/gcodetools_path_to_gcode.inx
+[type: gettext/xml] share/extensions/gcodetools_tools_library.inx
[type: gettext/xml] share/extensions/gimp_xcf.inx
[type: gettext/xml] share/extensions/grid_cartesian.inx
[type: gettext/xml] share/extensions/grid_polar.inx
diff --git a/po/inkscape.pot b/po/inkscape.pot
index a2de67bed3ec388f0eb5a885da16cd614f81a1fe..34a16356f87d35911eefda9f10ea9d908c160326 100644 (file)
--- a/po/inkscape.pot
+++ b/po/inkscape.pot
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: inkscape-devel@lists.sourceforge.net\n"
-"POT-Creation-Date: 2010-10-02 17:43+0200\n"
+"POT-Creation-Date: 2010-10-08 12:37+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
#: ../share/extensions/web-set-att.inx.h:4
#: ../share/extensions/web-transmit-att.inx.h:4
#: ../src/ui/dialog/extension-editor.cpp:81
+#: ../share/extensions/gcodetools_all_in_one.inx.h:25
+#: ../share/extensions/gcodetools_area.inx.h:17
+#: ../share/extensions/gcodetools_check_for_updates.inx.h:5
+#: ../share/extensions/gcodetools_dxf_points.inx.h:13
+#: ../share/extensions/gcodetools_engraving.inx.h:11
+#: ../share/extensions/gcodetools_lathe.inx.h:14
+#: ../share/extensions/gcodetools_orientation_points.inx.h:5
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:11
+#: ../share/extensions/gcodetools_tools_library.inx.h:3
msgid "Help"
msgstr ""
#: ../share/extensions/web-set-att.inx.h:6
#: ../share/extensions/web-transmit-att.inx.h:6
#: ../src/ui/dialog/tracedialog.cpp:617 ../src/ui/dialog/tracedialog.cpp:623
+#: ../share/extensions/gcodetools_all_in_one.inx.h:33
+#: ../share/extensions/gcodetools_area.inx.h:22
+#: ../share/extensions/gcodetools_engraving.inx.h:16
+#: ../share/extensions/gcodetools_lathe.inx.h:22
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:15
msgid "Options"
msgstr ""
msgstr ""
#: ../share/extensions/measure.inx.h:1
+#: ../share/extensions/gcodetools_all_in_one.inx.h:8
+#: ../share/extensions/gcodetools_area.inx.h:5
msgid "Area"
msgstr ""
msgid "Unable to find image data."
msgstr ""
+#: ../share/extensions/gcodetools.py:3087
+msgid ""
+"Directory does not exist! Please specify existing directory at Preferences "
+"tab!"
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:3112
+#, python-format
+msgid ""
+"Can not write to specified file!\n"
+"%s"
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:3240
+#, python-format
+msgid ""
+"Orientation points for '%s' layer have not been found! Please add "
+"orientation points using Orientation tab!"
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:3246
+#, python-format
+msgid "There are more than one orientation point groups in '%s' layer"
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:3277
+#: ../share/extensions/gcodetools.py:3279
+msgid ""
+"Orientation points are wrong! (if there are two orientation points they "
+"sould not be the same. If there are three orientation points they should not "
+"be in a straight line.)"
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:3398
+#, python-format
+msgid ""
+"Warning! Found bad orientation points in '%s' layer. Resulting Gcode could "
+"be corrupt!"
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:3411
+msgid ""
+"This extension works with Paths and Dynamic Offsets and groups of them only! "
+"All other objects will be ignored!\n"
+"Solution 1: press Path->Object to path or Shift+Ctrl+C.\n"
+"Solution 2: Path->Dynamic offset or Ctrl+J.\n"
+"Solution 3: export all contours to PostScript level 2 (File->Save As->.ps) "
+"and File->Import this file."
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:3468
+#, python-format
+msgid ""
+"Warning! Tool's and default tool's parameter's (%s) types are not the same "
+"( type('%s') != type('%s') )."
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:3471
+#, python-format
+msgid "Warning! Tool has parameter that default tool has not ( '%s': '%s' )."
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:3485
+#, python-format
+msgid "Layer '%s' contains more than one tool!"
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:3488
+#, python-format
+msgid ""
+"Can not find tool for '%s' layer! Please add one with Tools library tab!"
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:3591
+#: ../share/extensions/gcodetools.py:3672
+msgid "No paths are selected! Trying to work on all available paths."
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:3610
+#: ../share/extensions/gcodetools.py:3681
+msgid ""
+"Warning: One or more paths dont have 'd' parameter, try to Ungroup (Ctrl"
+"+Shift+G) and Object to Path (Ctrl+Shift+C)!"
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:3640
+msgid ""
+"Noting is selected. Please select something to convert to drill point "
+"(dxfpoint) or clear point sign."
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:3713
+#: ../share/extensions/gcodetools.py:3807
+msgid "This extension requires at least one selected path."
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:3719
+#, python-format
+msgid "Tool diameter must be > 0 but tool's diameter on '%s' layer is not!"
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:3730
+msgid "Warning: omitting non-path"
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:4062
+#, python-format
+msgid "Tool '%s' has no shape!"
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:4072
+msgid "No need to engrave sharp angles."
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:4085
+msgid ""
+"Active layer already has orientation points! Remove them or select another "
+"layer!"
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:4133
+msgid "Active layer already has a tool! Remove it or select another layer!"
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:4257
+msgid "Selection is empty! Will compute whole drawing."
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:4317
+msgid ""
+"Tutorials, manuals and support can be found at\n"
+"English support forum:\n"
+"\thttp://www.cnc-club.ru/gcodetools\n"
+"and Russian support forum:\n"
+"\thttp://www.cnc-club.ru/gcodetoolsru"
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:4362
+msgid "Lathe X and Z axis remap should be 'X', 'Y' or 'Z'. Exiting..."
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:4365
+msgid "Lathe X and Z axis remap should be the same. Exiting..."
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:4526
+msgid ""
+"Select one of the active tabs - Path to Gcode, Area, Engraving, DXF points, "
+"Orientation, Offset, Lathe or Tools library."
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:4532
+msgid ""
+"Orientation points have not been defined! A default set of orientation "
+"points has been automatically added."
+msgstr ""
+
+#: ../share/extensions/gcodetools.py:4536
+msgid ""
+"Cutting tool has not been defined! A default tool has been automatically "
+"added."
+msgstr ""
+
#: ../share/extensions/inkex.py:67
#, python-format
msgid ""
msgstr ""
#: ../src/sp-flowtext.cpp:378 ../src/sp-text.cpp:427
-#: ../src/text-context.cpp:1604
+#: ../src/text-context.cpp:1628
msgid " [truncated]"
msgstr ""
msgid "Set as default"
msgstr ""
-#: ../src/dialogs/text-edit.cpp:665 ../src/text-context.cpp:1500
+#: ../src/dialogs/text-edit.cpp:665 ../src/text-context.cpp:1524
msgid "Set text style"
msgstr ""
"this extension."
msgstr ""
-#: ../src/extension/implementation/script.cpp:985
+#: ../src/extension/implementation/script.cpp:989
msgid ""
"Inkscape has received additional data from the script executed. The script "
"did not return an error, but this may indicate the results will not be as "
#. Name
#: ../src/libgdl/gdl-dock-item.c:287 ../src/widgets/toolbox.cpp:7616
+#: ../share/extensions/gcodetools_all_in_one.inx.h:34
+#: ../share/extensions/gcodetools_orientation_points.inx.h:6
msgid "Orientation"
msgstr ""
msgid "<b>Nothing</b> was deleted."
msgstr ""
-#: ../src/selection-chemistry.cpp:330 ../src/text-context.cpp:1002
+#: ../src/selection-chemistry.cpp:330 ../src/text-context.cpp:1026
#: ../src/ui/dialog/swatches.cpp:208 ../src/ui/dialog/swatches.cpp:271
#: ../src/widgets/toolbox.cpp:1362 ../src/widgets/toolbox.cpp:6130
msgid "Delete"
msgid "<b>Rotate</b>: %0.2f°; with <b>Ctrl</b> to snap angle"
msgstr ""
-#: ../src/seltrans.cpp:1367
+#: ../src/seltrans.cpp:1364
#, c-format
msgid "Move <b>center</b> to %s, %s"
msgstr ""
-#: ../src/seltrans.cpp:1542
+#: ../src/seltrans.cpp:1539
#, c-format
msgid ""
"<b>Move</b> by %s, %s; with <b>Ctrl</b> to restrict to horizontal/vertical; "
@@ -13878,120 +14059,120 @@ msgstr ""
msgid "Unicode (<b>Enter</b> to finish): %s: %s"
msgstr ""
-#: ../src/text-context.cpp:581 ../src/text-context.cpp:856
+#: ../src/text-context.cpp:581 ../src/text-context.cpp:880
msgid "Unicode (<b>Enter</b> to finish): "
msgstr ""
-#: ../src/text-context.cpp:656
+#: ../src/text-context.cpp:668
#, c-format
msgid "<b>Flowed text frame</b>: %s × %s"
msgstr ""
-#: ../src/text-context.cpp:688
+#: ../src/text-context.cpp:714
msgid "Type text; <b>Enter</b> to start new line."
msgstr ""
-#: ../src/text-context.cpp:701
+#: ../src/text-context.cpp:725
msgid "Flowed text is created."
msgstr ""
-#: ../src/text-context.cpp:703
+#: ../src/text-context.cpp:727
msgid "Create flowed text"
msgstr ""
-#: ../src/text-context.cpp:705
+#: ../src/text-context.cpp:729
msgid ""
"The frame is <b>too small</b> for the current font size. Flowed text not "
"created."
msgstr ""
-#: ../src/text-context.cpp:841
+#: ../src/text-context.cpp:865
msgid "No-break space"
msgstr ""
-#: ../src/text-context.cpp:843
+#: ../src/text-context.cpp:867
msgid "Insert no-break space"
msgstr ""
-#: ../src/text-context.cpp:880
+#: ../src/text-context.cpp:904
msgid "Make bold"
msgstr ""
-#: ../src/text-context.cpp:898
+#: ../src/text-context.cpp:922
msgid "Make italic"
msgstr ""
-#: ../src/text-context.cpp:937
+#: ../src/text-context.cpp:961
msgid "New line"
msgstr ""
-#: ../src/text-context.cpp:971
+#: ../src/text-context.cpp:995
msgid "Backspace"
msgstr ""
-#: ../src/text-context.cpp:1019
+#: ../src/text-context.cpp:1043
msgid "Kern to the left"
msgstr ""
-#: ../src/text-context.cpp:1044
+#: ../src/text-context.cpp:1068
msgid "Kern to the right"
msgstr ""
-#: ../src/text-context.cpp:1069
+#: ../src/text-context.cpp:1093
msgid "Kern up"
msgstr ""
-#: ../src/text-context.cpp:1095
+#: ../src/text-context.cpp:1119
msgid "Kern down"
msgstr ""
-#: ../src/text-context.cpp:1172
+#: ../src/text-context.cpp:1196
msgid "Rotate counterclockwise"
msgstr ""
-#: ../src/text-context.cpp:1193
+#: ../src/text-context.cpp:1217
msgid "Rotate clockwise"
msgstr ""
-#: ../src/text-context.cpp:1210
+#: ../src/text-context.cpp:1234
msgid "Contract line spacing"
msgstr ""
-#: ../src/text-context.cpp:1218
+#: ../src/text-context.cpp:1242
msgid "Contract letter spacing"
msgstr ""
-#: ../src/text-context.cpp:1237
+#: ../src/text-context.cpp:1261
msgid "Expand line spacing"
msgstr ""
-#: ../src/text-context.cpp:1245
+#: ../src/text-context.cpp:1269
msgid "Expand letter spacing"
msgstr ""
-#: ../src/text-context.cpp:1375
+#: ../src/text-context.cpp:1399
msgid "Paste text"
msgstr ""
-#: ../src/text-context.cpp:1621
+#: ../src/text-context.cpp:1645
#, c-format
msgid ""
"Type or edit flowed text (%d characters%s); <b>Enter</b> to start new "
"paragraph."
msgstr ""
-#: ../src/text-context.cpp:1623
+#: ../src/text-context.cpp:1647
#, c-format
msgid "Type or edit text (%d characters%s); <b>Enter</b> to start new line."
msgstr ""
-#: ../src/text-context.cpp:1631 ../src/tools-switch.cpp:197
+#: ../src/text-context.cpp:1655 ../src/tools-switch.cpp:197
msgid ""
"<b>Click</b> to select or create text, <b>drag</b> to create flowed text; "
"then type."
msgstr ""
-#: ../src/text-context.cpp:1741
+#: ../src/text-context.cpp:1765
msgid "Type text"
msgstr ""
@@ -25265,6 +25446,465 @@ msgstr ""
#. End:
#.
#. vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
+#: ../share/extensions/gcodetools_all_in_one.inx.h:1
+#: ../share/extensions/gcodetools_area.inx.h:1
+msgid ""
+"\"Create area offset\": creates several Inkscape path offsets to fill "
+"original path's area up to \"Area radius\" value. Outlines start from \"1/2 D"
+"\" up to \"Area width\" total width with \"D\" steps where D is taken from "
+"the nearest tool definition (\"Tool diameter\" value). Only one offset will "
+"be created if the \"Area width\" is equal to \"1/2 D\"."
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:2
+#: ../share/extensions/gcodetools_orientation_points.inx.h:1
+msgid "2-points mode (move and rotate, maintained aspect ratio X/Y)"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:3
+#: ../share/extensions/gcodetools_orientation_points.inx.h:2
+msgid "3-points mode (move, rotate and mirror, different X/Y scale)"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:4
+#: ../share/extensions/gcodetools_area.inx.h:2
+msgid "Action:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:5
+#: ../share/extensions/gcodetools_area.inx.h:3
+#: ../share/extensions/gcodetools_dxf_points.inx.h:1
+#: ../share/extensions/gcodetools_engraving.inx.h:1
+#: ../share/extensions/gcodetools_lathe.inx.h:1
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:1
+msgid "Add numeric suffix to filename"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:6
+#: ../share/extensions/gcodetools_area.inx.h:4
+#: ../share/extensions/gcodetools_dxf_points.inx.h:2
+#: ../share/extensions/gcodetools_engraving.inx.h:2
+#: ../share/extensions/gcodetools_lathe.inx.h:2
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:2
+msgid "Additional post-processor:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:7
+msgid "All in one"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:9
+#: ../share/extensions/gcodetools_area.inx.h:6
+msgid "Area artefacts"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:10
+#: ../share/extensions/gcodetools_area.inx.h:7
+msgid "Area width:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:11
+#: ../share/extensions/gcodetools_area.inx.h:8
+msgid "Artefact diameter:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:12
+#: ../share/extensions/gcodetools_area.inx.h:9
+#: ../share/extensions/gcodetools_lathe.inx.h:3
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:3
+msgid ""
+"Biarc interpolation tolerance is the maximum distance between path and its "
+"approximation. The segment will be split into two segments if the distance "
+"between path's segment and it's approximation exceeds biarc interpolation "
+"tolerance."
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:13
+#: ../share/extensions/gcodetools_area.inx.h:10
+#: ../share/extensions/gcodetools_lathe.inx.h:4
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:4
+msgid "Biarc interpolation tolerance:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:14
+#: ../share/extensions/gcodetools_dxf_points.inx.h:3
+msgid ""
+"Convert selected objects to drill points (as dxf_import plugin does). Also "
+"you can save original shape. Only the start point of each curve will be "
+"used. Also you can manually select object, open XML editor (Shift+Ctrl+X) "
+"and add or remove XML tag 'dxfpoint' with any value."
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:15
+#: ../share/extensions/gcodetools_dxf_points.inx.h:4
+msgid "Convert selection:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:16
+#: ../share/extensions/gcodetools_dxf_points.inx.h:6
+msgid "DXF points"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:17
+#: ../share/extensions/gcodetools_area.inx.h:11
+#: ../share/extensions/gcodetools_dxf_points.inx.h:7
+#: ../share/extensions/gcodetools_engraving.inx.h:3
+#: ../share/extensions/gcodetools_lathe.inx.h:6
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:5
+msgid "Directory:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:18
+msgid "Draw additional graphics to debug engraving path:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:19
+#: ../share/extensions/gcodetools_engraving.inx.h:5
+msgid "Engraving"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:20
+#: ../share/extensions/gcodetools_area.inx.h:12
+#: ../share/extensions/gcodetools_dxf_points.inx.h:8
+#: ../share/extensions/gcodetools_engraving.inx.h:6
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:6
+msgid "File:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:21
+#: ../share/extensions/gcodetools_area.inx.h:13
+#: ../share/extensions/gcodetools_dxf_points.inx.h:9
+#: ../share/extensions/gcodetools_engraving.inx.h:7
+#: ../share/extensions/gcodetools_lathe.inx.h:10
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:7
+msgid "Full path to log file:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:22
+#: ../share/extensions/gcodetools_area.inx.h:14
+#: ../share/extensions/gcodetools_check_for_updates.inx.h:3
+#: ../share/extensions/gcodetools_dxf_points.inx.h:10
+#: ../share/extensions/gcodetools_engraving.inx.h:8
+#: ../share/extensions/gcodetools_lathe.inx.h:11
+#: ../share/extensions/gcodetools_orientation_points.inx.h:3
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:8
+#: ../share/extensions/gcodetools_tools_library.inx.h:1
+msgid "Gcodetools"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:23
+#: ../share/extensions/gcodetools_area.inx.h:15
+#: ../share/extensions/gcodetools_check_for_updates.inx.h:4
+#: ../share/extensions/gcodetools_dxf_points.inx.h:11
+#: ../share/extensions/gcodetools_engraving.inx.h:9
+#: ../share/extensions/gcodetools_lathe.inx.h:12
+#: ../share/extensions/gcodetools_orientation_points.inx.h:4
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:9
+#: ../share/extensions/gcodetools_tools_library.inx.h:2
+msgid ""
+"Gcodetools plug-in: converts paths to Gcode (using circular interpolation), "
+"makes offset paths and engraves sharp corners using cone cutters. This plug-"
+"in calculates Gcode for paths using circular interpolation or linear motion "
+"when needed. Tutorials, manuals and support can be found at English support "
+"forum: http://www.cnc-club.ru/gcodetools and Russian support forum: http://"
+"www.cnc-club.ru/gcodetoolsru Credits: Nick Drobchenko, Vladimir Kalyaev, "
+"John Brooker, Henry Nicolas. Gcodetools ver. 1.6.01"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:24
+#: ../share/extensions/gcodetools_area.inx.h:16
+#: ../share/extensions/gcodetools_dxf_points.inx.h:12
+#: ../share/extensions/gcodetools_engraving.inx.h:10
+#: ../share/extensions/gcodetools_lathe.inx.h:13
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:10
+msgid "Generate log file"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:26
+#: ../share/extensions/gcodetools_tools_library.inx.h:4
+msgid "Just check tools"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:27
+#: ../share/extensions/gcodetools_area.inx.h:18
+msgid "Maximum area cutting curves:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:28
+#: ../share/extensions/gcodetools_engraving.inx.h:12
+msgid "Maximum distance for engraving:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:29
+#: ../share/extensions/gcodetools_area.inx.h:19
+#: ../share/extensions/gcodetools_lathe.inx.h:19
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:12
+msgid "Maximum splitting depth:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:30
+#: ../share/extensions/gcodetools_area.inx.h:20
+#: ../share/extensions/gcodetools_engraving.inx.h:13
+#: ../share/extensions/gcodetools_lathe.inx.h:20
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:13
+msgid "Minimum arc radius:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:31
+#: ../share/extensions/gcodetools_engraving.inx.h:14
+msgid "Number of sample points used to calculate distance:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:32
+#: ../share/extensions/gcodetools_area.inx.h:21
+#: ../share/extensions/gcodetools_engraving.inx.h:15
+#: ../share/extensions/gcodetools_lathe.inx.h:21
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:14
+msgid "Offset along Z axis:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:35
+#: ../share/extensions/gcodetools_orientation_points.inx.h:8
+msgid ""
+"Orientation points are used to calculate transformation (offset,scale,mirror,"
+"rotation in XY plane) of the path. 3-points mode only: do not put all three "
+"into one line (use 2-points mode instead). You can modify Z surface, Z depth "
+"values later using text tool (3rd coordinates). If there are no orientation "
+"points inside current layer they are taken from the upper layer. Do not "
+"ungroup orientation points! You can select them using double click to enter "
+"the group or by Ctrl+Click. Now press apply to create control points "
+"(independent set for each layer)."
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:36
+#: ../share/extensions/gcodetools_orientation_points.inx.h:9
+msgid "Orientation type:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:37
+#: ../share/extensions/gcodetools_area.inx.h:23
+#: ../share/extensions/gcodetools_lathe.inx.h:23
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:16
+msgid "Path to Gcode"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:38
+#: ../share/extensions/gcodetools_area.inx.h:24
+#: ../share/extensions/gcodetools_dxf_points.inx.h:14
+#: ../share/extensions/gcodetools_engraving.inx.h:17
+#: ../share/extensions/gcodetools_lathe.inx.h:24
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:17
+msgid "Post-processor:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:39
+#: ../share/extensions/gcodetools_area.inx.h:25
+#: ../share/extensions/gcodetools_dxf_points.inx.h:15
+#: ../share/extensions/gcodetools_engraving.inx.h:18
+#: ../share/extensions/gcodetools_lathe.inx.h:25
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:18
+msgid "Preferences"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:40
+#: ../share/extensions/gcodetools_area.inx.h:26
+#: ../share/extensions/gcodetools_engraving.inx.h:19
+#: ../share/extensions/gcodetools_lathe.inx.h:26
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:19
+msgid "Scale along Z axis:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:41
+#: ../share/extensions/gcodetools_area.inx.h:27
+#: ../share/extensions/gcodetools_engraving.inx.h:20
+#: ../share/extensions/gcodetools_lathe.inx.h:27
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:20
+msgid "Select all paths if nothing is selected"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:42
+#: ../share/extensions/gcodetools_tools_library.inx.h:5
+msgid ""
+"Selected tool type fills appropriate default values. You can change these "
+"values using the Text tool later on. The topmost (z order) tool in the "
+"active layer is used. If there is no tool inside the current layer it is "
+"taken from the upper layer. Press Apply to create new tool."
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:43
+#: ../share/extensions/gcodetools_engraving.inx.h:21
+msgid "Sharp angle tolerance:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:44
+#: ../share/extensions/gcodetools_engraving.inx.h:22
+msgid ""
+"This function creates path to engrave sharp angles. Cutter's shape function "
+"is defined by the tool. Some simple shapes: cone....(45 degrees)...........: "
+"w cone....(height/diameter=10/3).: 10/3 w sphere..(\"r\" diameter).........: "
+"math.sqrt(max(0,r**2-w**2)) ellipse.(R1=r and R2=r*4r).....: math.sqrt(max(0,"
+"r**2-w**2))*4"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:45
+#: ../share/extensions/gcodetools_tools_library.inx.h:6
+msgid "Tools library"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:46
+#: ../share/extensions/gcodetools_tools_library.inx.h:7
+msgid "Tools type:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:47
+#: ../share/extensions/gcodetools_area.inx.h:28
+#: ../share/extensions/gcodetools_dxf_points.inx.h:16
+#: ../share/extensions/gcodetools_engraving.inx.h:23
+#: ../share/extensions/gcodetools_lathe.inx.h:28
+#: ../share/extensions/gcodetools_orientation_points.inx.h:10
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:21
+msgid "Units (mm or in):"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:48
+#: ../share/extensions/gcodetools_area.inx.h:29
+msgid ""
+"Usage: 1. Select all Area Offsets (gray outlines) 2. Object/Ungroup (Shift"
+"+Ctrl+G) 3. Press Apply Suspected small objects will be marked out by "
+"colored arrows."
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:49
+#: ../share/extensions/gcodetools_orientation_points.inx.h:11
+msgid "Z depth:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:50
+#: ../share/extensions/gcodetools_area.inx.h:30
+#: ../share/extensions/gcodetools_dxf_points.inx.h:17
+#: ../share/extensions/gcodetools_engraving.inx.h:24
+#: ../share/extensions/gcodetools_lathe.inx.h:29
+#: ../share/extensions/gcodetools_path_to_gcode.inx.h:22
+msgid "Z safe height for G00 move over blank:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:51
+#: ../share/extensions/gcodetools_orientation_points.inx.h:12
+msgid "Z surface:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:52
+#: ../share/extensions/gcodetools_dxf_points.inx.h:18
+msgid "clear dxfpoint sign"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:53
+#: ../share/extensions/gcodetools_tools_library.inx.h:8
+msgid "cone"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:54
+#: ../share/extensions/gcodetools_tools_library.inx.h:9
+msgid "cylinder"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:55
+#: ../share/extensions/gcodetools_tools_library.inx.h:10
+msgid "default"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:56
+#: ../share/extensions/gcodetools_area.inx.h:31
+msgid "delete"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:57
+#: ../share/extensions/gcodetools_tools_library.inx.h:11
+msgid "lathe cutter"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:58
+#: ../share/extensions/gcodetools_area.inx.h:32
+msgid "mark with an arrow"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:59
+#: ../share/extensions/gcodetools_area.inx.h:33
+msgid "mark with style"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:60
+#: ../share/extensions/gcodetools_tools_library.inx.h:12
+msgid "plasma"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:61
+#: ../share/extensions/gcodetools_dxf_points.inx.h:19
+msgid "set as dxfpoint and draw arrow"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:62
+#: ../share/extensions/gcodetools_dxf_points.inx.h:20
+msgid "set as dxfpoint and save shape"
+msgstr ""
+
+#: ../share/extensions/gcodetools_all_in_one.inx.h:63
+#: ../share/extensions/gcodetools_tools_library.inx.h:13
+msgid "tangent knife"
+msgstr ""
+
+#: ../share/extensions/gcodetools_check_for_updates.inx.h:1
+msgid "Check for Gcodetools latest stable version and try to get the updates."
+msgstr ""
+
+#: ../share/extensions/gcodetools_check_for_updates.inx.h:2
+msgid "Check for updates"
+msgstr ""
+
+#: ../share/extensions/gcodetools_dxf_points.inx.h:5
+msgid "DXF Points"
+msgstr ""
+
+#: ../share/extensions/gcodetools_engraving.inx.h:4
+msgid "Draw additional graphics to debug engraving path"
+msgstr ""
+
+#: ../share/extensions/gcodetools_lathe.inx.h:5
+msgid "Create fine cut using:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_lathe.inx.h:7
+msgid "File"
+msgstr ""
+
+#: ../share/extensions/gcodetools_lathe.inx.h:8
+msgid "Fine cut count:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_lathe.inx.h:9
+msgid "Fine cut width:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_lathe.inx.h:15
+msgid "Lathe"
+msgstr ""
+
+#: ../share/extensions/gcodetools_lathe.inx.h:16
+msgid "Lathe X axis remap:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_lathe.inx.h:17
+msgid "Lathe Z axis remap:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_lathe.inx.h:18
+msgid "Lathe width:"
+msgstr ""
+
+#: ../share/extensions/gcodetools_orientation_points.inx.h:7
+msgid "Orientation points"
+msgstr ""
+
#: ../share/extensions/render_barcode_datamatrix.inx.h:1
msgid "Barcode - Datamatrix"
msgstr ""
index f621837f7226c0e53369a4aca4661386b8765aa7..d77ac1fec79e651f416bcefd7b6e82fcc6c96104 100644 (file)
fractalize.py \
funcplot.py \
gears.py\
+ gcodetools.py\
generate_voronoi.py \
gimp_xcf.py \
grid_cartesian.py \
fractalize.inx \
funcplot.inx \
gears.inx\
+ gcodetools_all_in_one.inx\
+ gcodetools_area.inx\
+ gcodetools_check_for_updates.inx\
+ gcodetools_dxf_points.inx\
+ gcodetools_engraving.inx\
+ gcodetools_lathe.inx\
+ gcodetools_orientation_points.inx\
+ gcodetools_path_to_gcode.inx\
+ gcodetools_tools_library.inx\
generate_voronoi.inx \
gimp_xcf.inx \
grid_cartesian.inx \
diff --git a/share/extensions/gcodetools.py b/share/extensions/gcodetools.py
--- /dev/null
@@ -0,0 +1,4602 @@
+#!/usr/bin/env python
+"""
+Copyright (C) 2009 Nick Drobchenko, nick@cnc-club.ru
+based on gcode.py (C) 2007 hugomatic...
+based on addnodes.py (C) 2005,2007 Aaron Spike, aaron@ekips.org
+based on dots.py (C) 2005 Aaron Spike, aaron@ekips.org
+based on interp.py (C) 2005 Aaron Spike, aaron@ekips.org
+based on bezmisc.py (C) 2005 Aaron Spike, aaron@ekips.org
+based on cubicsuperpath.py (C) 2005 Aaron Spike, aaron@ekips.org
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""
+
+###
+### Gcodetools v 1.6.03
+###
+gcodetools_current_version = "1.6.03"
+
+import inkex, simplestyle, simplepath
+import cubicsuperpath, simpletransform, bezmisc
+
+import os
+import math
+import bezmisc
+import re
+import copy
+import sys
+import time
+import cmath
+import numpy
+import codecs
+import random
+import gettext
+_ = gettext.gettext
+
+
+### Check if inkex has errormsg (0.46 version doesnot have one.) Could be removed later.
+if "errormsg" not in dir(inkex):
+ inkex.errormsg = lambda msg: sys.stderr.write((unicode(msg) + "\n").encode("UTF-8"))
+
+
+def bezierslopeatt(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),t):
+ ax,ay,bx,by,cx,cy,x0,y0=bezmisc.bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
+ dx=3*ax*(t**2)+2*bx*t+cx
+ dy=3*ay*(t**2)+2*by*t+cy
+ if dx==dy==0 :
+ dx = 6*ax*t+2*bx
+ dy = 6*ay*t+2*by
+ if dx==dy==0 :
+ dx = 6*ax
+ dy = 6*ay
+ if dx==dy==0 :
+ print_("Slope error x = %s*t^3+%s*t^2+%s*t+%s, y = %s*t^3+%s*t^2+%s*t+%s, t = %s, dx==dy==0" % (ax,bx,cx,dx,ay,by,cy,dy,t))
+ print_(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
+ dx, dy = 1, 1
+
+ return dx,dy
+bezmisc.bezierslopeatt = bezierslopeatt
+
+
+def ireplace(self,old,new,count=0):
+ pattern = re.compile(re.escape(old),re.I)
+ return re.sub(pattern,new,self,count)
+
+################################################################################
+###
+### Styles and additional parameters
+###
+################################################################################
+
+math.pi2 = math.pi*2
+straight_tolerance = 0.0001
+straight_distance_tolerance = 0.0001
+engraving_tolerance = 0.0001
+loft_lengths_tolerance = 0.0000001
+options = {}
+defaults = {
+'header': """%
+(Header)
+(Generated by gcodetools from Inkscape.)
+(Using default header. To add your own header create file "header" in the output dir.)
+M3
+(Header end.)
+""",
+'footer': """
+(Footer)
+M5
+G00 X0.0000 Y0.0000
+M2
+(Using default footer. To add your own footer create file "footer" in the output dir.)
+(end)
+%"""
+}
+
+intersection_recursion_depth = 10
+intersection_tolerance = 0.00001
+
+styles = {
+ "loft_style" : {
+ 'main curve': simplestyle.formatStyle({ 'stroke': '#88f', 'fill': 'none', 'stroke-width':'1', 'marker-end':'url(#Arrow2Mend)' }),
+ },
+ "biarc_style" : {
+ 'biarc0': simplestyle.formatStyle({ 'stroke': '#88f', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
+ 'biarc1': simplestyle.formatStyle({ 'stroke': '#8f8', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
+ 'line': simplestyle.formatStyle({ 'stroke': '#f88', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
+ 'area': simplestyle.formatStyle({ 'stroke': '#777', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' }),
+ },
+ "biarc_style_dark" : {
+ 'biarc0': simplestyle.formatStyle({ 'stroke': '#33a', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
+ 'biarc1': simplestyle.formatStyle({ 'stroke': '#3a3', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
+ 'line': simplestyle.formatStyle({ 'stroke': '#a33', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
+ 'area': simplestyle.formatStyle({ 'stroke': '#222', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
+ },
+ "biarc_style_dark_area" : {
+ 'biarc0': simplestyle.formatStyle({ 'stroke': '#33a', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' }),
+ 'biarc1': simplestyle.formatStyle({ 'stroke': '#3a3', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' }),
+ 'line': simplestyle.formatStyle({ 'stroke': '#a33', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' }),
+ 'area': simplestyle.formatStyle({ 'stroke': '#222', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
+ },
+ "biarc_style_i" : {
+ 'biarc0': simplestyle.formatStyle({ 'stroke': '#880', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
+ 'biarc1': simplestyle.formatStyle({ 'stroke': '#808', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
+ 'line': simplestyle.formatStyle({ 'stroke': '#088', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
+ 'area': simplestyle.formatStyle({ 'stroke': '#999', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
+ },
+ "biarc_style_dark_i" : {
+ 'biarc0': simplestyle.formatStyle({ 'stroke': '#dd5', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
+ 'biarc1': simplestyle.formatStyle({ 'stroke': '#d5d', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
+ 'line': simplestyle.formatStyle({ 'stroke': '#5dd', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
+ 'area': simplestyle.formatStyle({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
+ },
+ "biarc_style_lathe_feed" : {
+ 'biarc0': simplestyle.formatStyle({ 'stroke': '#07f', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
+ 'biarc1': simplestyle.formatStyle({ 'stroke': '#0f7', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
+ 'line': simplestyle.formatStyle({ 'stroke': '#f44', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
+ 'area': simplestyle.formatStyle({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
+ },
+ "biarc_style_lathe_passing feed" : {
+ 'biarc0': simplestyle.formatStyle({ 'stroke': '#07f', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
+ 'biarc1': simplestyle.formatStyle({ 'stroke': '#0f7', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
+ 'line': simplestyle.formatStyle({ 'stroke': '#f44', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
+ 'area': simplestyle.formatStyle({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
+ },
+ "biarc_style_lathe_fine feed" : {
+ 'biarc0': simplestyle.formatStyle({ 'stroke': '#7f0', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
+ 'biarc1': simplestyle.formatStyle({ 'stroke': '#f70', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
+ 'line': simplestyle.formatStyle({ 'stroke': '#744', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
+ 'area': simplestyle.formatStyle({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
+ },
+ "area artefact": simplestyle.formatStyle({ 'stroke': '#ff0000', 'fill': '#ffff00', 'stroke-width':'1' }),
+ "area artefact arrow": simplestyle.formatStyle({ 'stroke': '#ff0000', 'fill': '#ffff00', 'stroke-width':'1' }),
+ "dxf_points": simplestyle.formatStyle({ "stroke": "#ff0000", "fill": "#ff0000"}),
+
+ }
+
+
+################################################################################
+### Cubic Super Path additional functions
+################################################################################
+
+def csp_simple_bound(csp):
+ minx,miny,maxx,maxy = None,None,None,None
+ for subpath in csp:
+ for sp in subpath :
+ for p in sp:
+ minx = min(minx,p[0]) if minx!=None else p[0]
+ miny = min(miny,p[1]) if miny!=None else p[1]
+ maxx = max(maxx,p[0]) if maxx!=None else p[0]
+ maxy = max(maxy,p[1]) if maxy!=None else p[1]
+ return minx,miny,maxx,maxy
+
+
+def csp_segment_to_bez(sp1,sp2) :
+ return sp1[1:]+sp2[:2]
+
+
+def bound_to_bound_distance(sp1,sp2,sp3,sp4) :
+ min_dist = 1e100
+ max_dist = 0
+ points1 = csp_segment_to_bez(sp1,sp2)
+ points2 = csp_segment_to_bez(sp3,sp4)
+ for i in range(4) :
+ for j in range(4) :
+ min_, max_ = line_to_line_min_max_distance_2(points1[i-1], points1[i], points2[j-1], points2[j])
+ min_dist = min(min_dist,min_)
+ max_dist = max(max_dist,max_)
+ print_("bound_to_bound", min_dist, max_dist)
+ return min_dist, max_dist
+
+def csp_to_point_distance(csp, p, dist_bounds = [0,1e100], tolerance=.01) :
+ min_dist = [1e100,0,0,0]
+ for j in range(len(csp)) :
+ for i in range(1,len(csp[j])) :
+ d = csp_seg_to_point_distance(csp[j][i-1],csp[j][i],p,sample_points = 5, tolerance = .01)
+ if d[0] < dist_bounds[0] :
+# draw_pointer( list(csp_at_t(subpath[dist[2]-1],subpath[dist[2]],dist[3]))
+# +list(csp_at_t(csp[dist[4]][dist[5]-1],csp[dist[4]][dist[5]],dist[6])),"red","line", comment = math.sqrt(dist[0]))
+ return [d[0],j,i,d[1]]
+ else :
+ if d[0] < min_dist[0] : min_dist = [d[0],j,i,d[1]]
+ return min_dist
+
+def csp_seg_to_point_distance(sp1,sp2,p,sample_points = 5, tolerance = .01) :
+ ax,ay,bx,by,cx,cy,dx,dy = csp_parameterize(sp1,sp2)
+ dx, dy = dx-p[0], dy-p[1]
+ if sample_points < 2 : sample_points = 2
+ d = min( [(p[0]-sp1[1][0])**2 + (p[1]-sp1[1][1])**2,0.], [(p[0]-sp2[1][0])**2 + (p[1]-sp2[1][1])**2,1.] )
+ for k in range(sample_points) :
+ t = float(k)/(sample_points-1)
+ i = 0
+ while i==0 or abs(f)>0.000001 and i<20 :
+ t2,t3 = t**2,t**3
+ f = (ax*t3+bx*t2+cx*t+dx)*(3*ax*t2+2*bx*t+cx) + (ay*t3+by*t2+cy*t+dy)*(3*ay*t2+2*by*t+cy)
+ df = (6*ax*t+2*bx)*(ax*t3+bx*t2+cx*t+dx) + (3*ax*t2+2*bx*t+cx)**2 + (6*ay*t+2*by)*(ay*t3+by*t2+cy*t+dy) + (3*ay*t2+2*by*t+cy)**2
+ if df!=0 :
+ t = t - f/df
+ else :
+ break
+ i += 1
+ if 0<=t<=1 :
+ p1 = csp_at_t(sp1,sp2,t)
+ d1 = (p1[0]-p[0])**2 + (p1[1]-p[1])**2
+ if d1 < d[0] :
+ d = [d1,t]
+ return d
+
+
+def csp_seg_to_csp_seg_distance(sp1,sp2,sp3,sp4, dist_bounds = [0,1e100], sample_points = 5, tolerance=.01) :
+ # check the ending points first
+ dist = csp_seg_to_point_distance(sp1,sp2,sp3[1],sample_points, tolerance)
+ dist += [0.]
+ if dist[0] <= dist_bounds[0] : return dist
+ d = csp_seg_to_point_distance(sp1,sp2,sp4[1],sample_points, tolerance)
+ if d[0]<dist[0] :
+ dist = d+[1.]
+ if dist[0] <= dist_bounds[0] : return dist
+ d = csp_seg_to_point_distance(sp3,sp4,sp1[1],sample_points, tolerance)
+ if d[0]<dist[0] :
+ dist = [d[0],0.,d[1]]
+ if dist[0] <= dist_bounds[0] : return dist
+ d = csp_seg_to_point_distance(sp3,sp4,sp2[1],sample_points, tolerance)
+ if d[0]<dist[0] :
+ dist = [d[0],1.,d[1]]
+ if dist[0] <= dist_bounds[0] : return dist
+ sample_points -= 2
+ if sample_points < 1 : sample_points = 1
+ ax1,ay1,bx1,by1,cx1,cy1,dx1,dy1 = csp_parameterize(sp1,sp2)
+ ax2,ay2,bx2,by2,cx2,cy2,dx2,dy2 = csp_parameterize(sp3,sp4)
+ # try to find closes points using Newtons method
+ for k in range(sample_points) :
+ for j in range(sample_points) :
+ t1,t2 = float(k+1)/(sample_points+1), float(j)/(sample_points+1)
+ t12, t13, t22, t23 = t1*t1, t1*t1*t1, t2*t2, t2*t2*t2
+ i = 0
+ F1, F2, F = [0,0], [[0,0],[0,0]], 1e100
+ x,y = ax1*t13+bx1*t12+cx1*t1+dx1 - (ax2*t23+bx2*t22+cx2*t2+dx2), ay1*t13+by1*t12+cy1*t1+dy1 - (ay2*t23+by2*t22+cy2*t2+dy2)
+ while i<2 or abs(F-Flast)>tolerance and i<30 :
+ #draw_pointer(csp_at_t(sp1,sp2,t1))
+ f1x = 3*ax1*t12+2*bx1*t1+cx1
+ f1y = 3*ay1*t12+2*by1*t1+cy1
+ f2x = 3*ax2*t22+2*bx2*t2+cx2
+ f2y = 3*ay2*t22+2*by2*t2+cy2
+ F1[0] = 2*f1x*x + 2*f1y*y
+ F1[1] = -2*f2x*x - 2*f2y*y
+ F2[0][0] = 2*(6*ax1*t1+2*bx1)*x + 2*f1x*f1x + 2*(6*ay1*t1+2*by1)*y +2*f1y*f1y
+ F2[0][1] = -2*f1x*f2x - 2*f1y*f2y
+ F2[1][0] = -2*f2x*f1x - 2*f2y*f1y
+ F2[1][1] = -2*(6*ax2*t2+2*bx2)*x + 2*f2x*f2x - 2*(6*ay2*t2+2*by2)*y + 2*f2y*f2y
+ F2 = inv_2x2(F2)
+ if F2!=None :
+ t1 -= ( F2[0][0]*F1[0] + F2[0][1]*F1[1] )
+ t2 -= ( F2[1][0]*F1[0] + F2[1][1]*F1[1] )
+ t12, t13, t22, t23 = t1*t1, t1*t1*t1, t2*t2, t2*t2*t2
+ x,y = ax1*t13+bx1*t12+cx1*t1+dx1 - (ax2*t23+bx2*t22+cx2*t2+dx2), ay1*t13+by1*t12+cy1*t1+dy1 - (ay2*t23+by2*t22+cy2*t2+dy2)
+ Flast = F
+ F = x*x+y*y
+ else :
+ break
+ i += 1
+ if F < dist[0] and 0<=t1<=1 and 0<=t2<=1:
+ dist = [F,t1,t2]
+ if dist[0] <= dist_bounds[0] :
+ return dist
+ return dist
+
+
+def csp_to_csp_distance(csp1,csp2, dist_bounds = [0,1e100], tolerance=.01) :
+ dist = [1e100,0,0,0,0,0,0]
+ for i1 in range(len(csp1)) :
+ for j1 in range(1,len(csp1[i1])) :
+ for i2 in range(len(csp2)) :
+ for j2 in range(1,len(csp2[i2])) :
+ d = csp_seg_bound_to_csp_seg_bound_max_min_distance(csp1[i1][j1-1],csp1[i1][j1],csp2[i2][j2-1],csp2[i2][j2])
+ if d[0] >= dist_bounds[1] : continue
+ if d[1] < dist_bounds[0] : return [d[1],i1,j1,1,i2,j2,1]
+ d = csp_seg_to_csp_seg_distance(csp1[i1][j1-1],csp1[i1][j1],csp2[i2][j2-1],csp2[i2][j2], dist_bounds, tolerance=tolerance)
+ if d[0] < dist[0] :
+ dist = [d[0], i1,j1,d[1], i2,j2,d[2]]
+ if dist[0] <= dist_bounds[0] :
+ return dist
+ if dist[0] >= dist_bounds[1] :
+ return dist
+ return dist
+# draw_pointer( list(csp_at_t(csp1[dist[1]][dist[2]-1],csp1[dist[1]][dist[2]],dist[3]))
+# + list(csp_at_t(csp2[dist[4]][dist[5]-1],csp2[dist[4]][dist[5]],dist[6])), "#507","line")
+
+
+def csp_split(sp1,sp2,t=.5) :
+ [x1,y1],[x2,y2],[x3,y3],[x4,y4] = sp1[1], sp1[2], sp2[0], sp2[1]
+ x12 = x1+(x2-x1)*t
+ y12 = y1+(y2-y1)*t
+ x23 = x2+(x3-x2)*t
+ y23 = y2+(y3-y2)*t
+ x34 = x3+(x4-x3)*t
+ y34 = y3+(y4-y3)*t
+ x1223 = x12+(x23-x12)*t
+ y1223 = y12+(y23-y12)*t
+ x2334 = x23+(x34-x23)*t
+ y2334 = y23+(y34-y23)*t
+ x = x1223+(x2334-x1223)*t
+ y = y1223+(y2334-y1223)*t
+ return [sp1[0],sp1[1],[x12,y12]], [[x1223,y1223],[x,y],[x2334,y2334]], [[x34,y34],sp2[1],sp2[2]]
+
+def csp_true_bounds(csp) :
+ # Finds minx,miny,maxx,maxy of the csp and return their (x,y,i,j,t)
+ minx = [float("inf"), 0, 0, 0]
+ maxx = [float("-inf"), 0, 0, 0]
+ miny = [float("inf"), 0, 0, 0]
+ maxy = [float("-inf"), 0, 0, 0]
+ for i in range(len(csp)):
+ for j in range(1,len(csp[i])):
+ ax,ay,bx,by,cx,cy,x0,y0 = bezmisc.bezierparameterize((csp[i][j-1][1],csp[i][j-1][2],csp[i][j][0],csp[i][j][1]))
+ roots = cubic_solver(0, 3*ax, 2*bx, cx) + [0,1]
+ for root in roots :
+ if type(root) is complex and abs(root.imag)<1e-10:
+ root = root.real
+ if type(root) is not complex and 0<=root<=1:
+ y = ay*(root**3)+by*(root**2)+cy*root+y0
+ x = ax*(root**3)+bx*(root**2)+cx*root+x0
+ maxx = max([x,y,i,j,root],maxx)
+ minx = min([x,y,i,j,root],minx)
+
+ roots = cubic_solver(0, 3*ay, 2*by, cy) + [0,1]
+ for root in roots :
+ if type(root) is complex and root.imag==0:
+ root = root.real
+ if type(root) is not complex and 0<=root<=1:
+ y = ay*(root**3)+by*(root**2)+cy*root+y0
+ x = ax*(root**3)+bx*(root**2)+cx*root+x0
+ maxy = max([y,x,i,j,root],maxy)
+ miny = min([y,x,i,j,root],miny)
+ maxy[0],maxy[1] = maxy[1],maxy[0]
+ miny[0],miny[1] = miny[1],miny[0]
+
+ return minx,miny,maxx,maxy
+
+
+############################################################################
+### csp_segments_intersection(sp1,sp2,sp3,sp4)
+###
+### Returns array containig all intersections between two segmets of cubic
+### super path. Results are [ta,tb], or [ta0, ta1, tb0, tb1, "Overlap"]
+### where ta, tb are values of t for the intersection point.
+############################################################################
+def csp_segments_intersection(sp1,sp2,sp3,sp4) :
+ a, b = csp_segment_to_bez(sp1,sp2), csp_segment_to_bez(sp3,sp4)
+
+ def polish_intersection(a,b,ta,tb, tolerance = intersection_tolerance) :
+ ax,ay,bx,by,cx,cy,dx,dy = bezmisc.bezierparameterize(a)
+ ax1,ay1,bx1,by1,cx1,cy1,dx1,dy1 = bezmisc.bezierparameterize(b)
+ i = 0
+ F, F1 = [.0,.0], [[.0,.0],[.0,.0]]
+ while i==0 or (abs(F[0])**2+abs(F[1])**2 > tolerance and i<10):
+ ta3, ta2, tb3, tb2 = ta**3, ta**2, tb**3, tb**2
+ F[0] = ax*ta3+bx*ta2+cx*ta+dx-ax1*tb3-bx1*tb2-cx1*tb-dx1
+ F[1] = ay*ta3+by*ta2+cy*ta+dy-ay1*tb3-by1*tb2-cy1*tb-dy1
+ F1[0][0] = 3*ax *ta2 + 2*bx *ta + cx
+ F1[0][1] = -3*ax1*tb2 - 2*bx1*tb - cx1
+ F1[1][0] = 3*ay *ta2 + 2*by *ta + cy
+ F1[1][1] = -3*ay1*tb2 - 2*by1*tb - cy1
+ det = F1[0][0]*F1[1][1] - F1[0][1]*F1[1][0]
+ if det!=0 :
+ F1 = [ [ F1[1][1]/det, -F1[0][1]/det], [-F1[1][0]/det, F1[0][0]/det] ]
+ ta = ta - ( F1[0][0]*F[0] + F1[0][1]*F[1] )
+ tb = tb - ( F1[1][0]*F[0] + F1[1][1]*F[1] )
+ else: break
+ i += 1
+
+ return ta, tb
+
+
+ def recursion(a,b, ta0,ta1,tb0,tb1, depth_a,depth_b) :
+ global bezier_intersection_recursive_result
+ if a==b :
+ bezier_intersection_recursive_result += [[ta0,tb0,ta1,tb1,"Overlap"]]
+ return
+ tam, tbm = (ta0+ta1)/2, (tb0+tb1)/2
+ if depth_a>0 and depth_b>0 :
+ a1,a2 = bez_split(a,0.5)
+ b1,b2 = bez_split(b,0.5)
+ if bez_bounds_intersect(a1,b1) : recursion(a1,b1, ta0,tam,tb0,tbm, depth_a-1,depth_b-1)
+ if bez_bounds_intersect(a2,b1) : recursion(a2,b1, tam,ta1,tb0,tbm, depth_a-1,depth_b-1)
+ if bez_bounds_intersect(a1,b2) : recursion(a1,b2, ta0,tam,tbm,tb1, depth_a-1,depth_b-1)
+ if bez_bounds_intersect(a2,b2) : recursion(a2,b2, tam,ta1,tbm,tb1, depth_a-1,depth_b-1)
+ elif depth_a>0 :
+ a1,a2 = bez_split(a,0.5)
+ if bez_bounds_intersect(a1,b) : recursion(a1,b, ta0,tam,tb0,tb1, depth_a-1,depth_b)
+ if bez_bounds_intersect(a2,b) : recursion(a2,b, tam,ta1,tb0,tb1, depth_a-1,depth_b)
+ elif depth_b>0 :
+ b1,b2 = bez_split(b,0.5)
+ if bez_bounds_intersect(a,b1) : recursion(a,b1, ta0,ta1,tb0,tbm, depth_a,depth_b-1)
+ if bez_bounds_intersect(a,b2) : recursion(a,b2, ta0,ta1,tbm,tb1, depth_a,depth_b-1)
+ else : # Both segments have been subdevided enougth. Let's get some intersections :).
+ intersection, t1, t2 = straight_segments_intersection([a[0]]+[a[3]],[b[0]]+[b[3]])
+ if intersection :
+ if intersection == "Overlap" :
+ t1 = ( max(0,min(1,t1[0]))+max(0,min(1,t1[1])) )/2
+ t2 = ( max(0,min(1,t2[0]))+max(0,min(1,t2[1])) )/2
+ bezier_intersection_recursive_result += [[ta0+t1*(ta1-ta0),tb0+t2*(tb1-tb0)]]
+
+ global bezier_intersection_recursive_result
+ bezier_intersection_recursive_result = []
+ recursion(a,b,0.,1.,0.,1.,intersection_recursion_depth,intersection_recursion_depth)
+ intersections = bezier_intersection_recursive_result
+ for i in range(len(intersections)) :
+ if len(intersections[i])<5 or intersections[i][4] != "Overlap" :
+ intersections[i] = polish_intersection(a,b,intersections[i][0],intersections[i][1])
+ return intersections
+
+
+def csp_segments_true_intersection(sp1,sp2,sp3,sp4) :
+ intersections = csp_segments_intersection(sp1,sp2,sp3,sp4)
+ res = []
+ for intersection in intersections :
+ if (
+ (len(intersection)==5 and intersection[4] == "Overlap" and (0<=intersection[0]<=1 or 0<=intersection[1]<=1) and (0<=intersection[2]<=1 or 0<=intersection[3]<=1) )
+ or ( 0<=intersection[0]<=1 and 0<=intersection[1]<=1 )
+ ) :
+ res += [intersection]
+ return res
+
+
+def csp_get_t_at_curvature(sp1,sp2,c, sample_points = 16):
+ # returns a list containning [t1,t2,t3,...,tn], 0<=ti<=1...
+ if sample_points < 2 : sample_points = 2
+ tolerance = .0000000001
+ res = []
+ ax,ay,bx,by,cx,cy,dx,dy = csp_parameterize(sp1,sp2)
+ for k in range(sample_points) :
+ t = float(k)/(sample_points-1)
+ i, F = 0, 1e100
+ while i<2 or abs(F)>tolerance and i<17 :
+ try : # some numerical calculation could exceed the limits
+ t2 = t*t
+ #slopes...
+ f1x = 3*ax*t2+2*bx*t+cx
+ f1y = 3*ay*t2+2*by*t+cy
+ f2x = 6*ax*t+2*bx
+ f2y = 6*ay*t+2*by
+ f3x = 6*ax
+ f3y = 6*ay
+ d = (f1x**2+f1y**2)**1.5
+ F1 = (
+ ( (f1x*f3y-f3x*f1y)*d - (f1x*f2y-f2x*f1y)*3.*(f2x*f1x+f2y*f1y)*((f1x**2+f1y**2)**.5) ) /
+ ((f1x**2+f1y**2)**3)
+ )
+ F = (f1x*f2y-f1y*f2x)/d - c
+ t -= F/F1
+ except:
+ break
+ i += 1
+ if 0<=t<=1 and F<=tolerance:
+ if len(res) == 0 :
+ res.append(t)
+ for i in res :
+ if abs(t-i)<=0.001 :
+ break
+ if not abs(t-i)<=0.001 :
+ res.append(t)
+ return res
+
+
+def csp_max_curvature(sp1,sp2):
+ ax,ay,bx,by,cx,cy,dx,dy = csp_parameterize(sp1,sp2)
+ tolerance = .0001
+ F = 0.
+ i = 0
+ while i<2 or F-Flast<tolerance and i<10 :
+ t = .5
+ f1x = 3*ax*t**2 + 2*bx*t + cx
+ f1y = 3*ay*t**2 + 2*by*t + cy
+ f2x = 6*ax*t + 2*bx
+ f2y = 6*ay*t + 2*by
+ f3x = 6*ax
+ f3y = 6*ay
+ d = pow(f1x**2+f1y**2,1.5)
+ if d != 0 :
+ Flast = F
+ F = (f1x*f2y-f1y*f2x)/d
+ F1 = (
+ ( d*(f1x*f3y-f3x*f1y) - (f1x*f2y-f2x*f1y)*3.*(f2x*f1x+f2y*f1y)*pow(f1x**2+f1y**2,.5) ) /
+ (f1x**2+f1y**2)**3
+ )
+ i+=1
+ if F1!=0:
+ t -= F/F1
+ else:
+ break
+ else: break
+ return t
+
+
+def csp_curvature_at_t(sp1,sp2,t, depth = 3) :
+ ax,ay,bx,by,cx,cy,dx,dy = bezmisc.bezierparameterize(csp_segment_to_bez(sp1,sp2))
+
+ #curvature = (x'y''-y'x'') / (x'^2+y'^2)^1.5
+
+ f1x = 3*ax*t**2 + 2*bx*t + cx
+ f1y = 3*ay*t**2 + 2*by*t + cy
+ f2x = 6*ax*t + 2*bx
+ f2y = 6*ay*t + 2*by
+ d = (f1x**2+f1y**2)**1.5
+ if d != 0 :
+ return (f1x*f2y-f1y*f2x)/d
+ else :
+ t1 = f1x*f2y-f1y*f2x
+ if t1 > 0 : return 1e100
+ if t1 < 0 : return -1e100
+ # Use the Lapitals rule to solve 0/0 problem for 2 times...
+ t1 = 2*(bx*ay-ax*by)*t+(ay*cx-ax*cy)
+ if t1 > 0 : return 1e100
+ if t1 < 0 : return -1e100
+ t1 = bx*ay-ax*by
+ if t1 > 0 : return 1e100
+ if t1 < 0 : return -1e100
+ if depth>0 :
+ # little hack ;^) hope it wont influence anything...
+ return csp_curvature_at_t(sp1,sp2,t*1.004, depth-1)
+ return 1e100
+
+
+def csp_curvature_radius_at_t(sp1,sp2,t) :
+ c = csp_curvature_at_t(sp1,sp2,t)
+ if c == 0 : return 1e100
+ else: return 1/c
+
+
+def csp_special_points(sp1,sp2) :
+ # special points = curvature == 0
+ ax,ay,bx,by,cx,cy,dx,dy = bezmisc.bezierparameterize((sp1[1],sp1[2],sp2[0],sp2[1]))
+ a = 3*ax*by-3*ay*bx
+ b = 3*ax*cy-3*cx*ay
+ c = bx*cy-cx*by
+ roots = cubic_solver(0, a, b, c)
+ res = []
+ for i in roots :
+ if type(i) is complex and i.imag==0:
+ i = i.real
+ if type(i) is not complex and 0<=i<=1:
+ res.append(i)
+ return res
+
+
+def csp_subpath_ccw(subpath):
+ # Remove all zerro length segments
+ s = 0
+ #subpath = subpath[:]
+ if (P(subpath[-1][1])-P(subpath[0][1])).l2() > 1e-10 :
+ subpath[-1][2] = subpath[-1][1]
+ subpath[0][0] = subpath[0][1]
+ subpath += [ [subpath[0][1],subpath[0][1],subpath[0][1]] ]
+ pl = subpath[-1][2]
+ for sp1 in subpath:
+ for p in sp1 :
+ s += (p[0]-pl[0])*(p[1]+pl[1])
+ pl = p
+ return s<0
+
+
+def csp_at_t(sp1,sp2,t):
+ ax,bx,cx,dx = sp1[1][0], sp1[2][0], sp2[0][0], sp2[1][0]
+ ay,by,cy,dy = sp1[1][1], sp1[2][1], sp2[0][1], sp2[1][1]
+
+ x1, y1 = ax+(bx-ax)*t, ay+(by-ay)*t
+ x2, y2 = bx+(cx-bx)*t, by+(cy-by)*t
+ x3, y3 = cx+(dx-cx)*t, cy+(dy-cy)*t
+
+ x4,y4 = x1+(x2-x1)*t, y1+(y2-y1)*t
+ x5,y5 = x2+(x3-x2)*t, y2+(y3-y2)*t
+
+ x,y = x4+(x5-x4)*t, y4+(y5-y4)*t
+ return [x,y]
+
+
+def csp_splitatlength(sp1, sp2, l = 0.5, tolerance = 0.01):
+ bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
+ t = bezmisc.beziertatlength(bez, l, tolerance)
+ return csp_split(sp1, sp2, t)
+
+
+def cspseglength(sp1,sp2, tolerance = 0.001):
+ bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
+ return bezmisc.bezierlength(bez, tolerance)
+
+
+def csplength(csp):
+ total = 0
+ lengths = []
+ for sp in csp:
+ for i in xrange(1,len(sp)):
+ l = cspseglength(sp[i-1],sp[i])
+ lengths.append(l)
+ total += l
+ return lengths, total
+
+
+def csp_segments(csp):
+ l, seg = 0, [0]
+ for sp in csp:
+ for i in xrange(1,len(sp)):
+ l += cspseglength(sp[i-1],sp[i])
+ seg += [ l ]
+
+ if l>0 :
+ seg = [seg[i]/l for i in xrange(len(seg))]
+ return seg,l
+
+
+def rebuild_csp (csp, segs, s=None):
+ # rebuild_csp() adds to csp control points making it's segments looks like segs
+ if s==None : s, l = csp_segments(csp)
+
+ if len(s)>len(segs) : return None
+ segs = segs[:]
+ segs.sort()
+ for i in xrange(len(s)):
+ d = None
+ for j in xrange(len(segs)):
+ d = min( [abs(s[i]-segs[j]),j], d) if d!=None else [abs(s[i]-segs[j]),j]
+ del segs[d[1]]
+ for i in xrange(len(segs)):
+ for j in xrange(0,len(s)):
+ if segs[i]<s[j] : break
+ if s[j]-s[j-1] != 0 :
+ t = (segs[i] - s[j-1])/(s[j]-s[j-1])
+ sp1,sp2,sp3 = csp_split(csp[j-1],csp[j], t)
+ csp = csp[:j-1] + [sp1,sp2,sp3] + csp[j+1:]
+ s = s[:j] + [ s[j-1]*(1-t)+s[j]*t ] + s[j:]
+ return csp, s
+
+
+def csp_slope(sp1,sp2,t):
+ bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
+ return bezmisc.bezierslopeatt(bez,t)
+
+
+def csp_line_intersection(l1,l2,sp1,sp2):
+ dd=l1[0]
+ cc=l2[0]-l1[0]
+ bb=l1[1]
+ aa=l2[1]-l1[1]
+ if aa==cc==0 : return []
+ if aa:
+ coef1=cc/aa
+ coef2=1
+ else:
+ coef1=1
+ coef2=aa/cc
+ bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
+ ax,ay,bx,by,cx,cy,x0,y0=bezmisc.bezierparameterize(bez)
+ a=coef1*ay-coef2*ax
+ b=coef1*by-coef2*bx
+ c=coef1*cy-coef2*cx
+ d=coef1*(y0-bb)-coef2*(x0-dd)
+ roots = cubic_solver(a,b,c,d)
+ retval = []
+ for i in roots :
+ if type(i) is complex and abs(i.imag)<1e-7:
+ i = i.real
+ if type(i) is not complex and -1e-10<=i<=1.+1e-10:
+ retval.append(i)
+ return retval
+
+
+def csp_split_by_two_points(sp1,sp2,t1,t2) :
+ if t1>t2 : t1, t2 = t2, t1
+ if t1 == t2 :
+ sp1,sp2,sp3 = csp_split(sp1,sp2,t)
+ return [sp1,sp2,sp2,sp3]
+ elif t1 <= 1e-10 and t2 >= 1.-1e-10 :
+ return [sp1,sp1,sp2,sp2]
+ elif t1 <= 1e-10:
+ sp1,sp2,sp3 = csp_split(sp1,sp2,t2)
+ return [sp1,sp1,sp2,sp3]
+ elif t2 >= 1.-1e-10 :
+ sp1,sp2,sp3 = csp_split(sp1,sp2,t1)
+ return [sp1,sp2,sp3,sp3]
+ else:
+ sp1,sp2,sp3 = csp_split(sp1,sp2,t1)
+ sp2,sp3,sp4 = csp_split(sp2,sp3,(t2-t1)/(1-t1) )
+ return [sp1,sp2,sp3,sp4]
+
+
+def csp_subpath_split_by_points(subpath, points) :
+ # points are [[i,t]...] where i-segment's number
+ points.sort()
+ points = [[1,0.]] + points + [[len(subpath)-1,1.]]
+ parts = []
+ for int1,int2 in zip(points,points[1:]) :
+ if int1==int2 :
+ continue
+ if int1[1] == 1. :
+ int1[0] += 1
+ int1[1] = 0.
+ if int1==int2 :
+ continue
+ if int2[1] == 0. :
+ int2[0] -= 1
+ int2[1] = 1.
+ if int1[0] == 0 and int2[0]==len(subpath)-1:# and small(int1[1]) and small(int2[1]-1) :
+ continue
+ if int1[0]==int2[0] : # same segment
+ sp = csp_split_by_two_points(subpath[int1[0]-1],subpath[int1[0]],int1[1], int2[1])
+ if sp[1]!=sp[2] :
+ parts += [ [sp[1],sp[2]] ]
+ else :
+ sp5,sp1,sp2 = csp_split(subpath[int1[0]-1],subpath[int1[0]],int1[1])
+ sp3,sp4,sp5 = csp_split(subpath[int2[0]-1],subpath[int2[0]],int2[1])
+ if int1[0]==int2[0]-1 :
+ parts += [ [sp1, [sp2[0],sp2[1],sp3[2]], sp4] ]
+ else :
+ parts += [ [sp1,sp2]+subpath[int1[0]+1:int2[0]-1]+[sp3,sp4] ]
+ return parts
+
+
+def csp_from_arc(start, end, center, r, slope_st) :
+ # Creates csp that approximise specified arc
+ r = abs(r)
+ alpha = (atan2(end[0]-center[0],end[1]-center[1]) - atan2(start[0]-center[0],start[1]-center[1])) % math.pi2
+
+ sectors = int(abs(alpha)*2/math.pi)+1
+ alpha_start = atan2(start[0]-center[0],start[1]-center[1])
+ cos_,sin_ = math.cos(alpha_start), math.sin(alpha_start)
+ k = (4.*math.tan(alpha/sectors/4.)/3.)
+ if dot(slope_st , [- sin_*k*r, cos_*k*r]) < 0 :
+ if alpha>0 : alpha -= math.pi2
+ else: alpha += math.pi2
+ if abs(alpha*r)<0.001 :
+ return []
+
+ sectors = int(abs(alpha)*2/math.pi)+1
+ k = (4.*math.tan(alpha/sectors/4.)/3.)
+ result = []
+ for i in range(sectors+1) :
+ cos_,sin_ = math.cos(alpha_start + alpha*i/sectors), math.sin(alpha_start + alpha*i/sectors)
+ sp = [ [], [center[0] + cos_*r, center[1] + sin_*r], [] ]
+ sp[0] = [sp[1][0] + sin_*k*r, sp[1][1] - cos_*k*r ]
+ sp[2] = [sp[1][0] - sin_*k*r, sp[1][1] + cos_*k*r ]
+ result += [sp]
+ result[0][0] = result[0][1][:]
+ result[-1][2] = result[-1][1]
+
+ return result
+
+
+def point_to_arc_distance(p, arc):
+ ### Distance calculattion from point to arc
+ P0,P2,c,a = arc
+ dist = None
+ p = P(p)
+ r = (P0-c).mag()
+ if r>0 :
+ i = c + (p-c).unit()*r
+ alpha = ((i-c).angle() - (P0-c).angle())
+ if a*alpha<0:
+ if alpha>0: alpha = alpha-math.pi2
+ else: alpha = math.pi2+alpha
+ if between(alpha,0,a) or min(abs(alpha),abs(alpha-a))<straight_tolerance :
+ return (p-i).mag(), [i.x, i.y]
+ else :
+ d1, d2 = (p-P0).mag(), (p-P2).mag()
+ if d1<d2 :
+ return (d1, [P0.x,P0.y])
+ else :
+ return (d2, [P2.x,P2.y])
+
+
+def csp_to_arc_distance(sp1,sp2, arc1, arc2, tolerance = 0.01 ): # arc = [start,end,center,alpha]
+ n, i = 10, 0
+ d, d1, dl = (0,(0,0)), (0,(0,0)), 0
+ while i<1 or (abs(d1[0]-dl[0])>tolerance and i<4):
+ i += 1
+ dl = d1*1
+ for j in range(n+1):
+ t = float(j)/n
+ p = csp_at_t(sp1,sp2,t)
+ d = min(point_to_arc_distance(p,arc1), point_to_arc_distance(p,arc2))
+ d1 = max(d1,d)
+ n=n*2
+ return d1[0]
+
+
+def csp_simple_bound_to_point_distance(p, csp):
+ minx,miny,maxx,maxy = None,None,None,None
+ for subpath in csp:
+ for sp in subpath:
+ for p_ in sp:
+ minx = min(minx,p_[0]) if minx!=None else p_[0]
+ miny = min(miny,p_[1]) if miny!=None else p_[1]
+ maxx = max(maxx,p_[0]) if maxx!=None else p_[0]
+ maxy = max(maxy,p_[1]) if maxy!=None else p_[1]
+ return math.sqrt(max(minx-p[0],p[0]-maxx,0)**2+max(miny-p[1],p[1]-maxy,0)**2)
+
+
+def csp_point_inside_bound(sp1, sp2, p):
+ bez = [sp1[1],sp1[2],sp2[0],sp2[1]]
+ x,y = p
+ c = 0
+ for i in range(4):
+ [x0,y0], [x1,y1] = bez[i-1], bez[i]
+ if x0-x1!=0 and (y-y0)*(x1-x0)>=(x-x0)*(y1-y0) and x>min(x0,x1) and x<=max(x0,x1) :
+ c +=1
+ return c%2==0
+
+
+def csp_bound_to_point_distance(sp1, sp2, p):
+ if csp_point_inside_bound(sp1, sp2, p) :
+ return 0.
+ bez = csp_segment_to_bez(sp1,sp2)
+ min_dist = 1e100
+ for i in range(0,4):
+ d = point_to_line_segment_distance_2(p, bez[i-1],bez[i])
+ if d <= min_dist : min_dist = d
+ return min_dist
+
+
+def line_line_intersect(p1,p2,p3,p4) : # Return only true intersection.
+ if (p1[0]==p2[0] and p1[1]==p2[1]) or (p3[0]==p4[0] and p3[1]==p4[1]) : return False
+ x = (p2[0]-p1[0])*(p4[1]-p3[1]) - (p2[1]-p1[1])*(p4[0]-p3[0])
+ if x==0 : # Lines are parallel
+ if (p3[0]-p1[0])*(p2[1]-p1[1]) == (p3[1]-p1[1])*(p2[0]-p1[0]) :
+ if p3[0]!=p4[0] :
+ t11 = (p1[0]-p3[0])/(p4[0]-p3[0])
+ t12 = (p2[0]-p3[0])/(p4[0]-p3[0])
+ t21 = (p3[0]-p1[0])/(p2[0]-p1[0])
+ t22 = (p4[0]-p1[0])/(p2[0]-p1[0])
+ else:
+ t11 = (p1[1]-p3[1])/(p4[1]-p3[1])
+ t12 = (p2[1]-p3[1])/(p4[1]-p3[1])
+ t21 = (p3[1]-p1[1])/(p2[1]-p1[1])
+ t22 = (p4[1]-p1[1])/(p2[1]-p1[1])
+ return ("Overlap" if (0<=t11<=1 or 0<=t12<=1) and (0<=t21<=1 or 0<=t22<=1) else False)
+ else: return False
+ else :
+ return (
+ 0<=((p4[0]-p3[0])*(p1[1]-p3[1]) - (p4[1]-p3[1])*(p1[0]-p3[0]))/x<=1 and
+ 0<=((p2[0]-p1[0])*(p1[1]-p3[1]) - (p2[1]-p1[1])*(p1[0]-p3[0]))/x<=1 )
+
+
+def line_line_intersection_points(p1,p2,p3,p4) : # Return only points [ (x,y) ]
+ if (p1[0]==p2[0] and p1[1]==p2[1]) or (p3[0]==p4[0] and p3[1]==p4[1]) : return []
+ x = (p2[0]-p1[0])*(p4[1]-p3[1]) - (p2[1]-p1[1])*(p4[0]-p3[0])
+ if x==0 : # Lines are parallel
+ if (p3[0]-p1[0])*(p2[1]-p1[1]) == (p3[1]-p1[1])*(p2[0]-p1[0]) :
+ if p3[0]!=p4[0] :
+ t11 = (p1[0]-p3[0])/(p4[0]-p3[0])
+ t12 = (p2[0]-p3[0])/(p4[0]-p3[0])
+ t21 = (p3[0]-p1[0])/(p2[0]-p1[0])
+ t22 = (p4[0]-p1[0])/(p2[0]-p1[0])
+ else:
+ t11 = (p1[1]-p3[1])/(p4[1]-p3[1])
+ t12 = (p2[1]-p3[1])/(p4[1]-p3[1])
+ t21 = (p3[1]-p1[1])/(p2[1]-p1[1])
+ t22 = (p4[1]-p1[1])/(p2[1]-p1[1])
+ res = []
+ if (0<=t11<=1 or 0<=t12<=1) and (0<=t21<=1 or 0<=t22<=1) :
+ if 0<=t11<=1 : res += [p1]
+ if 0<=t12<=1 : res += [p2]
+ if 0<=t21<=1 : res += [p3]
+ if 0<=t22<=1 : res += [p4]
+ return res
+ else: return []
+ else :
+ t1 = ((p4[0]-p3[0])*(p1[1]-p3[1]) - (p4[1]-p3[1])*(p1[0]-p3[0]))/x
+ t2 = ((p2[0]-p1[0])*(p1[1]-p3[1]) - (p2[1]-p1[1])*(p1[0]-p3[0]))/x
+ if 0<=t1<=1 and 0<=t2<=1 : return [ [p1[0]*(1-t1)+p2[0]*t1, p1[1]*(1-t1)+p2[1]*t1] ]
+ else : return []
+
+
+def point_to_point_d2(a,b):
+ return (a[0]-b[0])**2 + (a[1]-b[1])**2
+
+
+def point_to_point_d(a,b):
+ return math.sqrt((a[0]-b[0])**2 + (a[1]-b[1])**2)
+
+
+def point_to_line_segment_distance_2(p1, p2,p3) :
+ # p1 - point, p2,p3 - line segment
+ #draw_pointer(p1)
+ w0 = [p1[0]-p2[0], p1[1]-p2[1]]
+ v = [p3[0]-p2[0], p3[1]-p2[1]]
+ c1 = w0[0]*v[0] + w0[1]*v[1]
+ if c1 <= 0 :
+ return w0[0]*w0[0]+w0[1]*w0[1]
+ c2 = v[0]*v[0] + v[1]*v[1]
+ if c2 <= c1 :
+ return (p1[0]-p3[0])**2 + (p1[1]-p3[1])**2
+ return (p1[0]- p2[0]-v[0]*c1/c2)**2 + (p1[1]- p2[1]-v[1]*c1/c2)
+
+
+def line_to_line_distance_2(p1,p2,p3,p4):
+ if line_line_intersect(p1,p2,p3,p4) : return 0
+ return min(
+ point_to_line_segment_distance_2(p1,p3,p4),
+ point_to_line_segment_distance_2(p2,p3,p4),
+ point_to_line_segment_distance_2(p3,p1,p2),
+ point_to_line_segment_distance_2(p4,p1,p2))
+
+
+def csp_seg_bound_to_csp_seg_bound_max_min_distance(sp1,sp2,sp3,sp4) :
+ bez1 = csp_segment_to_bez(sp1,sp2)
+ bez2 = csp_segment_to_bez(sp3,sp4)
+ min_dist = 1e100
+ max_dist = 0.
+ for i in range(4) :
+ if csp_point_inside_bound(sp1, sp2, bez2[i]) or csp_point_inside_bound(sp3, sp4, bez1[i]) :
+ min_dist = 0.
+ break
+ for i in range(4) :
+ for j in range(4) :
+ d = line_to_line_distance_2(bez1[i-1],bez1[i],bez2[j-1],bez2[j])
+ if d < min_dist : min_dist = d
+ d = (bez2[j][0]-bez1[i][0])**2 + (bez2[j][1]-bez1[i][1])**2
+ if max_dist < d : max_dist = d
+ return min_dist, max_dist
+
+
+def csp_reverse(csp) :
+ for i in range(len(csp)) :
+ n = []
+ for j in csp[i] :
+ n = [ [j[2][:],j[1][:],j[0][:]] ] + n
+ csp[i] = n[:]
+ return csp
+
+
+def csp_normalized_slope(sp1,sp2,t) :
+ ax,ay,bx,by,cx,cy,dx,dy=bezmisc.bezierparameterize((sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:]))
+ if sp1[1]==sp2[1]==sp1[2]==sp2[0] : return [1.,0.]
+ f1x = 3*ax*t*t+2*bx*t+cx
+ f1y = 3*ay*t*t+2*by*t+cy
+ if abs(f1x*f1x+f1y*f1y) > 1e-20 :
+ l = math.sqrt(f1x*f1x+f1y*f1y)
+ return [f1x/l, f1y/l]
+
+ if t == 0 :
+ f1x = sp2[0][0]-sp1[1][0]
+ f1y = sp2[0][1]-sp1[1][1]
+ if abs(f1x*f1x+f1y*f1y) > 1e-20 :
+ l = math.sqrt(f1x*f1x+f1y*f1y)
+ return [f1x/l, f1y/l]
+ else :
+ f1x = sp2[1][0]-sp1[1][0]
+ f1y = sp2[1][1]-sp1[1][1]
+ if f1x*f1x+f1y*f1y != 0 :
+ l = math.sqrt(f1x*f1x+f1y*f1y)
+ return [f1x/l, f1y/l]
+ elif t == 1 :
+ f1x = sp2[1][0]-sp1[2][0]
+ f1y = sp2[1][1]-sp1[2][1]
+ if abs(f1x*f1x+f1y*f1y) > 1e-20 :
+ l = math.sqrt(f1x*f1x+f1y*f1y)
+ return [f1x/l, f1y/l]
+ else :
+ f1x = sp2[1][0]-sp1[1][0]
+ f1y = sp2[1][1]-sp1[1][1]
+ if f1x*f1x+f1y*f1y != 0 :
+ l = math.sqrt(f1x*f1x+f1y*f1y)
+ return [f1x/l, f1y/l]
+ else :
+ return [1.,0.]
+
+
+def csp_normalized_normal(sp1,sp2,t) :
+ nx,ny = csp_normalized_slope(sp1,sp2,t)
+ return [-ny, nx]
+
+
+def csp_parameterize(sp1,sp2):
+ return bezmisc.bezierparameterize(csp_segment_to_bez(sp1,sp2))
+
+
+def csp_concat_subpaths(*s):
+
+ def concat(s1,s2) :
+ if s1 == [] : return s2
+ if s2 == [] : return s1
+ if (s1[-1][1][0]-s2[0][1][0])**2 + (s1[-1][1][1]-s2[0][1][1])**2 > 0.00001 :
+ return s1[:-1]+[ [s1[-1][0],s1[-1][1],s1[-1][1]], [s2[0][1],s2[0][1],s2[0][2]] ] + s2[1:]
+ else :
+ return s1[:-1]+[ [s1[-1][0],s2[0][1],s2[0][2]] ] + s2[1:]
+
+ if len(s) == 0 : return []
+ if len(s) ==1 : return s[0]
+ result = s[0]
+ for s1 in s[1:]:
+ result = concat(result,s1)
+ return result
+
+
+def csp_draw(csp, color="#05f", group = None, style="fill:none;", width = .1, comment = "") :
+ if csp!=[] and csp!=[[]] :
+ if group == None : group = options.doc_root
+ style += "stroke:"+color+";"+ "stroke-width:%0.4fpx;"%width
+ args = {"d": cubicsuperpath.formatPath(csp), "style":style}
+ if comment!="" : args["comment"] = str(comment)
+ inkex.etree.SubElement( group, inkex.addNS('path','svg'), args )
+
+
+def csp_subpaths_end_to_start_distance2(s1,s2):
+ return (s1[-1][1][0]-s2[0][1][0])**2 + (s1[-1][1][1]-s2[0][1][1])**2
+
+
+def csp_clip_by_line(csp,l1,l2) :
+ result = []
+ for i in range(len(csp)):
+ s = csp[i]
+ intersections = []
+ for j in range(1,len(s)) :
+ intersections += [ [j,int_] for int_ in csp_line_intersection(l1,l2,s[j-1],s[j])]
+ splitted_s = csp_subpath_split_by_points(s, intersections)
+ for s in splitted_s[:] :
+ clip = False
+ for p in csp_true_bounds([s]) :
+ if (l1[1]-l2[1])*p[0] + (l2[0]-l1[0])*p[1] + (l1[0]*l2[1]-l2[0]*l1[1])<-0.01 :
+ clip = True
+ break
+ if clip :
+ splitted_s.remove(s)
+ result += splitted_s
+ return result
+
+
+def csp_subpath_line_to(subpath, points) :
+ # Appends subpath with line or polyline.
+ if len(points)>0 :
+ if len(subpath)>0:
+ subpath[-1][2] = subpath[-1][1][:]
+ if type(points[0]) == type([1,1]) :
+ for p in points :
+ subpath += [ [p[:],p[:],p[:]] ]
+ else:
+ subpath += [ [points,points,points] ]
+ return subpath
+
+
+def csp_join_subpaths(csp) :
+ result = csp[:]
+ done_smf = True
+ joined_result = []
+ while done_smf :
+ done_smf = False
+ while len(result)>0:
+ s1 = result[-1][:]
+ del(result[-1])
+ j = 0
+ joined_smf = False
+ while j<len(joined_result) :
+ if csp_subpaths_end_to_start_distance2(joined_result[j],s1) <0.000001 :
+ joined_result[j] = csp_concat_subpaths(joined_result[j],s1)
+ done_smf = True
+ joined_smf = True
+ break
+ if csp_subpaths_end_to_start_distance2(s1,joined_result[j]) <0.000001 :
+ joined_result[j] = csp_concat_subpaths(s1,joined_result[j])
+ done_smf = True
+ joined_smf = True
+ break
+ j += 1
+ if not joined_smf : joined_result += [s1[:]]
+ if done_smf :
+ result = joined_result[:]
+ joined_result = []
+ return joined_result
+
+
+def triangle_cross(a,b,c):
+ return (a[0]-b[0])*(c[1]-b[1]) - (c[0]-b[0])*(a[1]-b[1])
+
+
+def csp_segment_convex_hull(sp1,sp2):
+ a,b,c,d = sp1[1][:], sp1[2][:], sp2[0][:], sp2[1][:]
+
+ abc = triangle_cross(a,b,c)
+ abd = triangle_cross(a,b,d)
+ bcd = triangle_cross(b,c,d)
+ cad = triangle_cross(c,a,d)
+ if abc == 0 and abd == 0 : return [min(a,b,c,d), max(a,b,c,d)]
+ if abc == 0 : return [d, min(a,b,c), max(a,b,c)]
+ if abd == 0 : return [c, min(a,b,d), max(a,b,d)]
+ if bcd == 0 : return [a, min(b,c,d), max(b,c,d)]
+ if cad == 0 : return [b, min(c,a,d), max(c,a,d)]
+
+ m1, m2, m3 = abc*abd>0, abc*bcd>0, abc*cad>0
+ if m1 and m2 and m3 : return [a,b,c]
+ if m1 and m2 and not m3 : return [a,b,c,d]
+ if m1 and not m2 and m3 : return [a,b,d,c]
+ if not m1 and m2 and m3 : return [a,d,b,c]
+ if m1 and not (m2 and m3) : return [a,b,d]
+ if not (m1 and m2) and m3 : return [c,a,d]
+ if not (m1 and m3) and m2 : return [b,c,d]
+
+ raise ValueError, "csp_segment_convex_hull happend something that shouldnot happen!"
+
+
+################################################################################
+### Bezier additional functions
+################################################################################
+
+def bez_bounds_intersect(bez1, bez2) :
+ return bounds_intersect(bez_bound(bez2), bez_bound(bez1))
+
+
+def bez_bound(bez) :
+ return [
+ min(bez[0][0], bez[1][0], bez[2][0], bez[3][0]),
+ min(bez[0][1], bez[1][1], bez[2][1], bez[3][1]),
+ max(bez[0][0], bez[1][0], bez[2][0], bez[3][0]),
+ max(bez[0][1], bez[1][1], bez[2][1], bez[3][1]),
+ ]
+
+
+def bounds_intersect(a, b) :
+ return not ( (a[0]>b[2]) or (b[0]>a[2]) or (a[1]>b[3]) or (b[1]>a[3]) )
+
+
+def tpoint((x1,y1),(x2,y2),t):
+ return [x1+t*(x2-x1),y1+t*(y2-y1)]
+
+
+def bez_to_csp_segment(bez) :
+ return [bez[0],bez[0],bez[1]], [bez[2],bez[3],bez[3]]
+
+
+def bez_split(a,t=0.5) :
+ a1 = tpoint(a[0],a[1],t)
+ at = tpoint(a[1],a[2],t)
+ b2 = tpoint(a[2],a[3],t)
+ a2 = tpoint(a1,at,t)
+ b1 = tpoint(b2,at,t)
+ a3 = tpoint(a2,b1,t)
+ return [a[0],a1,a2,a3], [a3,b1,b2,a[3]]
+
+
+def bez_at_t(bez,t) :
+ return csp_at_t([bez[0],bez[0],bez[1]],[bez[2],bez[3],bez[3]],t)
+
+
+def bez_to_point_distance(bez,p,needed_dist=[0.,1e100]):
+ # returns [d^2,t]
+ return csp_seg_to_point_distance(bez_to_csp_segment(bez),p,needed_dist)
+
+
+def bez_normalized_slope(bez,t):
+ return csp_normalized_slope([bez[0],bez[0],bez[1]], [bez[2],bez[3],bez[3]],t)
+
+################################################################################
+### Some vector functions
+################################################################################
+
+def normalize((x,y)) :
+ l = math.sqrt(x**2+y**2)
+ if l == 0 : return [0.,0.]
+ else : return [x/l, y/l]
+
+
+def cross(a,b) :
+ return a[1] * b[0] - a[0] * b[1]
+
+
+def dot(a,b) :
+ return a[0] * b[0] + a[1] * b[1]
+
+
+def rotate_ccw(d) :
+ return [-d[1],d[0]]
+
+
+def vectors_ccw(a,b):
+ return a[0]*b[1]-b[0]*a[1] < 0
+
+
+def vector_from_to_length(a,b):
+ return math.sqrt((a[0]-b[0])*(a[0]-b[0]) + (a[1]-b[1])*(a[1]-b[1]))
+
+################################################################################
+### Common functions
+################################################################################
+
+def matrix_mul(a,b) :
+ return [ [ sum([a[i][k]*b[k][j] for k in range(len(a[0])) ]) for j in range(len(b[0]))] for i in range(len(a))]
+ try :
+ return [ [ sum([a[i][k]*b[k][j] for k in range(len(a[0])) ]) for j in range(len(b[0]))] for i in range(len(a))]
+ except :
+ return None
+
+
+def transpose(a) :
+ try :
+ return [ [ a[i][j] for i in range(len(a)) ] for j in range(len(a[0])) ]
+ except :
+ return None
+
+
+def det_3x3(a):
+ return float(
+ a[0][0]*a[1][1]*a[2][2] + a[0][1]*a[1][2]*a[2][0] + a[1][0]*a[2][1]*a[0][2]
+ - a[0][2]*a[1][1]*a[2][0] - a[0][0]*a[2][1]*a[1][2] - a[0][1]*a[2][2]*a[1][0]
+ )
+
+
+def inv_3x3(a): # invert matrix 3x3
+ det = det_3x3(a)
+ if det==0: return None
+ return [
+ [ (a[1][1]*a[2][2] - a[2][1]*a[1][2])/det, -(a[0][1]*a[2][2] - a[2][1]*a[0][2])/det, (a[0][1]*a[1][2] - a[1][1]*a[0][2])/det ],
+ [ -(a[1][0]*a[2][2] - a[2][0]*a[1][2])/det, (a[0][0]*a[2][2] - a[2][0]*a[0][2])/det, -(a[0][0]*a[1][2] - a[1][0]*a[0][2])/det ],
+ [ (a[1][0]*a[2][1] - a[2][0]*a[1][1])/det, -(a[0][0]*a[2][1] - a[2][0]*a[0][1])/det, (a[0][0]*a[1][1] - a[1][0]*a[0][1])/det ]
+ ]
+
+
+def inv_2x2(a): # invert matrix 2x2
+ det = a[0][0]*a[1][1] - a[1][0]*a[0][1]
+ if det==0: return None
+ return [
+ [a[1][1]/det, -a[0][1]/det],
+ [-a[1][0]/det, a[0][0]/det]
+ ]
+
+
+def small(a) :
+ global small_tolerance
+ return abs(a)<small_tolerance
+
+
+def atan2(*arg):
+ if len(arg)==1 and ( type(arg[0]) == type([0.,0.]) or type(arg[0])==type((0.,0.)) ) :
+ return (math.pi/2 - math.atan2(arg[0][0], arg[0][1]) ) % math.pi2
+ elif len(arg)==2 :
+
+ return (math.pi/2 - math.atan2(arg[0],arg[1]) ) % math.pi2
+ else :
+ raise ValueError, "Bad argumets for atan! (%s)" % arg
+
+
+def draw_text(text,x,y,style = None, font_size = 20) :
+ if style == None :
+ style = "font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;fill:#000000;fill-opacity:1;stroke:none;"
+ style += "font-size:%fpx;"%font_size
+ t = inkex.etree.SubElement( options.doc_root, inkex.addNS('text','svg'), {
+ 'x': str(x),
+ inkex.addNS("space","xml"):"preserve",
+ 'y': str(y)
+ })
+ text = str(text).split("\n")
+ for s in text :
+ span = inkex.etree.SubElement( t, inkex.addNS('tspan','svg'),
+ {
+ 'x': str(x),
+ 'y': str(+y),
+ inkex.addNS("role","sodipodi"):"line",
+ })
+ y += font_size
+ span.text = s
+
+
+def draw_pointer(x,color = "#f00", figure = "cross", comment = "", width = .1) :
+ if figure == "line" :
+ s = ""
+ for i in range(1,len(x)/2) :
+ s+= " %s, %s " %(x[i*2],x[i*2+1])
+ inkex.etree.SubElement( options.doc_root, inkex.addNS('path','svg'), {"d": "M %s,%s L %s"%(x[0],x[1],s), "style":"fill:none;stroke:%s;stroke-width:%f;"%(color,width),"comment":str(comment)} )
+ else :
+ inkex.etree.SubElement( options.doc_root, inkex.addNS('path','svg'), {"d": "m %s,%s l 10,10 -20,-20 10,10 -10,10, 20,-20"%(x[0],x[1]), "style":"fill:none;stroke:%s;stroke-width:%f;"%(color,width),"comment":str(comment)} )
+
+
+def straight_segments_intersection(a,b, true_intersection = True) : # (True intersection means check ta and tb are in [0,1])
+ ax,bx,cx,dx, ay,by,cy,dy = a[0][0],a[1][0],b[0][0],b[1][0], a[0][1],a[1][1],b[0][1],b[1][1]
+ if (ax==bx and ay==by) or (cx==dx and cy==dy) : return False, 0, 0
+ if (bx-ax)*(dy-cy)-(by-ay)*(dx-cx)==0 : # Lines are parallel
+ ta = (ax-cx)/(dx-cx) if cx!=dx else (ay-cy)/(dy-cy)
+ tb = (bx-cx)/(dx-cx) if cx!=dx else (by-cy)/(dy-cy)
+ tc = (cx-ax)/(bx-ax) if ax!=bx else (cy-ay)/(by-ay)
+ td = (dx-ax)/(bx-ax) if ax!=bx else (dy-ay)/(by-ay)
+ return ("Overlap" if 0<=ta<=1 or 0<=tb<=1 or 0<=tc<=1 or 0<=td<=1 or not true_intersection else False), (ta,tb), (tc,td)
+ else :
+ ta = ( (ay-cy)*(dx-cx)-(ax-cx)*(dy-cy) ) / ( (bx-ax)*(dy-cy)-(by-ay)*(dx-cx) )
+ tb = ( ax-cx+ta*(bx-ax) ) / (dx-cx) if dx!=cx else ( ay-cy+ta*(by-ay) ) / (dy-cy)
+ return (0<=ta<=1 and 0<=tb<=1 or not true_intersection), ta, tb
+
+
+
+def isnan(x): return type(x) is float and x != x
+
+def isinf(x): inf = 1e5000; return x == inf or x == -inf
+
+def between(c,x,y):
+ return x-straight_tolerance<=c<=y+straight_tolerance or y-straight_tolerance<=c<=x+straight_tolerance
+
+
+def cubic_solver(a,b,c,d):
+ if a!=0:
+ # Monics formula see http://en.wikipedia.org/wiki/Cubic_function#Monic_formula_of_roots
+ a,b,c = (b/a, c/a, d/a)
+ m = 2*a**3 - 9*a*b + 27*c
+ k = a**2 - 3*b
+ n = m**2 - 4*k**3
+ w1 = -.5 + .5*cmath.sqrt(3)*1j
+ w2 = -.5 - .5*cmath.sqrt(3)*1j
+ if n>=0 :
+ t = m+math.sqrt(n)
+ m1 = pow(t/2,1./3) if t>=0 else -pow(-t/2,1./3)
+ t = m-math.sqrt(n)
+ n1 = pow(t/2,1./3) if t>=0 else -pow(-t/2,1./3)
+ else :
+ m1 = pow(complex((m+cmath.sqrt(n))/2),1./3)
+ n1 = pow(complex((m-cmath.sqrt(n))/2),1./3)
+ x1 = -1./3 * (a + m1 + n1)
+ x2 = -1./3 * (a + w1*m1 + w2*n1)
+ x3 = -1./3 * (a + w2*m1 + w1*n1)
+ return [x1,x2,x3]
+ elif b!=0:
+ det = c**2-4*b*d
+ if det>0 :
+ return [(-c+math.sqrt(det))/(2*b),(-c-math.sqrt(det))/(2*b)]
+ elif d == 0 :
+ return [-c/(b*b)]
+ else :
+ return [(-c+cmath.sqrt(det))/(2*b),(-c-cmath.sqrt(det))/(2*b)]
+ elif c!=0 :
+ return [-d/c]
+ else : return []
+
+
+################################################################################
+### print_ prints any arguments into specified log file
+################################################################################
+
+def print_(*arg):
+ f = open(options.log_filename,"a")
+ for s in arg :
+ s = str(unicode(s).encode('unicode_escape'))+" "
+ f.write( s )
+ f.write("\n")
+ f.close()
+
+
+################################################################################
+### Point (x,y) operations
+################################################################################
+class P:
+ def __init__(self, x, y=None):
+ if not y==None:
+ self.x, self.y = float(x), float(y)
+ else:
+ self.x, self.y = float(x[0]), float(x[1])
+ def __add__(self, other): return P(self.x + other.x, self.y + other.y)
+ def __sub__(self, other): return P(self.x - other.x, self.y - other.y)
+ def __neg__(self): return P(-self.x, -self.y)
+ def __mul__(self, other):
+ if isinstance(other, P):
+ return self.x * other.x + self.y * other.y
+ return P(self.x * other, self.y * other)
+ __rmul__ = __mul__
+ def __div__(self, other): return P(self.x / other, self.y / other)
+ def mag(self): return math.hypot(self.x, self.y)
+ def unit(self):
+ h = self.mag()
+ if h: return self / h
+ else: return P(0,0)
+ def dot(self, other): return self.x * other.x + self.y * other.y
+ def rot(self, theta):
+ c = math.cos(theta)
+ s = math.sin(theta)
+ return P(self.x * c - self.y * s, self.x * s + self.y * c)
+ def angle(self): return math.atan2(self.y, self.x)
+ def __repr__(self): return '%f,%f' % (self.x, self.y)
+ def pr(self): return "%.2f,%.2f" % (self.x, self.y)
+ def to_list(self): return [self.x, self.y]
+ def ccw(self): return P(-self.y,self.x)
+ def l2(self): return self.x*self.x + self.y*self.y
+
+################################################################################
+###
+### Offset function
+###
+### This function offsets given cubic super path.
+### It's based on src/livarot/PathOutline.cpp from Inkscape's source code.
+###
+###
+################################################################################
+def csp_offset(csp, r) :
+ offset_tolerance = 0.05
+ offset_subdivision_depth = 10
+ time_ = time.time()
+ time_start = time_
+ print_("Offset start at %s"% time_)
+ print_("Offset radius %s"% r)
+
+
+ def csp_offset_segment(sp1,sp2,r) :
+ result = []
+ t = csp_get_t_at_curvature(sp1,sp2,1/r)
+ if len(t) == 0 : t =[0.,1.]
+ t.sort()
+ if t[0]>.00000001 : t = [0.]+t
+ if t[-1]<.99999999 : t.append(1.)
+ for st,end in zip(t,t[1:]) :
+ c = csp_curvature_at_t(sp1,sp2,(st+end)/2)
+ sp = csp_split_by_two_points(sp1,sp2,st,end)
+ if sp[1]!=sp[2]:
+ if (c>1/r and r<0 or c<1/r and r>0) :
+ offset = offset_segment_recursion(sp[1],sp[2],r, offset_subdivision_depth, offset_tolerance)
+ else : # This part will be clipped for sure... TODO Optimize it...
+ offset = offset_segment_recursion(sp[1],sp[2],r, offset_subdivision_depth, offset_tolerance)
+
+ if result==[] :
+ result = offset[:]
+ else:
+ if csp_subpaths_end_to_start_distance2(result,offset)<0.0001 :
+ result = csp_concat_subpaths(result,offset)
+ else:
+
+ intersection = csp_get_subapths_last_first_intersection(result,offset)
+ if intersection != [] :
+ i,t1,j,t2 = intersection
+ sp1_,sp2_,sp3_ = csp_split(result[i-1],result[i],t1)
+ result = result[:i-1] + [ sp1_, sp2_ ]
+ sp1_,sp2_,sp3_ = csp_split(offset[j-1],offset[j],t2)
+ result = csp_concat_subpaths( result, [sp2_,sp3_] + offset[j+1:] )
+ else :
+ pass # ???
+ #raise ValueError, "Offset curvature clipping error"
+ #csp_draw([result])
+ return result
+
+
+ def create_offset_segment(sp1,sp2,r) :
+ # See Gernot Hoffmann "Bezier Curves" p.34 -> 7.1 Bezier Offset Curves
+ p0,p1,p2,p3 = P(sp1[1]),P(sp1[2]),P(sp2[0]),P(sp2[1])
+ s0,s1,s3 = p1-p0,p2-p1,p3-p2
+ n0 = s0.ccw().unit() if s0.l2()!=0 else P(csp_normalized_normal(sp1,sp2,0))
+ n3 = s3.ccw().unit() if s3.l2()!=0 else P(csp_normalized_normal(sp1,sp2,1))
+ n1 = s1.ccw().unit() if s1.l2()!=0 else (n0.unit()+n3.unit()).unit()
+
+ q0,q3 = p0+r*n0, p3+r*n3
+ c = csp_curvature_at_t(sp1,sp2,0)
+ q1 = q0 + (p1-p0)*(1- (r*c if abs(c)<100 else 0) )
+ c = csp_curvature_at_t(sp1,sp2,1)
+ q2 = q3 + (p2-p3)*(1- (r*c if abs(c)<100 else 0) )
+
+
+ return [[q0.to_list(), q0.to_list(), q1.to_list()],[q2.to_list(), q3.to_list(), q3.to_list()]]
+
+
+ def csp_get_subapths_last_first_intersection(s1,s2):
+ _break = False
+ for i in range(1,len(s1)) :
+ sp11, sp12 = s1[-i-1], s1[-i]
+ for j in range(1,len(s2)) :
+ sp21,sp22 = s2[j-1], s2[j]
+ intersection = csp_segments_true_intersection(sp11,sp12,sp21,sp22)
+ if intersection != [] :
+ _break = True
+ break
+ if _break:break
+ if _break :
+ intersection = max(intersection)
+ return [len(s1)-i,intersection[0], j,intersection[1]]
+ else :
+ return []
+
+
+ def csp_join_offsets(prev,next,sp1,sp2,sp1_l,sp2_l,r):
+ if len(next)>1 :
+ if (P(prev[-1][1])-P(next[0][1])).l2()<0.001 :
+ return prev,[],next
+ intersection = csp_get_subapths_last_first_intersection(prev,next)
+ if intersection != [] :
+ i,t1,j,t2 = intersection
+ sp1_,sp2_,sp3_ = csp_split(prev[i-1],prev[i],t1)
+ sp3_,sp4_,sp5_ = csp_split(next[j-1], next[j],t2)
+ return prev[:i-1] + [ sp1_, sp2_ ], [], [sp4_,sp5_] + next[j+1:]
+
+ # Offsets do not intersect... will add an arc...
+ start = (P(csp_at_t(sp1_l,sp2_l,1.)) + r*P(csp_normalized_normal(sp1_l,sp2_l,1.))).to_list()
+ end = (P(csp_at_t(sp1,sp2,0.)) + r*P(csp_normalized_normal(sp1,sp2,0.))).to_list()
+ arc = csp_from_arc(start, end, sp1[1], r, csp_normalized_slope(sp1_l,sp2_l,1.) )
+ if arc == [] :
+ return prev,[],next
+ else:
+ # Clip prev by arc
+ if csp_subpaths_end_to_start_distance2(prev,arc)>0.00001 :
+ intersection = csp_get_subapths_last_first_intersection(prev,arc)
+ if intersection != [] :
+ i,t1,j,t2 = intersection
+ sp1_,sp2_,sp3_ = csp_split(prev[i-1],prev[i],t1)
+ sp3_,sp4_,sp5_ = csp_split(arc[j-1],arc[j],t2)
+ prev = prev[:i-1] + [ sp1_, sp2_ ]
+ arc = [sp4_,sp5_] + arc[j+1:]
+ #else : raise ValueError, "Offset curvature clipping error"
+ # Clip next by arc
+ if next == [] :
+ return prev,[],arc
+ if csp_subpaths_end_to_start_distance2(arc,next)>0.00001 :
+ intersection = csp_get_subapths_last_first_intersection(arc,next)
+ if intersection != [] :
+ i,t1,j,t2 = intersection
+ sp1_,sp2_,sp3_ = csp_split(arc[i-1],arc[i],t1)
+ sp3_,sp4_,sp5_ = csp_split(next[j-1],next[j],t2)
+ arc = arc[:i-1] + [ sp1_, sp2_ ]
+ next = [sp4_,sp5_] + next[j+1:]
+ #else : raise ValueError, "Offset curvature clipping error"
+
+ return prev,arc,next
+
+
+ def offset_segment_recursion(sp1,sp2,r, depth, tolerance) :
+ sp1_r,sp2_r = create_offset_segment(sp1,sp2,r)
+ err = max(
+ csp_seg_to_point_distance(sp1_r,sp2_r, (P(csp_at_t(sp1,sp2,.25)) + P(csp_normalized_normal(sp1,sp2,.25))*r).to_list())[0],
+ csp_seg_to_point_distance(sp1_r,sp2_r, (P(csp_at_t(sp1,sp2,.50)) + P(csp_normalized_normal(sp1,sp2,.50))*r).to_list())[0],
+ csp_seg_to_point_distance(sp1_r,sp2_r, (P(csp_at_t(sp1,sp2,.75)) + P(csp_normalized_normal(sp1,sp2,.75))*r).to_list())[0],
+ )
+
+ if err>tolerance**2 and depth>0:
+ #print_(csp_seg_to_point_distance(sp1_r,sp2_r, (P(csp_at_t(sp1,sp2,.25)) + P(csp_normalized_normal(sp1,sp2,.25))*r).to_list())[0], tolerance)
+ if depth > offset_subdivision_depth-2 :
+ t = csp_max_curvature(sp1,sp2)
+ t = max(.1,min(.9 ,t))
+ else :
+ t = .5
+ sp3,sp4,sp5 = csp_split(sp1,sp2,t)
+ r1 = offset_segment_recursion(sp3,sp4,r, depth-1, tolerance)
+ r2 = offset_segment_recursion(sp4,sp5,r, depth-1, tolerance)
+ return r1[:-1]+ [[r1[-1][0],r1[-1][1],r2[0][2]]] + r2[1:]
+ else :
+ #csp_draw([[sp1_r,sp2_r]])
+ #draw_pointer(sp1[1]+sp1_r[1], "#057", "line")
+ #draw_pointer(sp2[1]+sp2_r[1], "#705", "line")
+ return [sp1_r,sp2_r]
+
+
+ ############################################################################
+ # Some small definitions
+ ############################################################################
+ csp_len = len(csp)
+
+ ############################################################################
+ # Prepare the path
+ ############################################################################
+ # Remove all small segments (segment length < 0.001)
+
+ for i in xrange(len(csp)) :
+ for j in xrange(len(csp[i])) :
+ sp = csp[i][j]
+ if (P(sp[1])-P(sp[0])).mag() < 0.001 :
+ csp[i][j][0] = sp[1]
+ if (P(sp[2])-P(sp[0])).mag() < 0.001 :
+ csp[i][j][2] = sp[1]
+ for i in xrange(len(csp)) :
+ for j in xrange(1,len(csp[i])) :
+ if cspseglength(csp[i][j-1], csp[i][j])<0.001 :
+ csp[i] = csp[i][:j] + csp[i][j+1:]
+ if cspseglength(csp[i][-1],csp[i][0])>0.001 :
+ csp[i][-1][2] = csp[i][-1][1]
+ csp[i]+= [ [csp[i][0][1],csp[i][0][1],csp[i][0][1]] ]
+
+ # TODO Get rid of self intersections.
+
+ original_csp = csp[:]
+ # Clip segments which has curvature>1/r. Because their offset will be selfintersecting and very nasty.
+
+ print_("Offset prepared the path in %s"%(time.time()-time_))
+ print_("Path length = %s"% sum([len(i)for i in csp] ) )
+ time_ = time.time()
+
+ ############################################################################
+ # Offset
+ ############################################################################
+ # Create offsets for all segments in the path. And join them together inside each subpath.
+ unclipped_offset = [[] for i in xrange(csp_len)]
+ offsets_original = [[] for i in xrange(csp_len)]
+ join_points = [[] for i in xrange(csp_len)]
+ intersection = [[] for i in xrange(csp_len)]
+ for i in xrange(csp_len) :
+ subpath = csp[i]
+ subpath_offset = []
+ last_offset_len = 0
+ for sp1,sp2 in zip(subpath, subpath[1:]) :
+ segment_offset = csp_offset_segment(sp1,sp2,r)
+ if subpath_offset == [] :
+ subpath_offset = segment_offset
+
+ prev_l = len(subpath_offset)
+ else :
+ prev, arc, next = csp_join_offsets(subpath_offset[-prev_l:],segment_offset,sp1,sp2,sp1_l,sp2_l,r)
+ #csp_draw([prev],"Blue")
+ #csp_draw([arc],"Magenta")
+ subpath_offset = csp_concat_subpaths(subpath_offset[:-prev_l+1],prev,arc,next)
+ prev_l = len(next)
+ sp1_l, sp2_l = sp1[:], sp2[:]
+
+ # Join last and first offsets togother to close the curve
+
+ prev, arc, next = csp_join_offsets(subpath_offset[-prev_l:], subpath_offset[:2], subpath[0], subpath[1], sp1_l,sp2_l, r)
+ subpath_offset[:2] = next[:]
+ subpath_offset = csp_concat_subpaths(subpath_offset[:-prev_l+1],prev,arc)
+ #csp_draw([prev],"Blue")
+ #csp_draw([arc],"Red")
+ #csp_draw([next],"Red")
+
+ # Collect subpath's offset and save it to unclipped offset list.
+ unclipped_offset[i] = subpath_offset[:]
+
+ #for k,t in intersection[i]:
+ # draw_pointer(csp_at_t(subpath_offset[k-1], subpath_offset[k], t))
+
+ #inkex.etree.SubElement( options.doc_root, inkex.addNS('path','svg'), {"d": cubicsuperpath.formatPath(unclipped_offset), "style":"fill:none;stroke:#0f0;"} )
+ print_("Offsetted path in %s"%(time.time()-time_))
+ time_ = time.time()
+
+ #for i in range(len(unclipped_offset)):
+ # csp_draw([unclipped_offset[i]], color = ["Green","Red","Blue"][i%3], width = .1)
+ #return []
+ ############################################################################
+ # Now to the clipping.
+ ############################################################################
+ # First of all find all intersection's between all segments of all offseted subpaths, including self intersections.
+
+ #TODO define offset tolerance here
+ global small_tolerance
+ small_tolerance = 0.01
+ summ = 0
+ summ1 = 0
+ for subpath_i in xrange(csp_len) :
+ for subpath_j in xrange(subpath_i,csp_len) :
+ subpath = unclipped_offset[subpath_i]
+ subpath1 = unclipped_offset[subpath_j]
+ for i in xrange(1,len(subpath)) :
+ # If subpath_i==subpath_j we are looking for self intersections, so
+ # we'll need search intersections only for xrange(i,len(subpath1))
+ for j in ( xrange(i,len(subpath1)) if subpath_i==subpath_j else xrange(len(subpath1))) :
+ if subpath_i==subpath_j and j==i :
+ # Find self intersections of a segment
+ sp1,sp2,sp3 = csp_split(subpath[i-1],subpath[i],.5)
+ intersections = csp_segments_intersection(sp1,sp2,sp2,sp3)
+ summ +=1
+ for t in intersections :
+ summ1 += 1
+ if not ( small(t[0]-1) and small(t[1]) ) and 0<=t[0]<=1 and 0<=t[1]<=1 :
+ intersection[subpath_i] += [ [i,t[0]/2],[j,t[1]/2+.5] ]
+ else :
+ intersections = csp_segments_intersection(subpath[i-1],subpath[i],subpath1[j-1],subpath1[j])
+ summ +=1
+ for t in intersections :
+ summ1 += 1
+ #TODO tolerance dependence to cpsp_length(t)
+ if len(t) == 2 and 0<=t[0]<=1 and 0<=t[1]<=1 and not (
+ subpath_i==subpath_j and (
+ (j-i-1) % (len(subpath)-1) == 0 and small(t[0]-1) and small(t[1]) or
+ (i-j-1) % (len(subpath)-1) == 0 and small(t[1]-1) and small(t[0]) ) ) :
+ intersection[subpath_i] += [ [i,t[0]] ]
+ intersection[subpath_j] += [ [j,t[1]] ]
+ #draw_pointer(csp_at_t(subpath[i-1],subpath[i],t[0]),"#f00")
+ #print_(t)
+ #print_(i,j)
+ elif len(t)==5 and t[4]=="Overlap":
+ intersection[subpath_i] += [ [i,t[0]], [i,t[1]] ]
+ intersection[subpath_j] += [ [j,t[1]], [j,t[3]] ]
+
+ print_("Intersections found in %s"%(time.time()-time_))
+ print_("Examined %s segments"%(summ))
+ print_("found %s intersections"%(summ1))
+ time_ = time.time()
+
+ ########################################################################
+ # Split unclipped offset by intersection points into splitted_offset
+ ########################################################################
+ splitted_offset = []
+ for i in xrange(csp_len) :
+ subpath = unclipped_offset[i]
+ if len(intersection[i]) > 0 :
+ parts = csp_subpath_split_by_points(subpath, intersection[i])
+ # Close parts list to close path (The first and the last parts are joined together)
+ if [1,0.] not in intersection[i] :
+ parts[0][0][0] = parts[-1][-1][0]
+ parts[0] = csp_concat_subpaths(parts[-1], parts[0])
+ splitted_offset += parts[:-1]
+ else:
+ splitted_offset += parts[:]
+ else :
+ splitted_offset += [subpath[:]]
+
+ #for i in range(len(splitted_offset)):
+ # csp_draw([splitted_offset[i]], color = ["Green","Red","Blue"][i%3])
+ print_("Splitted in %s"%(time.time()-time_))
+ time_ = time.time()
+
+
+ ########################################################################
+ # Clipping
+ ########################################################################
+ result = []
+ for subpath_i in range(len(splitted_offset)):
+ clip = False
+ s1 = splitted_offset[subpath_i]
+ for subpath_j in range(len(splitted_offset)):
+ s2 = splitted_offset[subpath_j]
+ if (P(s1[0][1])-P(s2[-1][1])).l2()<0.0001 and ( (subpath_i+1) % len(splitted_offset) != subpath_j ):
+ if dot(csp_normalized_normal(s2[-2],s2[-1],1.),csp_normalized_slope(s1[0],s1[1],0.))*r<-0.0001 :
+ clip = True
+ break
+ if (P(s2[0][1])-P(s1[-1][1])).l2()<0.0001 and ( (subpath_j+1) % len(splitted_offset) != subpath_i ):
+ if dot(csp_normalized_normal(s2[0],s2[1],0.),csp_normalized_slope(s1[-2],s1[-1],1.))*r>0.0001 :
+ clip = True
+ break
+
+ if not clip :
+ result += [s1[:]]
+ elif options.offset_draw_clippend_path :
+ csp_draw([s1],color="Red",width=.1)
+ draw_pointer( csp_at_t(s2[-2],s2[-1],1.)+
+ (P(csp_at_t(s2[-2],s2[-1],1.))+ P(csp_normalized_normal(s2[-2],s2[-1],1.))*10).to_list(),"Green", "line" )
+ draw_pointer( csp_at_t(s1[0],s1[1],0.)+
+ (P(csp_at_t(s1[0],s1[1],0.))+ P(csp_normalized_slope(s1[0],s1[1],0.))*10).to_list(),"Red", "line" )
+
+ # Now join all together and check closure and orientation of result
+ joined_result = csp_join_subpaths(result)
+ # Check if each subpath from joined_result is closed
+ #csp_draw(joined_result,color="Green",width=1)
+
+
+ for s in joined_result[:] :
+ if csp_subpaths_end_to_start_distance2(s,s) > 0.001 :
+ # Remove open parts
+ if options.offset_draw_clippend_path:
+ csp_draw([s],color="Orange",width=1)
+ draw_pointer(s[0][1], comment= csp_subpaths_end_to_start_distance2(s,s))
+ draw_pointer(s[-1][1], comment = csp_subpaths_end_to_start_distance2(s,s))
+ joined_result.remove(s)
+ else :
+ # Remove small parts
+ minx,miny,maxx,maxy = csp_true_bounds([s])
+ if (minx[0]-maxx[0])**2 + (miny[1]-maxy[1])**2 < 0.1 :
+ joined_result.remove(s)
+ print_("Clipped and joined path in %s"%(time.time()-time_))
+ time_ = time.time()
+
+ ########################################################################
+ # Now to the Dummy cliping: remove parts from splitted offset if their
+ # centers are closer to the original path than offset radius.
+ ########################################################################
+
+ r1,r2 = ( (0.99*r)**2, (1.01*r)**2 ) if abs(r*.01)<1 else ((abs(r)-1)**2, (abs(r)+1)**2)
+ for s in joined_result[:]:
+ dist = csp_to_point_distance(original_csp, s[int(len(s)/2)][1], dist_bounds = [r1,r2], tolerance = .000001)
+ if not r1 < dist[0] < r2 :
+ joined_result.remove(s)
+ if options.offset_draw_clippend_path:
+ csp_draw([s], comment = math.sqrt(dist[0]))
+ draw_pointer(csp_at_t(csp[dist[1]][dist[2]-1],csp[dist[1]][dist[2]],dist[3])+s[int(len(s)/2)][1],"blue", "line", comment = [math.sqrt(dist[0]),i,j,sp] )
+
+ print_("-----------------------------")
+ print_("Total offset time %s"%(time.time()-time_start))
+ print_()
+ return joined_result
+
+
+
+
+
+################################################################################
+###
+### Biarc function
+###
+### Calculates biarc approximation of cubic super path segment
+### splits segment if needed or approximates it with straight line
+###
+################################################################################
+def biarc(sp1, sp2, z1, z2, depth=0):
+ def biarc_split(sp1,sp2, z1, z2, depth):
+ if depth<options.biarc_max_split_depth:
+ sp1,sp2,sp3 = csp_split(sp1,sp2)
+ l1, l2 = cspseglength(sp1,sp2), cspseglength(sp2,sp3)
+ if l1+l2 == 0 : zm = z1
+ else : zm = z1+(z2-z1)*l1/(l1+l2)
+ return biarc(sp1,sp2,z1,zm,depth+1)+biarc(sp2,sp3,zm,z2,depth+1)
+ else: return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
+
+ P0, P4 = P(sp1[1]), P(sp2[1])
+ TS, TE, v = (P(sp1[2])-P0), -(P(sp2[0])-P4), P0 - P4
+ tsa, tea, va = TS.angle(), TE.angle(), v.angle()
+ if TE.mag()<straight_distance_tolerance and TS.mag()<straight_distance_tolerance:
+ # Both tangents are zerro - line straight
+ return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
+ if TE.mag() < straight_distance_tolerance:
+ TE = -(TS+v).unit()
+ r = TS.mag()/v.mag()*2
+ elif TS.mag() < straight_distance_tolerance:
+ TS = -(TE+v).unit()
+ r = 1/( TE.mag()/v.mag()*2 )
+ else:
+ r=TS.mag()/TE.mag()
+ TS, TE = TS.unit(), TE.unit()
+ tang_are_parallel = ((tsa-tea)%math.pi<straight_tolerance or math.pi-(tsa-tea)%math.pi<straight_tolerance )
+ if ( tang_are_parallel and
+ ((v.mag()<straight_distance_tolerance or TE.mag()<straight_distance_tolerance or TS.mag()<straight_distance_tolerance) or
+ 1-abs(TS*v/(TS.mag()*v.mag()))<straight_tolerance) ):
+ # Both tangents are parallel and start and end are the same - line straight
+ # or one of tangents still smaller then tollerance
+
+ # Both tangents and v are parallel - line straight
+ return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
+
+ c,b,a = v*v, 2*v*(r*TS+TE), 2*r*(TS*TE-1)
+ if v.mag()==0:
+ return biarc_split(sp1, sp2, z1, z2, depth)
+ asmall, bsmall, csmall = abs(a)<10**-10,abs(b)<10**-10,abs(c)<10**-10
+ if asmall and b!=0: beta = -c/b
+ elif csmall and a!=0: beta = -b/a
+ elif not asmall:
+ discr = b*b-4*a*c
+ if discr < 0: raise ValueError, (a,b,c,discr)
+ disq = discr**.5
+ beta1 = (-b - disq) / 2 / a
+ beta2 = (-b + disq) / 2 / a
+ if beta1*beta2 > 0 : raise ValueError, (a,b,c,disq,beta1,beta2)
+ beta = max(beta1, beta2)
+ elif asmall and bsmall:
+ return biarc_split(sp1, sp2, z1, z2, depth)
+ alpha = beta * r
+ ab = alpha + beta
+ P1 = P0 + alpha * TS
+ P3 = P4 - beta * TE
+ P2 = (beta / ab) * P1 + (alpha / ab) * P3
+
+
+ def calculate_arc_params(P0,P1,P2):
+ D = (P0+P2)/2
+ if (D-P1).mag()==0: return None, None
+ R = D - ( (D-P0).mag()**2/(D-P1).mag() )*(P1-D).unit()
+ p0a, p1a, p2a = (P0-R).angle()%(2*math.pi), (P1-R).angle()%(2*math.pi), (P2-R).angle()%(2*math.pi)
+ alpha = (p2a - p0a) % (2*math.pi)
+ if (p0a<p2a and (p1a<p0a or p2a<p1a)) or (p2a<p1a<p0a) :
+ alpha = -2*math.pi+alpha
+ if abs(R.x)>1000000 or abs(R.y)>1000000 or (R-P0).mag<options.min_arc_radius :
+ return None, None
+ else :
+ return R, alpha
+ R1,a1 = calculate_arc_params(P0,P1,P2)
+ R2,a2 = calculate_arc_params(P2,P3,P4)
+ if R1==None or R2==None or (R1-P0).mag()<straight_tolerance or (R2-P2).mag()<straight_tolerance : return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
+
+ d = csp_to_arc_distance(sp1,sp2, [P0,P2,R1,a1],[P2,P4,R2,a2])
+ if d > options.biarc_tolerance and depth<options.biarc_max_split_depth : return biarc_split(sp1, sp2, z1, z2, depth)
+ else:
+ if R2.mag()*a2 == 0 : zm = z2
+ else : zm = z1 + (z2-z1)*(abs(R1.mag()*a1))/(abs(R2.mag()*a2)+abs(R1.mag()*a1))
+ return [ [ sp1[1], 'arc', [R1.x,R1.y], a1, [P2.x,P2.y], [z1,zm] ], [ [P2.x,P2.y], 'arc', [R2.x,R2.y], a2, [P4.x,P4.y], [zm,z2] ] ]
+
+
+def biarc_curve_segment_length(seg):
+ if seg[1] == "arc" :
+ return math.sqrt((seg[0][0]-seg[2][0])**2+(seg[0][1]-seg[2][1])**2)*seg[3]
+ elif seg[1] == "line" :
+ return math.sqrt((seg[0][0]-seg[4][0])**2+(seg[0][1]-seg[4][1])**2)
+ else:
+ return 0
+
+
+def biarc_curve_clip_at_l(curve, l, clip_type = "strict") :
+ # get first subcurve and ceck it's length
+ subcurve, subcurve_l, moved = [], 0, False
+ for seg in curve:
+ if seg[1] == "move" and moved or seg[1] == "end" :
+ break
+ if seg[1] == "move" : moved = True
+ subcurve_l += biarc_curve_segment_length(seg)
+ if seg[1] == "arc" or seg[1] == "line" :
+ subcurve += [seg]
+
+ if subcurve_l < l and clip_type == "strict" : return []
+ lc = 0
+ if (subcurve[-1][4][0]-subcurve[0][0][0])**2 + (subcurve[-1][4][1]-subcurve[0][0][1])**2 < 10**-7 : subcurve_closed = True
+ i = 0
+ reverse = False
+ while lc<l :
+ seg = subcurve[i]
+ if reverse :
+ if seg[1] == "line" :
+ seg = [seg[4], "line", 0 , 0, seg[0], seg[5]] # Hmmm... Do we have to swap seg[5][0] and seg[5][1] (zstart and zend) or not?
+ elif seg[1] == "arc" :
+ seg = [seg[4], "arc", seg[2] , -seg[3], seg[0], seg[5]] # Hmmm... Do we have to swap seg[5][0] and seg[5][1] (zstart and zend) or not?
+ ls = biarc_curve_segment_length(seg)
+ if ls != 0 :
+ if l-lc>ls :
+ res += [seg]
+ else :
+ if seg[1] == "arc" :
+ r = math.sqrt((seg[0][0]-seg[2][0])**2+(seg[0][1]-seg[2][1])**2)
+ x,y = seg[0][0]-seg[2][0], seg[0][1]-seg[2][1]
+ a = seg[3]/ls*(l-lc)
+ x,y = x*math.cos(a) - y*math.sin(a), x*math.sin(a) + y*math.cos(a)
+ x,y = x+seg[2][0], y+seg[2][1]
+ res += [[ seg[0], "arc", seg[2], a, [x,y], [seg[5][0],seg[5][1]/ls*(l-lc)] ]]
+ if seg[1] == "line" :
+ res += [[ seg[0], "line", 0, 0, [(seg[4][0]-seg[0][0])/ls*(l-lc),(seg[4][1]-seg[0][1])/ls*(l-lc)], [seg[5][0],seg[5][1]/ls*(l-lc)] ]]
+ i += 1
+ if i >= len(subcurve) and not subcurve_closed:
+ reverse = not reverse
+ i = i%len(subcurve)
+ return res
+
+
+
+class Postprocessor():
+ def __init__(self, error_function_handler):
+ self.error = error_function_handler
+ self.functions = {
+ "remap" : self.remap,
+ "remapi" : self.remapi ,
+ "scale" : self.scale,
+ "move" : self.move,
+ "flip" : self.flip_axis,
+ "flip_axis" : self.flip_axis,
+ "round" : self.round_coordinates,
+ "parameterize" : self.parameterize,
+ }
+
+
+ def process(self,command):
+ command = re.sub(r"\\\\",":#:#:slash:#:#:",command)
+ command = re.sub(r"\\;",":#:#:semicolon:#:#:",command)
+ command = command.split(";")
+ for s in command:
+ s = re.sub(":#:#:slash:#:#:","\\\\",s)
+ s = re.sub(":#:#:semicolon:#:#:","\\;",s)
+ s = s.strip()
+ if s!="" :
+ self.parse_command(s)
+
+
+ def parse_command(self,command):
+ r = re.match(r"([A-Za-z0-9_]+)\s*\(\s*(.*)\)",command)
+ if not r:
+ self.error("Parse error while postprocessing.\n(Command: '%s')"%(command), "error")
+ function, parameters = r.group(1).lower(),r.group(2)
+ if function in self.functions :
+ print_("Postprocessor: executing function %s(%s)"%(function,parameters))
+ self.functions[function](parameters)
+ else :
+ self.error("Unrecognized function '%s' while postprocessing.\n(Command: '%s')"%(function,command), "error")
+
+
+ def re_sub_on_gcode_lines(self, pattern,replacemant):
+ gcode = self.gcode.split("\n")
+ self.gcode = ""
+ for i in range(len(gcode)) :
+ self.gcode += re.sub(pattern,replacement,gcode[i])
+
+
+ def remapi(self,parameters):
+ self.remap(parameters, case_sensitive = True)
+
+
+ def remap(self,parameters, case_sensitive = False):
+ # remap parameters should be like "x->y,y->x"
+ parameters = parameters.replace("\,",":#:#:coma:#:#:")
+ parameters = parameters.split(",")
+ pattern, remap = [], []
+ for s in parameters:
+ s = s.replace(":#:#:coma:#:#:","\,")
+ r = re.match("""\s*(\'|\")(.*)\\1\s*->\s*(\'|\")(.*)\\3\s*""",s)
+ if not r :
+ self.error("Bad parameters for remap.\n(Parameters: '%s')"%(parameters), "error")
+ pattern +=[r.group(2)]
+ remap +=[r.group(4)]
+
+
+
+ for i in range(len(pattern)) :
+ if case_sensitive :
+ self.gcode = ireplace(self.gcode, pattern[i], ":#:#:remap_pattern%s:#:#:"%i )
+ else :
+ self.gcode = self.gcode.replace(pattern[i], ":#:#:remap_pattern%s:#:#:"%i)
+
+ for i in range(len(remap)) :
+ self.gcode = self.gcode.replace(":#:#:remap_pattern%s:#:#:"%i, remap[i])
+
+
+ def transform(self, move, scale):
+ axis = ["xi","yj","zk","a"]
+ flip = scale[0]*scale[1]*scale[2] < 0
+ gcode = ""
+ warned = []
+ r_scale = scale[0]
+ plane = "g17"
+ for s in self.gcode.split("\n"):
+ # get plane selection:
+ s_wo_comments = re.sub(r"\([^\)]*\)","",s)
+ r = re.search(r"(?i)(G17|G18|G19)", s_wo_comments)
+ if r :
+ plane = r.group(1).lower()
+ if plane == "g17" : r_scale = scale[0] # plane XY -> scale x
+ if plane == "g18" : r_scale = scale[0] # plane XZ -> scale x
+ if plane == "g19" : r_scale = scale[1] # plane YZ -> scale y
+ # Raise warning if scale factors are not the game for G02 and G03
+ if plane not in warned:
+ r = re.search(r"(?i)(G02|G03)", s_wo_comments)
+ if r :
+ if plane == "g17" and scale[0]!=scale[1]: self.error("Post-processor: Scale factors for X and Y axis are not the same. G02 and G03 codes will be corrupted.","warning")
+ if plane == "g18" and scale[0]!=scale[2]: self.error("Post-processor: Scale factors for X and Z axis are not the same. G02 and G03 codes will be corrupted.","warning")
+ if plane == "g19" and scale[1]!=scale[2]: self.error("Post-processor: Scale factors for Y and Z axis are not the same. G02 and G03 codes will be corrupted.","warning")
+ warned += [plane]
+ # Transform
+ for i in range(len(axis)) :
+ if move[i] != 0 or scale[i] != 1:
+ for a in axis[i] :
+ r = re.search(r"(?i)("+a+r")\s*(-?)\s*(\d*\.?\d*)", s)
+ if r and r.group(3)!="":
+ s = re.sub(r"(?i)("+a+r")\s*(-?)\s*(\d*\.?\d*)", r"\1 %f"%(float(r.group(2)+r.group(3))*scale[i]+(move[i] if a not in ["i","j","k"] else 0) ), s)
+ #scale radius R
+ if r_scale != 1 :
+ r = re.search(r"(?i)(r)\s*(-?\s*(\d*\.?\d*))", s)
+ if r and r.group(3)!="":
+ try:
+ s = re.sub(r"(?i)(r)\s*(-?)\s*(\d*\.?\d*)", r"\1 %f"%( float(r.group(2)+r.group(3))*r_scale ), s)
+ except:
+ pass
+
+ gcode += s + "\n"
+
+ self.gcode = gcode
+ if flip :
+ self.remapi("'G02'->'G03', 'G03'->'G02'")
+
+
+ def parameterize(self,parameters) :
+ planes = []
+ feeds = {}
+ coords = []
+ gcode = ""
+ coords_def = {"x":"x","y":"y","z":"z","i":"x","j":"y","k":"z","a":"a"}
+ for s in self.gcode.split("\n"):
+ s_wo_comments = re.sub(r"\([^\)]*\)","",s)
+ # get Planes
+ r = re.search(r"(?i)(G17|G18|G19)", s_wo_comments)
+ if r :
+ plane = r.group(1).lower()
+ if plane not in planes :
+ planes += [plane]
+ # get Feeds
+ r = re.search(r"(?i)(F)\s*(-?)\s*(\d*\.?\d*)", s_wo_comments)
+ if r :
+ feed = float (r.group(2)+r.group(3))
+ if feed not in feeds :
+ feeds[feed] = "#"+str(len(feeds)+20)
+
+ #Coordinates
+ for c in "xyzijka" :
+ r = re.search(r"(?i)("+c+r")\s*(-?)\s*(\d*\.?\d*)", s_wo_comments)
+ if r :
+ c = coords_def[r.group(1).lower()]
+ if c not in coords :
+ coords += [c]
+ # Add offset parametrization
+ offset = {"x":"#6","y":"#7","z":"#8","a":"#9"}
+ for c in coords:
+ gcode += "%s = 0 (%s axis offset)\n" % (offset[c],c.upper())
+
+ # Add scale parametrization
+ if planes == [] : planes = ["g17"]
+ if len(planes)>1 : # have G02 and G03 in several planes scale_x = scale_y = scale_z required
+ gcode += "#10 = 1 (Scale factor)\n"
+ scale = {"x":"#10","i":"#10","y":"#10","j":"#10","z":"#10","k":"#10","r":"#10"}
+ else :
+ gcode += "#10 = 1 (%s Scale factor)\n" % ({"g17":"XY","g18":"XZ","g19":"YZ"}[planes[0]])
+ gcode += "#11 = 1 (%s Scale factor)\n" % ({"g17":"Z","g18":"Y","g19":"X"}[planes[0]])
+ scale = {"x":"#10","i":"#10","y":"#10","j":"#10","z":"#10","k":"#10","r":"#10"}
+ if "g17" in planes :
+ scale["z"] = "#11"
+ scale["k"] = "#11"
+ if "g18" in planes :
+ scale["y"] = "#11"
+ scale["j"] = "#11"
+ if "g19" in planes :
+ scale["x"] = "#11"
+ scale["i"] = "#11"
+ # Add a scale
+ if "a" in coords:
+ gcode += "#12 = 1 (A axis scale)\n"
+ scale["a"] = "#12"
+
+ # Add feed parametrization
+ for f in feeds :
+ gcode += "%s = %f (Feed definition)\n" % (feeds[f],f)
+
+ # Parameterize Gcode
+ for s in self.gcode.split("\n"):
+ #feed replace :
+ r = re.search(r"(?i)(F)\s*(-?)\s*(\d*\.?\d*)", s)
+ if r and len(r.group(3))>0:
+ s = re.sub(r"(?i)(F)\s*(-?)\s*(\d*\.?\d*)", "F [%s]"%feeds[float(r.group(2)+r.group(3))], s)
+ #Coords XYZA replace
+ for c in "xyza" :
+ r = re.search(r"(?i)(("+c+r")\s*(-?)\s*(\d*\.?\d*))", s)
+ if r and len(r.group(4))>0:
+ s = re.sub(r"(?i)("+c+r")\s*((-?)\s*(\d*\.?\d*))", r"\1[\2*%s+%s]"%(scale[c],offset[c]), s)
+
+ #Coords IJKR replace
+ for c in "ijkr" :
+ r = re.search(r"(?i)(("+c+r")\s*(-?)\s*(\d*\.?\d*))", s)
+ if r and len(r.group(4))>0:
+ s = re.sub(r"(?i)("+c+r")\s*((-?)\s*(\d*\.?\d*))", r"\1[\2*%s]"%scale[c], s)
+
+ gcode += s + "\n"
+
+ self.gcode = gcode
+
+
+ def round_coordinates(self,parameters) :
+ try:
+ round_ = int(parameters)
+ except :
+ self.error("Bad parameters for round. Round should be an integer! \n(Parameters: '%s')"%(parameters), "error")
+ gcode = ""
+ for s in self.gcode.split("\n"):
+ for a in "xyzijkaf" :
+ r = re.search(r"(?i)("+a+r")\s*(-?\s*(\d*\.?\d*))", s)
+ if r :
+
+ if r.group(2)!="":
+ s = re.sub(
+ r"(?i)("+a+r")\s*(-?)\s*(\d*\.?\d*)",
+ (r"\1 %0."+str(round_)+"f" if round_>0 else r"\1 %d")%round(float(r.group(2)),round_),
+ s)
+ gcode += s + "\n"
+ self.gcode = gcode
+
+
+ def scale(self, parameters):
+ parameters = parameters.split(",")
+ scale = [1.,1.,1.,1.]
+ try :
+ for i in range(len(parameters)) :
+ if float(parameters[i])==0 :
+ self.error("Bad parameters for scale. Scale should not be 0 at any axis! \n(Parameters: '%s')"%(parameters), "error")
+ scale[i] = float(parameters[i])
+ except :
+ self.error("Bad parameters for scale.\n(Parameters: '%s')"%(parameters), "error")
+ self.transform([0,0,0,0],scale)
+
+
+ def move(self, parameters):
+ parameters = parameters.split(",")
+ move = [0.,0.,0.,0.]
+ try :
+ for i in range(len(parameters)) :
+ move[i] = float(parameters[i])
+ except :
+ self.error("Bad parameters for move.\n(Parameters: '%s')"%(parameters), "error")
+ self.transform(move,[1.,1.,1.,1.])
+
+
+ def flip_axis(self, parameters):
+ parameters = parameters.lower()
+ axis = {"x":1.,"y":1.,"z":1.,"a":1.}
+ for p in parameters:
+ if p in [","," "," ","\r","'",'"'] : continue
+ if p not in ["x","y","z","a"] :
+ self.error("Bad parameters for flip_axis. Parameter should be string consists of 'xyza' \n(Parameters: '%s')"%(parameters), "error")
+ axis[p] = -axis[p]
+ self.scale("%f,%f,%f,%f"%(axis["x"],axis["y"],axis["z"],axis["a"]))
+
+
+
+################################################################################
+### Polygon class
+################################################################################
+class Polygon:
+ def __init__(self, polygon=None):
+ self.polygon = [] if polygon==None else polygon[:]
+
+
+ def move(self, x, y) :
+ for i in range(len(self.polygon)) :
+ for j in range(len(self.polygon[i])) :
+ self.polygon[i][j][0] += x
+ self.polygon[i][j][1] += y
+
+
+ def bounds(self) :
+ minx,miny,maxx,maxy = 1e400, 1e400, -1e400, -1e400
+ for poly in self.polygon :
+ for p in poly :
+ if minx > p[0] : minx = p[0]
+ if miny > p[1] : miny = p[1]
+ if maxx < p[0] : maxx = p[0]
+ if maxy < p[1] : maxy = p[1]
+ return minx*1,miny*1,maxx*1,maxy*1
+
+
+ def width(self):
+ b = self.bounds()
+ return b[2]-b[0]
+
+
+ def rotate_(self,sin,cos) :
+ for i in range(len(self.polygon)) :
+ for j in range(len(self.polygon[i])) :
+ x,y = self.polygon[i][j][0], self.polygon[i][j][1]
+ self.polygon[i][j][0] = x*cos - y*sin
+ self.polygon[i][j][1] = x*sin + y*cos
+
+
+ def rotate(self, a):
+ cos, sin = math.cos(a), math.sin(a)
+ self.rotate_(sin,cos)
+
+
+ def drop_into_direction(self, direction, surface) :
+ # Polygon is a list of simple polygons
+ # Surface is a polygon + line y = 0
+ # Direction is [dx,dy]
+ if len(self.polygon) == 0 or len(self.polygon[0])==0 : return
+ if direction[0]**2 + direction[1]**2 <1e-10 : return
+ direction = normalize(direction)
+ sin,cos = direction[0], -direction[1]
+ self.rotate_(-sin,cos)
+ surface.rotate_(-sin,cos)
+ self.drop_down(surface, zerro_plane = False)
+ self.rotate_(sin,cos)
+ surface.rotate_(sin,cos)
+
+
+ def centroid(self):
+ centroids = []
+ sa = 0
+ for poly in self.polygon:
+ cx,cy,a = 0,0,0
+ for i in range(len(poly)):
+ [x1,y1],[x2,y2] = poly[i-1],poly[i]
+ cx += (x1+x2)*(x1*y2-x2*y1)
+ cy += (y1+y2)*(x1*y2-x2*y1)
+ a += (x1*y2-x2*y1)
+ a *= 3.
+ if abs(a)>0 :
+ cx /= a
+ cy /= a
+ sa += abs(a)
+ centroids += [ [cx,cy,a] ]
+ if sa == 0 : return [0.,0.]
+ cx,cy = 0.,0.
+ for c in centroids :
+ cx += c[0]*c[2]
+ cy += c[1]*c[2]
+ cx /= sa
+ cy /= sa
+ return [cx,cy]
+
+
+ def drop_down(self, surface, zerro_plane = True) :
+ # Polygon is a list of simple polygons
+ # Surface is a polygon + line y = 0
+ # Down means min y (0,-1)
+ if len(self.polygon) == 0 or len(self.polygon[0])==0 : return
+ # Get surface top point
+ top = surface.bounds()[3]
+ if zerro_plane : top = max(0, top)
+ # Get polygon bottom point
+ bottom = self.bounds()[1]
+ self.move(0, top - bottom + 10)
+ # Now get shortest distance from surface to polygon in positive x=0 direction
+ # Such distance = min(distance(vertex, edge)...) where edge from surface and
+ # vertex from polygon and vice versa...
+ dist = 1e300
+ for poly in surface.polygon :
+ for i in range(len(poly)) :
+ for poly1 in self.polygon :
+ for i1 in range(len(poly1)) :
+ st,end = poly[i-1], poly[i]
+ vertex = poly1[i1]
+ if st[0]<=vertex[0]<= end[0] or end[0]<=vertex[0]<=st[0] :
+ if st[0]==end[0] : d = min(vertex[1]-st[1],vertex[1]-end[1])
+ else : d = vertex[1] - st[1] - (end[1]-st[1])*(vertex[0]-st[0])/(end[0]-st[0])
+ if dist > d : dist = d
+ # and vice versa just change the sign because vertex now under the edge
+ st,end = poly1[i1-1], poly1[i1]
+ vertex = poly[i]
+ if st[0]<=vertex[0]<=end[0] or end[0]<=vertex[0]<=st[0] :
+ if st[0]==end[0] : d = min(- vertex[1]+st[1],-vertex[1]+end[1])
+ else : d = - vertex[1] + st[1] + (end[1]-st[1])*(vertex[0]-st[0])/(end[0]-st[0])
+ if dist > d : dist = d
+
+ if zerro_plane and dist > 10 + top : dist = 10 + top
+ #print_(dist, top, bottom)
+ #self.draw()
+ self.move(0, -dist)
+
+
+ def draw(self,color="#075",width=.1) :
+ for poly in self.polygon :
+ csp_draw( [csp_subpath_line_to([],poly+[poly[0]])], color=color,width=width )
+
+
+ def add(self, add) :
+ if type(add) == type([]) :
+ self.polygon += add[:]
+ else :
+ self.polygon += add.polygon[:]
+
+
+ def point_inside(self,p) :
+ inside = False
+ for poly in self.polygon :
+ for i in range(len(poly)):
+ st,end = poly[i-1], poly[i]
+ if p==st or p==end : return True # point is a vertex = point is on the edge
+ if st[0]>end[0] : st, end = end, st # This will be needed to check that edge if open only at rigth end
+ c = (p[1]-st[1])*(end[0]-st[0])-(end[1]-st[1])*(p[0]-st[0])
+ #print_(c)
+ if st[0]<=p[0]<end[0] :
+ if c<0 :
+ inside = not inside
+ elif c == 0 : return True # point is on the edge
+ elif st[0]==end[0]==p[0] and (st[1]<=p[1]<=end[1] or end[1]<=p[1]<=st[1]) : # point is on the edge
+ return True
+ return inside
+
+
+ def hull(self) :
+ # Add vertices at all self intersection points.
+ hull = []
+ for i1 in range(len(self.polygon)):
+ poly1 = self.polygon[i1]
+ poly_ = []
+ for j1 in range(len(poly1)):
+ s, e = poly1[j1-1],poly1[j1]
+ poly_ += [s]
+
+ # Check self intersections
+ for j2 in range(j1+1,len(poly1)):
+ s1, e1 = poly1[j2-1],poly1[j2]
+ int_ = line_line_intersection_points(s,e,s1,e1)
+ for p in int_ :
+ if point_to_point_d2(p,s)>0.000001 and point_to_point_d2(p,e)>0.000001 :
+ poly_ += [p]
+ # Check self intersections with other polys
+ for i2 in range(len(self.polygon)):
+ if i1==i2 : continue
+ poly2 = self.polygon[i2]
+ for j2 in range(len(poly2)):
+ s1, e1 = poly2[j2-1],poly2[j2]
+ int_ = line_line_intersection_points(s,e,s1,e1)
+ for p in int_ :
+ if point_to_point_d2(p,s)>0.000001 and point_to_point_d2(p,e)>0.000001 :
+ poly_ += [p]
+ hull += [poly_]
+ # Create the dictionary containing all edges in both directions
+ edges = {}
+ for poly in self.polygon :
+ for i in range(len(poly)):
+ s,e = tuple(poly[i-1]), tuple(poly[i])
+ if (point_to_point_d2(e,s)<0.000001) : continue
+ break_s, break_e = False, False
+ for p in edges :
+ if point_to_point_d2(p,s)<0.000001 :
+ break_s = True
+ s = p
+ if point_to_point_d2(p,e)<0.000001 :
+ break_e = True
+ e = p
+ if break_s and break_e : break
+ l = point_to_point_d(s,e)
+ if not break_s and not break_e :
+ edges[s] = [ [s,e,l] ]
+ edges[e] = [ [e,s,l] ]
+ #draw_pointer(s+e,"red","line")
+ #draw_pointer(s+e,"red","line")
+ else :
+ if e in edges :
+ for edge in edges[e] :
+ if point_to_point_d2(edge[1],s)<0.000001 :
+ break
+ if point_to_point_d2(edge[1],s)>0.000001 :
+ edges[e] += [ [e,s,l] ]
+ #draw_pointer(s+e,"red","line")
+
+ else :
+ edges[e] = [ [e,s,l] ]
+ #draw_pointer(s+e,"green","line")
+ if s in edges :
+ for edge in edges[s] :
+ if point_to_point_d2(edge[1],e)<0.000001 :
+ break
+ if point_to_point_d2(edge[1],e)>0.000001 :
+ edges[s] += [ [s,e, l] ]
+ #draw_pointer(s+e,"red","line")
+ else :
+ edges[s] = [ [s,e,l] ]
+ #draw_pointer(s+e,"green","line")
+
+
+ def angle_quadrant(sin,cos):
+ # quadrants are (0,pi/2], (pi/2,pi], (pi,3*pi/2], (3*pi/2, 2*pi], i.e. 0 is in the 4-th quadrant
+ if sin>0 and cos>=0 : return 1
+ if sin>=0 and cos<0 : return 2
+ if sin<0 and cos<=0 : return 3
+ if sin<=0 and cos>0 : return 4
+
+
+ def angle_is_less(sin,cos,sin1,cos1):
+ # 0 = 2*pi is the largest angle
+ if [sin1, cos1] == [0,1] : return True
+ if [sin, cos] == [0,1] : return False
+ if angle_quadrant(sin,cos)>angle_quadrant(sin1,cos1) :
+ return False
+ if angle_quadrant(sin,cos)<angle_quadrant(sin1,cos1) :
+ return True
+ if sin>=0 and cos>0 : return sin<sin1
+ if sin>0 and cos<=0 : return sin>sin1
+ if sin<=0 and cos<0 : return sin>sin1
+ if sin<0 and cos>=0 : return sin<sin1
+
+
+ def get_closes_edge_by_angle(edges, last):
+ # Last edge is normalized vector of the last edge.
+ min_angle = [0,1]
+ next = last
+ last_edge = [(last[0][0]-last[1][0])/last[2], (last[0][1]-last[1][1])/last[2]]
+ for p in edges:
+ #draw_pointer(list(p[0])+[p[0][0]+last_edge[0]*40,p[0][1]+last_edge[1]*40], "Red", "line", width=1)
+ #print_("len(edges)=",len(edges))
+ cur = [(p[1][0]-p[0][0])/p[2],(p[1][1]-p[0][1])/p[2]]
+ cos, sin = dot(cur,last_edge), cross(cur,last_edge)
+ #draw_pointer(list(p[0])+[p[0][0]+cur[0]*40,p[0][1]+cur[1]*40], "Orange", "line", width=1, comment = [sin,cos])
+ #print_("cos, sin=",cos,sin)
+ #print_("min_angle_before=",min_angle)
+
+ if angle_is_less(sin,cos,min_angle[0],min_angle[1]) :
+ min_angle = [sin,cos]
+ next = p
+ #print_("min_angle=",min_angle)
+
+ return next
+
+ # Join edges together into new polygon cutting the vertexes inside new polygon
+ self.polygon = []
+ len_edges = sum([len(edges[p]) for p in edges])
+ loops = 0
+
+ while len(edges)>0 :
+ poly = []
+ if loops > len_edges : raise ValueError, "Hull error"
+ loops+=1
+ # Find left most vertex.
+ start = (1e100,1)
+ for edge in edges :
+ start = min(start, min(edges[edge]))
+ last = [(start[0][0]-1,start[0][1]),start[0],1]
+ first_run = True
+ loops1 = 0
+ while (last[1]!=start[0] or first_run) :
+ first_run = False
+ if loops1 > len_edges : raise ValueError, "Hull error"
+ loops1 += 1
+ next = get_closes_edge_by_angle(edges[last[1]],last)
+ #draw_pointer(next[0]+next[1],"Green","line", comment=i, width= 1)
+ #print_(next[0],"-",next[1])
+
+ last = next
+ poly += [ list(last[0]) ]
+ self.polygon += [ poly ]
+ # Remove all edges that are intersects new poly (any vertex inside new poly)
+ poly_ = Polygon([poly])
+ for p in edges.keys()[:] :
+ if poly_.point_inside(list(p)) : del edges[p]
+ self.draw(color="Green", width=1)
+
+
+class Arangement_Genetic:
+ # gene = [fittness, order, rotation, xposition]
+ # spieces = [gene]*shapes count
+ # population = [spieces]
+ def __init__(self, polygons, material_width):
+ self.population = []
+ self.genes_count = len(polygons)
+ self.polygons = polygons
+ self.width = material_width
+ self.mutation_factor = 0.1
+ self.order_mutate_factor = 1.
+ self.move_mutate_factor = 1.
+
+
+ def add_random_species(self,count):
+ for i in range(count):
+ specimen = []
+ order = range(self.genes_count)
+ random.shuffle(order)
+ for j in order:
+ specimen += [ [j, random.random(), random.random()] ]
+ self.population += [ [None,specimen] ]
+
+
+ def species_distance2(self,sp1,sp2) :
+ # retun distance, each component is normalized
+ s = 0
+ for j in range(self.genes_count) :
+ s += ((sp1[j][0]-sp2[j][0])/self.genes_count)**2 + (( sp1[j][1]-sp2[j][1]))**2 + ((sp1[j][2]-sp2[j][2]))**2
+ return s
+
+
+ def similarity(self,sp1,top) :
+ # Define similarity as a simple distance between two points in len(gene)*len(spiece) -th dimentions
+ # for sp2 in top_spieces sum(|sp1-sp2|)/top_count
+ sim = 0
+ for sp2 in top :
+ sim += math.sqrt(species_distance2(sp1,sp2[1]))
+ return sim/len(top)
+
+
+ def leave_top_species(self,count):
+ self.population.sort()
+ res = [ copy.deepcopy(self.population[0]) ]
+ del self.population[0]
+ for i in range(count-1) :
+ t = []
+ for j in range(20) :
+ i1 = random.randint(0,len(self.population)-1)
+ t += [ [self.population[i1][0],i1] ]
+ t.sort()
+ res += [ copy.deepcopy(self.population[t[0][1]]) ]
+ del self.population[t[0][1]]
+ self.population = res
+ #del self.population[0]
+ #for c in range(count-1) :
+ # rank = []
+ # for i in range(len(self.population)) :
+ # sim = self.similarity(self.population[i][1],res)
+ # rank += [ [self.population[i][0] / sim if sim>0 else 1e100,i] ]
+ # rank.sort()
+ # res += [ copy.deepcopy(self.population[rank[0][1]]) ]
+ # print_(rank[0],self.population[rank[0][1]][0])
+ # print_(res[-1])
+ # del self.population[rank[0][1]]
+
+ self.population = res
+
+
+ def populate_species(self,count, parent_count):
+ self.population.sort()
+ self.inc = 0
+ for c in range(count):
+ parent1 = random.randint(0,parent_count-1)
+ parent2 = random.randint(0,parent_count-1)
+ if parent1==parent2 : parent2 = (parent2+1) % parent_count
+ parent1, parent2 = self.population[parent1][1], self.population[parent2][1]
+ i1,i2 = 0, 0
+ genes_order = []
+ specimen = [ [0,0.,0.] for i in range(self.genes_count) ]
+
+ self.incest_mutation_multiplyer = 1.
+ self.incest_mutation_count_multiplyer = 1.
+
+ if self.species_distance2(parent1, parent2) <= .01/self.genes_count :
+ # OMG it's a incest :O!!!
+ # Damn you bastards!
+ self.inc +=1
+ self.incest_mutation_multiplyer = 2.
+ self.incest_mutation_count_multiplyer = 2.
+ else :
+ if random.random()<.01 : print_(self.species_distance2(parent1, parent2))
+ start_gene = random.randint(0,self.genes_count)
+ end_gene = (max(1,random.randint(0,self.genes_count),int(self.genes_count/4))+start_gene) % self.genes_count
+ if end_gene<start_gene :
+ end_gene, start_gene = start_gene, end_gene
+ parent1, parent2 = parent2, parent1
+ for i in range(start_gene,end_gene) :
+ #rotation_mutate_param = random.random()/100
+ #xposition_mutate_param = random.random()/100
+ tr = 1. #- rotation_mutate_param
+ tp = 1. #- xposition_mutate_param
+ specimen[i] = [parent1[i][0], parent1[i][1]*tr+parent2[i][1]*(1-tr),parent1[i][2]*tp+parent2[i][2]*(1-tp)]
+ genes_order += [ parent1[i][0] ]
+
+ for i in range(0,start_gene)+range(end_gene,self.genes_count) :
+ tr = 0. #rotation_mutate_param
+ tp = 0. #xposition_mutate_param
+ j = i
+ while parent2[j][0] in genes_order :
+ j = (j+1)%self.genes_count
+ specimen[i] = [parent2[j][0], parent1[i][1]*tr+parent2[i][1]*(1-tr),parent1[i][2]*tp+parent2[i][2]*(1-tp)]
+ genes_order += [ parent2[j][0] ]
+
+
+ for i in range(random.randint(self.mutation_genes_count[0],self.mutation_genes_count[0]*self.incest_mutation_count_multiplyer )) :
+ if random.random() < self.order_mutate_factor * self.incest_mutation_multiplyer :
+ i1,i2 = random.randint(0,self.genes_count-1),random.randint(0,self.genes_count-1)
+ specimen[i1][0], specimen[i2][0] = specimen[i2][0], specimen[i1][0]
+ if random.random() < self.move_mutation_factor * self.incest_mutation_multiplyer:
+ i1 = random.randint(0,self.genes_count-1)
+ specimen[i1][1] = (specimen[i1][1]+random.random()*math.pi2*self.move_mutation_multiplier)%1.
+ specimen[i1][2] = (specimen[i1][2]+random.random()*self.move_mutation_multiplier)%1.
+ self.population += [ [None,specimen] ]
+
+
+ def test_spiece_drop_down(self,spiece) :
+ surface = Polygon()
+ for p in spiece :
+ time_ = time.time()
+ poly = Polygon(copy.deepcopy(self.polygons[p[0]].polygon))
+ poly.rotate(p[1]*math.pi2)
+ w = poly.width()
+ left = poly.bounds()[0]
+ poly.move( -left + (self.width-w)*p[2],0)
+ poly.drop_down(surface)
+ surface.add(poly)
+ return surface
+
+
+ def test(self,test_function):
+ for i in range(len(self.population)) :
+ if self.population[i][0] == None :
+ surface = test_function(self.population[i][1])
+ b = surface.bounds()
+ self.population[i][0] = (b[3]-b[1])*(b[2]-b[0])
+ self.population.sort()
+
+
+ def test_spiece_centroid(self,spiece) :
+ poly = Polygon(copy.deepcopy(self.polygons[spiece[0][0]].polygon))
+ poly.rotate(spiece[0][2]*math.pi2)
+ surface = Polygon(poly.polygon)
+ i = 0
+ for p in spiece[1:] :
+ i += 1
+ poly = Polygon(copy.deepcopy(self.polygons[p[0]].polygon))
+ poly.rotate(p[2]*math.pi2)
+ c = surface.centroid()
+ c1 = poly.centroid()
+ direction = [math.cos(p[1]*math.pi2), -math.sin(p[1]*math.pi2)]
+ poly.move(c[0]-c1[0]-direction[0]*100,c[1]-c1[1]-direction[1]*100)
+ poly.drop_into_direction(direction,surface)
+ surface.add(poly)
+ return surface
+
+
+
+ #surface.draw()
+
+
+################################################################################
+###
+### Gcodetools class
+###
+################################################################################
+
+class Gcodetools(inkex.Effect):
+
+ def export_gcode(self,gcode) :
+ if self.options.postprocessor != "" or self.options.postprocessor_custom != "" :
+ postprocessor = Postprocessor(self.error)
+ postprocessor.gcode = gcode
+ if self.options.postprocessor != "" :
+ postprocessor.process(self.options.postprocessor)
+ if self.options.postprocessor_custom != "" :
+ postprocessor.process(self.options.postprocessor_custom)
+ postprocessor.gcode = self.header + postprocessor.gcode + self.footer
+ f = open(self.options.directory+self.options.file, "w")
+ f.write(postprocessor.gcode)
+ f.close()
+
+
+################################################################################
+### Arrangement: arranges paths by givven params
+### TODO move it to the bottom
+################################################################################
+ def arrangement(self) :
+ paths = self.selected_paths
+ surface = Polygon()
+ polygons = []
+ time_ = time.time()
+ print_("Arrangement start at %s"%(time_))
+ original_paths = []
+ for layer in self.layers :
+ if layer in paths :
+ for path in paths[layer] :
+ csp = cubicsuperpath.parsePath(path.get("d"))
+ polygon = Polygon()
+ for subpath in csp :
+ for sp1, sp2 in zip(subpath,subpath[1:]) :
+ polygon.add([csp_segment_convex_hull(sp1,sp2)])
+ #print_("Redused edges count from", sum([len(poly) for poly in polygon.polygon ]) )
+ polygon.hull()
+ original_paths += [path]
+ polygons += [polygon]
+
+ print_("Paths hull computed in %s sec."%(time.time()-time_))
+ print_("Got %s polygons having average %s edges each."% ( len(polygons), float(sum([ sum([len(poly) for poly in polygon.polygon]) for polygon in polygons ])) / len(polygons) ) )
+ time_ = time.time()
+
+# material_width = self.options.arrangement_material_width
+# population = Arangement_Genetic(polygons, material_width)
+# population.add_random_species(1)
+# population.test_population_centroid()
+## return
+ material_width = self.options.arrangement_material_width
+ population = Arangement_Genetic(polygons, material_width)
+
+
+ print_("Genetic alhorithm start at %s"%(time_))
+ time_ = time.time()
+
+
+
+ population.add_random_species(50)
+ population.test(population.test_spiece_centroid)
+ print_("Initial population done in %s"%(time.time()-time_))
+ time_ = time.time()
+ pop = copy.deepcopy(population)
+ population_count = self.options.arrangement_population_count
+ last_champ = []
+ champions_count = 0
+
+
+
+
+ for i in range(population_count):
+ population.leave_top_species(20)
+ population.move_mutation_multiplier = random.random()/2
+
+ population.order_mutation_factor = .2
+ population.move_mutation_factor = 1.
+ population.mutation_genes_count = [1,2]
+ population.populate_species(250, 20)
+ """
+ randomize = i%100 < 40
+ if i%100 < 40 :
+ population.add_random_species(250)
+ if 40<= i%100 < 100 :
+ population.mutation_genes_count = [1,max(2,int(population.genes_count/4))] #[1,max(2,int(population.genes_count/2))] if 40<=i%100<60 else [1,max(2,int(population.genes_count/10))]
+ population.move_mutation_multiplier = 1. if 40<=i%100<80 else .1
+ population.move_mutation_factor = (-(i%100)/30+10/3) if 50<=i%100<100 else .5
+ population.order_mutation_factor = 1./(i%100-79) if 80<=i%100<100 else 1.
+ population.populate_species(250, 10)
+ """
+ population.test(population.test_spiece_centroid)
+ draw_new_champ = False
+ print_()
+ for x in population.population[:10]:
+ print_(x[0])
+
+ if population.population[0][0]!= last_champ :
+ draw_new_champ = True
+ last_champ = population.population[0][0]*1
+
+
+ k = ""
+ #for j in range(10) :
+ # k += "%s " % population.population[j][0]
+ print_("Cicle %s done in %s"%(i,time.time()-time_))
+ time_ = time.time()
+ print_("%s incests been found"%population.inc)
+ print_()
+ #print_(k)
+ #print_()
+ if i == 0 or i == population_count-1 or draw_new_champ :
+ colors = ["blue"]
+
+ surface = population.test_spiece_centroid(population.population[0][1])
+ b = surface.bounds()
+ x,y = 400* (champions_count%10), 700*int(champions_count/10)
+ surface.move(x-b[0],y-b[1])
+ surface.draw(width=2, color=colors[0])
+ draw_text("Step = %s\nSquare = %f"%(i,(b[2]-b[0])*(b[3]-b[1])),x,y-40)
+ champions_count += 1
+
+ spiece = population.population[0][1]
+ poly = Polygon(copy.deepcopy(population.polygons[spiece[0][0]].polygon))
+ poly.rotate(spiece[0][2]*math.pi2)
+ surface = Polygon(poly.polygon)
+ poly.draw(width = 2, color= "Violet")
+ for p in spiece[1:] :
+ poly = Polygon(copy.deepcopy(population.polygons[p[0]].polygon))
+ poly.rotate(p[2]*math.pi2)
+ direction = [math.cos(p[1]*math.pi2), -math.sin(p[1]*math.pi2)]
+ normalize(direction)
+ c = surface.centroid()
+ c1 = poly.centroid()
+ poly.move(c[0]-c1[0]-direction[0]*400,c[1]-c1[1]-direction[1]*400)
+ c = surface.centroid()
+ c1 = poly.centroid()
+ poly.draw(width = 5, color= "Violet")
+ draw_pointer(c+c1,"Green","line")
+ direction = normalize(direction)
+
+
+ sin,cos = direction[0], direction[1]
+ poly.rotate_(-sin,cos)
+ surface.rotate_(-sin,cos)
+# poly.draw(color = "Violet",width=4)
+ surface.draw(color = "Orange",width=4)
+ poly.rotate_(sin,cos)
+ surface.rotate_(sin,cos)
+
+
+ poly.drop_into_direction(direction,surface)
+ surface.add(poly)
+
+
+ # Now we'll need apply transforms to original paths
+
+
+ def __init__(self):
+ inkex.Effect.__init__(self)
+ self.OptionParser.add_option("-d", "--directory", action="store", type="string", dest="directory", default="/home/", help="Directory for gcode file")
+ self.OptionParser.add_option("-f", "--filename", action="store", type="string", dest="file", default="-1.0", help="File name")
+ self.OptionParser.add_option("", "--add-numeric-suffix-to-filename", action="store", type="inkbool", dest="add_numeric_suffix_to_filename", default=True,help="Add numeric suffix to filename")
+ self.OptionParser.add_option("", "--Zscale", action="store", type="float", dest="Zscale", default="1.0", help="Scale factor Z")
+ self.OptionParser.add_option("", "--Zoffset", action="store", type="float", dest="Zoffset", default="0.0", help="Offset along Z")
+ self.OptionParser.add_option("-s", "--Zsafe", action="store", type="float", dest="Zsafe", default="0.5", help="Z above all obstacles")
+ self.OptionParser.add_option("-z", "--Zsurface", action="store", type="float", dest="Zsurface", default="0.0", help="Z of the surface")
+ self.OptionParser.add_option("-c", "--Zdepth", action="store", type="float", dest="Zdepth", default="-0.125", help="Z depth of cut")
+ self.OptionParser.add_option("", "--Zstep", action="store", type="float", dest="Zstep", default="-0.125", help="Z step of cutting")
+ self.OptionParser.add_option("-p", "--feed", action="store", type="float", dest="feed", default="4.0", help="Feed rate in unit/min")
+
+ self.OptionParser.add_option("", "--biarc-tolerance", action="store", type="float", dest="biarc_tolerance", default="1", help="Tolerance used when calculating biarc interpolation.")
+ self.OptionParser.add_option("", "--biarc-max-split-depth", action="store", type="int", dest="biarc_max_split_depth", default="4", help="Defines maximum depth of splitting while approximating using biarcs.")
+
+ self.OptionParser.add_option("", "--tool-diameter", action="store", type="float", dest="tool_diameter", default="3", help="Tool diameter used for area cutting")
+ self.OptionParser.add_option("", "--max-area-curves", action="store", type="int", dest="max_area_curves", default="100", help="Maximum area curves for each area")
+ self.OptionParser.add_option("", "--area-inkscape-radius", action="store", type="float", dest="area_inkscape_radius", default="-10", help="Radius for preparing curves using inkscape")
+ self.OptionParser.add_option("", "--unit", action="store", type="string", dest="unit", default="G21 (All units in mm)", help="Units")
+ self.OptionParser.add_option("", "--active-tab", action="store", type="string", dest="active_tab", default="", help="Defines which tab is active")
+
+ self.OptionParser.add_option("", "--area-find-artefacts-diameter",action="store", type="float", dest="area_find_artefacts_diameter", default="1", help="artefacts seeking radius")
+ self.OptionParser.add_option("", "--area-find-artefacts-action", action="store", type="string", dest="area_find_artefacts_action", default="mark with an arrow", help="artefacts action type")
+
+ self.OptionParser.add_option("", "--auto_select_paths", action="store", type="inkbool", dest="auto_select_paths", default=True, help="Select all paths if nothing is selected.")
+
+ self.OptionParser.add_option("", "--loft-distances", action="store", type="string", dest="loft_distances", default="10", help="Distances between paths.")
+ self.OptionParser.add_option("", "--loft-direction", action="store", type="string", dest="loft_direction", default="crosswise", help="Direction of loft's interpolation.")
+ self.OptionParser.add_option("", "--loft-interpolation-degree", action="store", type="float", dest="loft_interpolation_degree", default="2", help="Which interpolation use to loft the paths smooth interpolation or staright.")
+
+ self.OptionParser.add_option("", "--min-arc-radius", action="store", type="float", dest="min_arc_radius", default=".1", help="All arc having radius less than minimum will be considered as straight line")
+
+ self.OptionParser.add_option("", "--engraving-sharp-angle-tollerance",action="store", type="float", dest="engraving_sharp_angle_tollerance", default="150", help="All angles thar are less than engraving-sharp-angle-tollerance will be thought sharp")
+ self.OptionParser.add_option("", "--engraving-max-dist", action="store", type="float", dest="engraving_max_dist", default="10", help="Distanse from original path where engraving is not needed (usualy it's cutting tool diameter)")
+ self.OptionParser.add_option("", "--engraving-newton-iterations", action="store", type="int", dest="engraving_newton_iterations", default="4", help="Number of sample points used to calculate distance")
+ self.OptionParser.add_option("", "--engraving-draw-calculation-paths",action="store", type="inkbool", dest="engraving_draw_calculation_paths", default=False, help="Draw additional graphics to debug engraving path")
+ self.OptionParser.add_option("", "--engraving-cutter-shape-function",action="store", type="string", dest="engraving_cutter_shape_function", default="w", help="Cutter shape function z(w). Ex. cone: w. ")
+
+ self.OptionParser.add_option("", "--lathe-width", action="store", type="float", dest="lathe_width", default=10., help="Lathe width")
+ self.OptionParser.add_option("", "--lathe-fine-cut-width", action="store", type="float", dest="lathe_fine_cut_width", default=1., help="Fine cut width")
+ self.OptionParser.add_option("", "--lathe-fine-cut-count", action="store", type="int", dest="lathe_fine_cut_count", default=1., help="Fine cut count")
+ self.OptionParser.add_option("", "--lathe-create-fine-cut-using", action="store", type="string", dest="lathe_create_fine_cut_using", default="Move path", help="Create fine cut using")
+ self.OptionParser.add_option("", "--lathe-x-axis-remap", action="store", type="string", dest="lathe_x_axis_remap", default="X", help="Lathe X axis remap")
+ self.OptionParser.add_option("", "--lathe-z-axis-remap", action="store", type="string", dest="lathe_z_axis_remap", default="Z", help="Lathe Z axis remap")
+
+ self.OptionParser.add_option("", "--create-log", action="store", type="inkbool", dest="log_create_log", default=False, help="Create log files")
+ self.OptionParser.add_option("", "--log-filename", action="store", type="string", dest="log_filename", default='', help="Create log files")
+
+ self.OptionParser.add_option("", "--orientation-points-count", action="store", type="int", dest="orientation_points_count", default='2', help="Orientation points count")
+ self.OptionParser.add_option("", "--tools-library-type", action="store", type="string", dest="tools_library_type", default='cylinder cutter', help="Create tools defention")
+
+ self.OptionParser.add_option("", "--dxfpoints-action", action="store", type="string", dest="dxfpoints_action", default='replace', help="dxfpoint sign toggle")
+
+ self.OptionParser.add_option("", "--help-language", action="store", type="string", dest="help_language", default='http://www.cnc-club.ru/forum/viewtopic.php?f=33&t=35', help="Open help page in webbrowser.")
+
+ self.OptionParser.add_option("", "--offset-radius", action="store", type="float", dest="offset_radius", default=10., help="Offset radius")
+ self.OptionParser.add_option("", "--offset-step", action="store", type="float", dest="offset_step", default=10., help="Offset step")
+ self.OptionParser.add_option("", "--offset-draw-clippend-path", action="store", type="inkbool", dest="offset_draw_clippend_path", default=False, help="Draw clipped path")
+ self.OptionParser.add_option("", "--offset-just-get-distance", action="store", type="inkbool", dest="offset_just_get_distance", default=False, help="Don't do offset just get distance")
+
+ self.OptionParser.add_option("", "--arrangement-material-width", action="store", type="float", dest="arrangement_material_width", default=500, help="Materials width for arrangement")
+ self.OptionParser.add_option("", "--arrangement-population-count",action="store", type="int", dest="arrangement_population_count", default=100, help="Genetic algorithm populations count")
+
+ self.OptionParser.add_option("", "--postprocessor", action="store", type="string", dest="postprocessor", default='', help="Postprocessor command.")
+ self.OptionParser.add_option("", "--postprocessor-custom", action="store", type="string", dest="postprocessor_custom", default='', help="Postprocessor custom command.")
+
+
+
+ self.default_tool = {
+ "name": "Default tool",
+ "id": "default tool",
+ "diameter":10.,
+ "shape": "10",
+ "penetration angle":90.,
+ "penetration feed":100.,
+ "depth step":1.,
+ "feed":400.,
+ "in trajectotry":"",
+ "out trajectotry":"",
+ "gcode before path":"",
+ "gcode after path":"",
+ "sog":"",
+ "spinlde rpm":"",
+ "CW or CCW":"",
+ "tool change gcode":" ",
+ "4th axis meaning": " ",
+ "4th axis scale": 1.,
+ "4th axis offset": 0.,
+ "passing feed":"800",
+ "fine feed":"800",
+ }
+ self.tools_field_order = [
+ 'name',
+ 'id',
+ 'diameter',
+ 'feed',
+ 'shape',
+ 'penetration angle',
+ 'penetration feed',
+ "passing feed",
+ 'depth step',
+ "in trajectotry",
+ "out trajectotry",
+ "gcode before path",
+ "gcode after path",
+ "sog",
+ "spinlde rpm",
+ "CW or CCW",
+ "tool change gcode",
+ ]
+
+
+ def parse_curve(self, p, layer, w = None, f = None):
+ c = []
+ if len(p)==0 :
+ return []
+ p = self.transform_csp(p, layer)
+
+
+ ### Sort to reduce Rapid distance
+ k = range(1,len(p))
+ keys = [0]
+ while len(k)>0:
+ end = p[keys[-1]][-1][1]
+ dist = None
+ for i in range(len(k)):
+ start = p[k[i]][0][1]
+ dist = max( ( -( ( end[0]-start[0])**2+(end[1]-start[1])**2 ) ,i) , dist )
+ keys += [k[dist[1]]]
+ del k[dist[1]]
+ for k in keys:
+ subpath = p[k]
+ c += [ [ [subpath[0][1][0],subpath[0][1][1]] , 'move', 0, 0] ]
+ for i in range(1,len(subpath)):
+ sp1 = [ [subpath[i-1][j][0], subpath[i-1][j][1]] for j in range(3)]
+ sp2 = [ [subpath[i ][j][0], subpath[i ][j][1]] for j in range(3)]
+ c += biarc(sp1,sp2,0,0) if w==None else biarc(sp1,sp2,-f(w[k][i-1]),-f(w[k][i]))
+# l1 = biarc(sp1,sp2,0,0) if w==None else biarc(sp1,sp2,-f(w[k][i-1]),-f(w[k][i]))
+# print_((-f(w[k][i-1]),-f(w[k][i]), [i1[5] for i1 in l1]) )
+ c += [ [ [subpath[-1][1][0],subpath[-1][1][1]] ,'end',0,0] ]
+ return c
+
+
+ def draw_curve(self, curve, layer, group=None, style=styles["biarc_style"]):
+
+ self.get_defs()
+ # Add marker to defs if it doesnot exists
+ if "DrawCurveMarker" not in self.defs :
+ defs = inkex.etree.SubElement( self.document.getroot(), inkex.addNS("defs","svg"))
+ marker = inkex.etree.SubElement( defs, inkex.addNS("marker","svg"), {"id":"DrawCurveMarker","orient":"auto","refX":"-8","refY":"-2.41063","style":"overflow:visible"})
+ inkex.etree.SubElement( marker, inkex.addNS("path","svg"),
+ { "d":"m -6.55552,-2.41063 0,0 L -13.11104,0 c 1.0473,-1.42323 1.04126,-3.37047 0,-4.82126",
+ "style": "fill:#000044; fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;" }
+ )
+ if "DrawCurveMarker_r" not in self.defs :
+ defs = inkex.etree.SubElement( self.document.getroot(), inkex.addNS("defs","svg"))
+ marker = inkex.etree.SubElement( defs, inkex.addNS("marker","svg"), {"id":"DrawCurveMarker_r","orient":"auto","refX":"8","refY":"-2.41063","style":"overflow:visible"})
+ inkex.etree.SubElement( marker, inkex.addNS("path","svg"),
+ { "d":"m 6.55552,-2.41063 0,0 L 13.11104,0 c -1.0473,-1.42323 -1.04126,-3.37047 0,-4.82126",
+ "style": "fill:#000044; fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;" }
+ )
+ for i in [0,1]:
+ style['biarc%s_r'%i] = simplestyle.parseStyle(style['biarc%s'%i])
+ style['biarc%s_r'%i]["marker-start"] = "url(#DrawCurveMarker_r)"
+ del(style['biarc%s_r'%i]["marker-end"])
+ style['biarc%s_r'%i] = simplestyle.formatStyle(style['biarc%s_r'%i])
+
+ if group==None:
+ group = inkex.etree.SubElement( self.layers[min(1,len(self.layers)-1)], inkex.addNS('g','svg'), {"gcodetools": "Preview group"} )
+ s, arcn = '', 0
+
+
+ a,b,c = [0.,0.], [1.,0.], [0.,1.]
+ k = (b[0]-a[0])*(c[1]-a[1])-(c[0]-a[0])*(b[1]-a[1])
+ a,b,c = self.transform(a, layer, True), self.transform(b, layer, True), self.transform(c, layer, True)
+ if ((b[0]-a[0])*(c[1]-a[1])-(c[0]-a[0])*(b[1]-a[1]))*k > 0 : reverse_angle = 1
+ else : reverse_angle = -1
+ for sk in curve:
+ si = sk[:]
+ si[0], si[2] = self.transform(si[0], layer, True), (self.transform(si[2], layer, True) if type(si[2])==type([]) and len(si[2])==2 else si[2])
+
+ if s!='':
+ if s[1] == 'line':
+ inkex.etree.SubElement( group, inkex.addNS('path','svg'),
+ {
+ 'style': style['line'],
+ 'd':'M %s,%s L %s,%s' % (s[0][0], s[0][1], si[0][0], si[0][1]),
+ "gcodetools": "Preview",
+ }
+ )
+ elif s[1] == 'arc':
+ arcn += 1
+ sp = s[0]
+ c = s[2]
+ s[3] = s[3]*reverse_angle
+
+ a = ( (P(si[0])-P(c)).angle() - (P(s[0])-P(c)).angle() )%math.pi2 #s[3]
+ if s[3]*a<0:
+ if a>0: a = a-math.pi2
+ else: a = math.pi2+a
+ r = math.sqrt( (sp[0]-c[0])**2 + (sp[1]-c[1])**2 )
+ a_st = ( math.atan2(sp[0]-c[0],- (sp[1]-c[1])) - math.pi/2 ) % (math.pi*2)
+ st = style['biarc%s' % (arcn%2)][:]
+ if a>0:
+ a_end = a_st+a
+ st = style['biarc%s'%(arcn%2)]
+ else:
+ a_end = a_st*1
+ a_st = a_st+a
+ st = style['biarc%s_r'%(arcn%2)]
+ inkex.etree.SubElement( group, inkex.addNS('path','svg'),
+ {
+ 'style': st,
+ inkex.addNS('cx','sodipodi'): str(c[0]),
+ inkex.addNS('cy','sodipodi'): str(c[1]),
+ inkex.addNS('rx','sodipodi'): str(r),
+ inkex.addNS('ry','sodipodi'): str(r),
+ inkex.addNS('start','sodipodi'): str(a_st),
+ inkex.addNS('end','sodipodi'): str(a_end),
+ inkex.addNS('open','sodipodi'): 'true',
+ inkex.addNS('type','sodipodi'): 'arc',
+ "gcodetools": "Preview",
+ })
+ s = si
+
+
+ def check_dir(self):
+ if self.options.directory[-1] not in ["/","\\"]:
+ if "\\" in self.options.directory :
+ self.options.directory += "\\"
+ else :
+ self.options.directory += "/"
+ print_("Checking direcrory: '%s'"%self.options.directory)
+ if (os.path.isdir(self.options.directory)):
+ if (os.path.isfile(self.options.directory+'header')):
+ f = open(self.options.directory+slash+'header', 'r')
+ self.header = f.read()
+ f.close()
+ else:
+ self.header = defaults['header']
+ if (os.path.isfile(self.options.directory+'footer')):
+ f = open(self.options.directory+'footer','r')
+ self.footer = f.read()
+ f.close()
+ else:
+ self.footer = defaults['footer']
+ self.header += self.options.unit + "\n"
+ else:
+ self.error(_("Directory does not exist! Please specify existing directory at Preferences tab!"),"error")
+ return False
+
+ if self.options.add_numeric_suffix_to_filename :
+ dir_list = os.listdir(self.options.directory)
+ if "." in self.options.file :
+ r = re.match(r"^(.*)(\..*)$",self.options.file)
+ ext = r.group(2)
+ name = r.group(1)
+ else:
+ ext = ""
+ name = self.options.file
+ max_n = 0
+ for s in dir_list :
+ r = re.match(r"^%s_0*(\d+)%s$"%(re.escape(name),re.escape(ext) ), s)
+ if r :
+ max_n = max(max_n,int(r.group(1)))
+ filename = name + "_" + ( "0"*(4-len(str(max_n+1))) + str(max_n+1) ) + ext
+ self.options.file = filename
+
+ print_("Testing writing rights on '%s'"%(self.options.directory+self.options.file))
+ try:
+ f = open(self.options.directory+self.options.file, "w")
+ f.close()
+ except:
+ self.error(_("Can not write to specified file!\n%s"%(self.options.directory+self.options.file)),"error")
+ return False
+ return True
+
+
+
+################################################################################
+###
+### Generate Gcode
+### Generates Gcode on given curve.
+###
+### Crve defenitnion [start point, type = {'arc','line','move','end'}, arc center, arc angle, end point, [zstart, zend]]
+###
+################################################################################
+ def generate_gcode(self, curve, layer, depth):
+ Zauto_scale = self.Zauto_scale[layer]
+ tool = self.tools[layer][0]
+
+ def c(c):
+ c = [c[i] if i<len(c) else None for i in range(6)]
+ if c[5] == 0 : c[5]=None
+ s,s1 = [" X", " Y", " Z", " I", " J", " K"], ["","","","","",""]
+ m,a = [1,1,self.options.Zscale*Zauto_scale,1,1,self.options.Zscale*Zauto_scale], [0,0,self.options.Zoffset,0,0,0]
+ r = ''
+ for i in range(6):
+ if c[i]!=None:
+ r += s[i] + ("%f" % (c[i]*m[i]+a[i])) + s1[i]
+ return r
+
+ def calculate_angle(a, current_a):
+ return min(
+ [abs(a-current_a%math.pi2+math.pi2), a+current_a-current_a%math.pi2+math.pi2],
+ [abs(a-current_a%math.pi2-math.pi2), a+current_a-current_a%math.pi2-math.pi2],
+ [abs(a-current_a%math.pi2), a+current_a-current_a%math.pi2])[1]
+ if len(curve)==0 : return ""
+
+ try :
+ self.last_used_tool == None
+ except :
+ self.last_used_tool = None
+ print_("working on curve")
+ print_(curve)
+ g = tool['tool change gcode'] +"\n" if tool != self.last_used_tool else "\n"
+
+ lg, zs, f = 'G00', self.options.Zsafe, " F%f"%tool['feed']
+ current_a = 0
+ go_to_safe_distance = "G00" + c([None,None,zs]) + "\n"
+ penetration_feed = " F%s"%tool['penetration feed']
+ for i in range(1,len(curve)):
+ # Creating Gcode for curve between s=curve[i-1] and si=curve[i] start at s[0] end at s[4]=si[0]
+ s, si = curve[i-1], curve[i]
+ feed = f if lg not in ['G01','G02','G03'] else ''
+ if s[1] == 'move':
+ g += go_to_safe_distance + "G00" + c(si[0]) + "\n" + tool['gcode before path'] + "\n"
+ lg = 'G00'
+ elif s[1] == 'end':
+ g += go_to_safe_distance + tool['gcode after path'] + "\n"
+ lg = 'G00'
+ elif s[1] == 'line':
+ if tool['4th axis meaning'] == "tangent knife" :
+ a = atan2(si[0][0]-s[0][0],si[0][1]-s[0][1])
+ a = calculate_angle(a, current_a)
+ g+="G01 A%s\n" % (a*tool['4th axis scale']+tool['4th axis offset'])
+ current_a = a
+ if lg=="G00": g += "G01" + c([None,None,s[5][0]+depth]) + penetration_feed +"\n"
+ g += "G01" +c(si[0]+[s[5][1]+depth]) + feed + "\n"
+ lg = 'G01'
+ elif s[1] == 'arc':
+ r = [(s[2][0]-s[0][0]), (s[2][1]-s[0][1])]
+ if tool['4th axis meaning'] == "tangent knife" :
+ if s[3]<0 : # CW
+ a1 = atan2(s[2][1]-s[0][1],-s[2][0]+s[0][0]) + math.pi
+ else: #CCW
+ a1 = atan2(-s[2][1]+s[0][1],s[2][0]-s[0][0]) + math.pi
+ a = calculate_angle(a1, current_a)
+ g+="G01 A%s\n" % (a*tool['4th axis scale']+tool['4th axis offset'])
+ current_a = a
+ axis4 = " A%s"%((current_a+s[3])*tool['4th axis scale']+tool['4th axis offset'])
+ current_a = current_a+s[3]
+ else : axis4 = ""
+ if lg=="G00": g += "G01" + c([None,None,s[5][0]+depth]) + penetration_feed + "\n"
+ if (r[0]**2 + r[1]**2)>self.options.min_arc_radius:
+ r1, r2 = (P(s[0])-P(s[2])), (P(si[0])-P(s[2]))
+ if abs(r1.mag()-r2.mag()) < 0.001 :
+ g += ("G02" if s[3]<0 else "G03") + c(si[0]+[ s[5][1]+depth, (s[2][0]-s[0][0]),(s[2][1]-s[0][1]) ]) + feed + axis4 + "\n"
+ else:
+ r = (r1.mag()+r2.mag())/2
+ g += ("G02" if s[3]<0 else "G03") + c(si[0]+[s[5][1]+depth]) + " R%f" % (r) + feed + axis4 + "\n"
+ lg = 'G02'
+ else:
+ if tool['4th axis meaning'] == "tangent knife" :
+ a = atan2(si[0][0]-s[0][0],si[0][1]-s[0][1]) + math.pi
+ a = calculate_angle(a, current_a)
+ g+="G01 A%s\n" % (a*tool['4th axis scale']+tool['4th axis offset'])
+ current_a = a
+ g += "G01" +c(si[0]+[s[5][1]+depth]) + feed + "\n"
+ lg = 'G01'
+ if si[1] == 'end':
+ g += go_to_safe_distance + tool['gcode after path'] + "\n"
+ return g
+
+
+ def get_transforms(self,g):
+ root = self.document.getroot()
+ trans = []
+ while (g!=root):
+ if 'transform' in g.keys():
+ t = g.get('transform')
+ t = simpletransform.parseTransform(t)
+ trans = simpletransform.composeTransform(t,trans) if trans != [] else t
+ print_(trans)
+ g=g.getparent()
+ return trans
+
+
+ def apply_transforms(self,g,csp):
+ trans = self.get_transforms(g)
+ if trans != []:
+ simpletransform.applyTransformToPath(trans, csp)
+ return csp
+
+
+ def transform(self,source_point, layer, reverse=False):
+ if layer not in self.transform_matrix:
+ for i in range(self.layers.index(layer),-1,-1):
+ if self.layers[i] in self.orientation_points :
+ break
+ if self.layers[i] not in self.orientation_points :
+ self.error(_("Orientation points for '%s' layer have not been found! Please add orientation points using Orientation tab!") % layer.get(inkex.addNS('label','inkscape')),"no_orientation_points")
+ elif self.layers[i] in self.transform_matrix :
+ self.transform_matrix[layer] = self.transform_matrix[self.layers[i]]
+ else :
+ orientation_layer = self.layers[i]
+ if len(self.orientation_points[orientation_layer])>1 :
+ self.error(_("There are more than one orientation point groups in '%s' layer") % orientation_layer.get(inkex.addNS('label','inkscape')),"more_than_one_orientation_point_groups")
+ points = self.orientation_points[orientation_layer][0]
+ if len(points)==2:
+ points += [ [ [(points[1][0][1]-points[0][0][1])+points[0][0][0], -(points[1][0][0]-points[0][0][0])+points[0][0][1]], [-(points[1][1][1]-points[0][1][1])+points[0][1][0], points[1][1][0]-points[0][1][0]+points[0][1][1]] ] ]
+ if len(points)==3:
+ print_("Layer '%s' Orientation points: " % orientation_layer.get(inkex.addNS('label','inkscape')))
+ for point in points:
+ print_(point)
+ # Zcoordinates definition taken from Orientatnion point 1 and 2
+ self.Zcoordinates[layer] = [max(points[0][1][2],points[1][1][2]), min(points[0][1][2],points[1][1][2])]
+ matrix = numpy.array([
+ [points[0][0][0], points[0][0][1], 1, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, points[0][0][0], points[0][0][1], 1, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, points[0][0][0], points[0][0][1], 1],
+ [points[1][0][0], points[1][0][1], 1, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, points[1][0][0], points[1][0][1], 1, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, points[1][0][0], points[1][0][1], 1],
+ [points[2][0][0], points[2][0][1], 1, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, points[2][0][0], points[2][0][1], 1, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, points[2][0][0], points[2][0][1], 1]
+ ])
+
+ if numpy.linalg.det(matrix)!=0 :
+ m = numpy.linalg.solve(matrix,
+ numpy.array(
+ [[points[0][1][0]], [points[0][1][1]], [1], [points[1][1][0]], [points[1][1][1]], [1], [points[2][1][0]], [points[2][1][1]], [1]]
+ )
+ ).tolist()
+ self.transform_matrix[layer] = [[m[j*3+i][0] for i in range(3)] for j in range(3)]
+
+ else :
+ self.error(_("Orientation points are wrong! (if there are two orientation points they sould not be the same. If there are three orientation points they should not be in a straight line.)"),"wrong_orientation_points")
+ else :
+ self.error(_("Orientation points are wrong! (if there are two orientation points they sould not be the same. If there are three orientation points they should not be in a straight line.)"),"wrong_orientation_points")
+
+ self.transform_matrix_reverse[layer] = numpy.linalg.inv(self.transform_matrix[layer]).tolist()
+ print_("\n Layer '%s' transformation matrixes:" % layer.get(inkex.addNS('label','inkscape')) )
+ print_(self.transform_matrix)
+ print_(self.transform_matrix_reverse)
+
+ ###self.Zauto_scale[layer] = math.sqrt( (self.transform_matrix[layer][0][0]**2 + self.transform_matrix[layer][1][1]**2)/2 )
+ ### Zautoscale is absolete
+ self.Zauto_scale[layer] = 1
+ print_("Z automatic scale = %s (computed according orientation points)" % self.Zauto_scale[layer])
+
+ x,y = source_point[0], source_point[1]
+ if not reverse :
+ t = self.transform_matrix[layer]
+ else :
+ t = self.transform_matrix_reverse[layer]
+ return [t[0][0]*x+t[0][1]*y+t[0][2], t[1][0]*x+t[1][1]*y+t[1][2]]
+
+
+ def transform_csp(self, csp_, layer, reverse = False):
+ csp = [ [ [csp_[i][j][0][:],csp_[i][j][1][:],csp_[i][j][2][:]] for j in range(len(csp_[i])) ] for i in range(len(csp_)) ]
+ for i in xrange(len(csp)):
+ for j in xrange(len(csp[i])):
+ for k in xrange(len(csp[i][j])):
+ csp[i][j][k] = self.transform(csp[i][j][k],layer, reverse)
+ return csp
+
+
+################################################################################
+### Errors handling function, notes are just printed into Logfile,
+### warnings are printed into log file and warning message is displayed but
+### extension continues working, errors causes log and execution is halted
+### Notes, warnings adn errors could be assigned to space or comma or dot
+### sepparated strings (case is ignoreg).
+################################################################################
+ def error(self, s, type_= "Warning"):
+ notes = "Note "
+ warnings = """
+ Warning tools_warning
+ bad_orientation_points_in_some_layers
+ more_than_one_orientation_point_groups
+ more_than_one_tool
+ orientation_have_not_been_defined
+ tool_have_not_been_defined
+ selection_does_not_contain_paths
+ selection_does_not_contain_paths_will_take_all
+ selection_is_empty_will_comupe_drawing
+ selection_contains_objects_that_are_not_paths
+ """
+ errors = """
+ Error
+ wrong_orientation_points
+ area_tools_diameter_error
+ no_tool_error
+ active_layer_already_has_tool
+ active_layer_already_has_orientation_points
+ """
+ if type_.lower() in re.split("[\s\n,\.]+", errors.lower()) :
+ print_(s)
+ inkex.errormsg(s+"\n")
+ sys.exit()
+ elif type_.lower() in re.split("[\s\n,\.]+", warnings.lower()) :
+ print_(s)
+ inkex.errormsg(s+"\n")
+ elif type_.lower() in re.split("[\s\n,\.]+", notes.lower()) :
+ print_(s)
+ else :
+ print_(s)
+ inkex.errormsg(s)
+ sys.exit()
+
+
+################################################################################
+### Get defs from svg
+################################################################################
+ def get_defs(self) :
+ self.defs = {}
+ def recursive(g) :
+ for i in g:
+ if i.tag == inkex.addNS("defs","svg") :
+ for j in i:
+ self.defs[j.get("id")] = i
+ if i.tag ==inkex.addNS("g",'svg') :
+ recursive(i)
+ recursive(self.document.getroot())
+
+
+################################################################################
+###
+### Get Gcodetools info from the svg
+###
+################################################################################
+ def get_info(self):
+ self.selected_paths = {}
+ self.paths = {}
+ self.tools = {}
+ self.orientation_points = {}
+ self.layers = [self.document.getroot()]
+ self.Zcoordinates = {}
+ self.transform_matrix = {}
+ self.transform_matrix_reverse = {}
+ self.Zauto_scale = {}
+
+ def recursive_search(g, layer, selected=False):
+ items = g.getchildren()
+ items.reverse()
+ for i in items:
+ if selected:
+ self.selected[i.get("id")] = i
+ if i.tag == inkex.addNS("g",'svg') and i.get(inkex.addNS('groupmode','inkscape')) == 'layer':
+ self.layers += [i]
+ recursive_search(i,i)
+ elif i.get('gcodetools') == "Gcodetools orientation group" :
+ points = self.get_orientation_points(i)
+ if points != None :
+ self.orientation_points[layer] = self.orientation_points[layer]+[points[:]] if layer in self.orientation_points else [points[:]]
+ print_("Found orientation points in '%s' layer: %s" % (layer.get(inkex.addNS('label','inkscape')), points))
+ else :
+ self.error(_("Warning! Found bad orientation points in '%s' layer. Resulting Gcode could be corrupt!") % layer.get(inkex.addNS('label','inkscape')), "bad_orientation_points_in_some_layers")
+ elif i.get("gcodetools") == "Gcodetools tool defenition" :
+ tool = self.get_tool(i)
+ self.tools[layer] = self.tools[layer] + [tool.copy()] if layer in self.tools else [tool.copy()]
+ print_("Found tool in '%s' layer: %s" % (layer.get(inkex.addNS('label','inkscape')), tool))
+ elif i.tag == inkex.addNS('path','svg'):
+ if "gcodetools" not in i.keys() :
+ self.paths[layer] = self.paths[layer] + [i] if layer in self.paths else [i]
+ if i.get("id") in self.selected :
+ self.selected_paths[layer] = self.selected_paths[layer] + [i] if layer in self.selected_paths else [i]
+ elif i.tag == inkex.addNS("g",'svg'):
+ recursive_search(i,layer, (i.get("id") in self.selected) )
+ elif i.get("id") in self.selected :
+ self.error(_("This extension works with Paths and Dynamic Offsets and groups of them only! All other objects will be ignored!\nSolution 1: press Path->Object to path or Shift+Ctrl+C.\nSolution 2: Path->Dynamic offset or Ctrl+J.\nSolution 3: export all contours to PostScript level 2 (File->Save As->.ps) and File->Import this file."),"selection_contains_objects_that_are_not_paths")
+
+
+ recursive_search(self.document.getroot(),self.document.getroot())
+
+
+ def get_orientation_points(self,g):
+ items = g.getchildren()
+ items.reverse()
+ p2, p3 = [], []
+ p = None
+ for i in items:
+ if i.tag == inkex.addNS("g",'svg') and i.get("gcodetools") == "Gcodetools orientation point (2 points)":
+ p2 += [i]
+ if i.tag == inkex.addNS("g",'svg') and i.get("gcodetools") == "Gcodetools orientation point (3 points)":
+ p3 += [i]
+ if len(p2)==2 : p=p2
+ elif len(p3)==3 : p=p3
+ if p==None : return None
+ points = []
+ for i in p :
+ point = [[],[]]
+ for node in i :
+ if node.get('gcodetools') == "Gcodetools orientation point arrow":
+ point[0] = self.apply_transforms(node,cubicsuperpath.parsePath(node.get("d")))[0][0][1]
+ if node.get('gcodetools') == "Gcodetools orientation point text":
+ r = re.match(r'(?i)\s*\(\s*(-?\s*\d*(?:,|\.)*\d*)\s*;\s*(-?\s*\d*(?:,|\.)*\d*)\s*;\s*(-?\s*\d*(?:,|\.)*\d*)\s*\)\s*',node.text)
+ point[1] = [float(r.group(1)),float(r.group(2)),float(r.group(3))]
+ if point[0]!=[] and point[1]!=[]: points += [point]
+ if len(points)==len(p2)==2 or len(points)==len(p3)==3 : return points
+ else : return None
+
+
+ def get_tool(self, g):
+ tool = self.default_tool.copy()
+ tool["self_group"] = g
+ for i in g:
+ # Get parameters
+ if i.get("gcodetools") == "Gcodetools tool background" :
+ tool["style"] = simplestyle.parseStyle(i.get("style"))
+ elif i.get("gcodetools") == "Gcodetools tool parameter" :
+ key = None
+ value = None
+ for j in i:
+ if j.get("gcodetools") == "Gcodetools tool defention field name":
+ key = j.text
+ if j.get("gcodetools") == "Gcodetools tool defention field value":
+ for k in j :
+ if k.tag == inkex.addNS('tspan','svg') and k.get("gcodetools") == "Gcodetools tool defention field value":
+ if k.text!=None : value = value +"\n" + k.text if value != None else k.text
+ if value == None or key == None: continue
+ #print_("Found tool parameter '%s':'%s'" % (key,value))
+ if key in self.default_tool.keys() :
+ try :
+ tool[key] = type(self.default_tool[key])(value)
+ except :
+ tool[key] = self.default_tool[key]
+ self.error(_("Warning! Tool's and default tool's parameter's (%s) types are not the same ( type('%s') != type('%s') ).") % (key, value, self.default_tool[key]), "tools_warning")
+ else :
+ tool[key] = value
+ self.error(_("Warning! Tool has parameter that default tool has not ( '%s': '%s' ).") % (key, value), "tools_warning" )
+ return tool
+
+
+ def set_tool(self,layer):
+# print_(("index(layer)=",self.layers.index(layer),"set_tool():layer=",layer,"self.tools=",self.tools))
+# for l in self.layers:
+# print_(("l=",l))
+ for i in range(self.layers.index(layer),-1,-1):
+# print_(("processing layer",i))
+ if self.layers[i] in self.tools :
+ break
+ if self.layers[i] in self.tools :
+ if self.layers[i] != layer : self.tools[layer] = self.tools[self.layers[i]]
+ if len(self.tools[layer])>1 : self.error(_("Layer '%s' contains more than one tool!") % self.layers[i].get(inkex.addNS('label','inkscape')), "more_than_one_tool")
+ return self.tools[layer]
+ else :
+ self.error(_("Can not find tool for '%s' layer! Please add one with Tools library tab!") % layer.get(inkex.addNS('label','inkscape')), "no_tool_error")
+
+
+################################################################################
+###
+### Path to Gcode
+###
+################################################################################
+ def path_to_gcode(self) :
+
+ def get_boundaries(points):
+ minx,miny,maxx,maxy=None,None,None,None
+ out=[[],[],[],[]]
+ for p in points:
+ if minx==p[0]:
+ out[0]+=[p]
+ if minx==None or p[0]<minx:
+ minx=p[0]
+ out[0]=[p]
+
+ if miny==p[1]:
+ out[1]+=[p]
+ if miny==None or p[1]<miny:
+ miny=p[1]
+ out[1]=[p]
+
+ if maxx==p[0]:
+ out[2]+=[p]
+ if maxx==None or p[0]>maxx:
+ maxx=p[0]
+ out[2]=[p]
+
+ if maxy==p[1]:
+ out[3]+=[p]
+ if maxy==None or p[1]>maxy:
+ maxy=p[1]
+ out[3]=[p]
+ return out
+
+
+ def remove_duplicates(points):
+ i=0
+ out=[]
+ for p in points:
+ for j in xrange(i,len(points)):
+ if p==points[j]: points[j]=[None,None]
+ if p!=[None,None]: out+=[p]
+ i+=1
+ return(out)
+
+
+ def get_way_len(points):
+ l=0
+ for i in xrange(1,len(points)):
+ l+=math.sqrt((points[i][0]-points[i-1][0])**2 + (points[i][1]-points[i-1][1])**2)
+ return l
+
+
+ def sort_dxfpoints(points):
+ points=remove_duplicates(points)
+# print_(get_boundaries(get_boundaries(points)[2])[1])
+ ways=[
+ # l=0, d=1, r=2, u=3
+ [3,0], # ul
+ [3,2], # ur
+ [1,0], # dl
+ [1,2], # dr
+ [0,3], # lu
+ [0,1], # ld
+ [2,3], # ru
+ [2,1], # rd
+ ]
+# print_(("points=",points))
+ minimal_way=[]
+ minimal_len=None
+ minimal_way_type=None
+ for w in ways:
+ tpoints=points[:]
+ cw=[]
+# print_(("tpoints=",tpoints))
+ for j in xrange(0,len(points)):
+ p=get_boundaries(get_boundaries(tpoints)[w[0]])[w[1]]
+# print_(p)
+ tpoints.remove(p[0])
+ cw+=p
+ curlen = get_way_len(cw)
+ if minimal_len==None or curlen < minimal_len:
+ minimal_len=curlen
+ minimal_way=cw
+ minimal_way_type=w
+
+ return minimal_way
+
+
+ def print_dxfpoints(points):
+ gcode=""
+ for point in points:
+ gcode +="(drilling dxfpoint)\nG00 Z%f\nG00 X%f Y%f\nG01 Z%f F%f\nG04 P%f\nG00 Z%f\n" % (self.options.Zsafe,point[0],point[1],self.Zcoordinates[layer][1],self.tools[layer][0]["penetration feed"],0.2,self.options.Zsafe)
+# print_(("got dxfpoints array=",points))
+ return gcode
+
+ if self.selected_paths == {} and self.options.auto_select_paths:
+ paths=self.paths
+ self.error(_("No paths are selected! Trying to work on all available paths."),"warning")
+ else :
+ paths = self.selected_paths
+ self.check_dir()
+ gcode = ""
+
+ biarc_group = inkex.etree.SubElement( self.selected_paths.keys()[0] if len(self.selected_paths.keys())>0 else self.layers[0], inkex.addNS('g','svg') )
+ print_(("self.layers=",self.layers))
+ print_(("paths=",paths))
+ for layer in self.layers :
+# print_(("processing layer",layer," of layers:",self.layers))
+ if layer in paths :
+# print_(("layer ",layer, " is in paths:",paths))
+ print_(("layer",layer))
+ self.set_tool(layer)
+ p = []
+ dxfpoints = []
+ for path in paths[layer] :
+ if "d" not in path.keys() :
+ self.error(_("Warning: One or more paths dont have 'd' parameter, try to Ungroup (Ctrl+Shift+G) and Object to Path (Ctrl+Shift+C)!"),"selection_contains_objects_that_are_not_paths")
+ continue
+ csp = cubicsuperpath.parsePath(path.get("d"))
+ csp = self.apply_transforms(path, csp)
+ if path.get("dxfpoint") == "1":
+ tmp_curve=self.transform_csp(csp, layer)
+ x=tmp_curve[0][0][0][0]
+ y=tmp_curve[0][0][0][1]
+ print_("got dxfpoint (scaled) at (%f,%f)" % (x,y))
+ dxfpoints += [[x,y]]
+ else:
+ p += csp
+ dxfpoints=sort_dxfpoints(dxfpoints)
+ gcode+=print_dxfpoints(dxfpoints)
+ curve = self.parse_curve(p, layer)
+ self.draw_curve(curve, layer, biarc_group)
+ if self.tools[layer][0]["depth step"] == 0 : self.tools[layer][0]["depth step"] = 1
+ for step in range( 0, int(math.ceil( abs( (self.Zcoordinates[layer][1]-self.Zcoordinates[layer][0])/self.tools[layer][0]["depth step"] )) ) ):
+ Zpos = max( self.Zcoordinates[layer][1], self.Zcoordinates[layer][0] - abs(self.tools[layer][0]["depth step"]*(step+1)) )
+ gcode += self.generate_gcode(curve, layer, Zpos)
+
+ self.export_gcode(gcode)
+
+################################################################################
+###
+### dxfpoints
+###
+################################################################################
+ def dxfpoints(self):
+ if self.selected_paths == {}:
+ self.error(_("Noting is selected. Please select something to convert to drill point (dxfpoint) or clear point sign."),"warning")
+ for layer in self.layers :
+ if layer in self.selected_paths :
+ for path in self.selected_paths[layer]:
+# print_(("processing path",path.get('d')))
+ if self.options.dxfpoints_action == 'replace':
+# print_("trying to set as dxfpoint")
+
+ path.set("dxfpoint","1")
+ r = re.match("^\s*.\s*(\S+)",path.get("d"))
+ if r!=None:
+ print_(("got path=",r.group(1)))
+ path.set("d","m %s 2.9375,-6.343750000001 0.8125,1.90625 6.843748640396,-6.84374864039 0,0 0.6875,0.6875 -6.84375,6.84375 1.90625,0.812500000001 z" % r.group(1))
+ path.set("style",styles["dxf_points"])
+
+ if self.options.dxfpoints_action == 'save':
+ path.set("dxfpoint","1")
+
+ if self.options.dxfpoints_action == 'clear' and path.get("dxfpoint") == "1":
+ path.set("dxfpoint","0")
+# for id, node in self.selected.iteritems():
+# print_((id,node,node.attrib))
+
+
+################################################################################
+###
+### Artefacts
+###
+################################################################################
+ def area_artefacts(self) :
+ if self.selected_paths == {} and self.options.auto_select_paths:
+ paths=self.paths
+ self.error(_("No paths are selected! Trying to work on all available paths."),"warning")
+ else :
+ paths = self.selected_paths
+ for layer in paths :
+# paths[layer].reverse() # Reverse list of paths to leave their order
+ for path in paths[layer] :
+ parent = path.getparent()
+ style = path.get("style") if "style" in path.keys() else ""
+ if "d" not in path.keys() :
+ self.error(_("Warning: One or more paths dont have 'd' parameter, try to Ungroup (Ctrl+Shift+G) and Object to Path (Ctrl+Shift+C)!"),"selection_contains_objects_that_are_not_paths")
+ continue
+ csp = cubicsuperpath.parsePath(path.get("d"))
+ csp = self.apply_transforms(path, csp)
+ for subpath in csp :
+ bounds = csp_simple_bound([subpath])
+ if (bounds[2]-bounds[0])**2+(bounds[3]-bounds[1])**2 < self.options.area_find_artefacts_diameter**2:
+ if self.options.area_find_artefacts_action == "mark with an arrow" :
+ inkex.etree.SubElement(parent, inkex.addNS('path','svg'),
+ {
+ 'd': 'm %s,%s 2.9375,-6.343750000001 0.8125,1.90625 6.843748640396,-6.84374864039 0,0 0.6875,0.6875 -6.84375,6.84375 1.90625,0.812500000001 z' % (subpath[0][1][0],subpath[0][1][1]),
+ 'style': styles["area artefact arrow"],
+ 'gcodetools': 'area artefact arrow',
+ })
+ inkex.etree.SubElement(parent, inkex.addNS('path','svg'), {'d': cubicsuperpath.formatPath([subpath]), 'style': style, "gcodetools_parameter":"area artefact"})
+ elif self.options.area_find_artefacts_action == "mark with style" :
+ inkex.etree.SubElement(parent, inkex.addNS('path','svg'), {'d': cubicsuperpath.formatPath([subpath]), 'style': styles["area artefact"]})
+ elif self.options.area_find_artefacts_action == "delete" :
+ print_("Deleted artifact %s" % subpath )
+ else :
+ inkex.etree.SubElement(parent, inkex.addNS('path','svg'), {'d': cubicsuperpath.formatPath([subpath]), 'style': style})
+ parent.remove(path)
+ return
+
+
+################################################################################
+###
+### Calculate area curves
+###
+################################################################################
+ def area(self) :
+ if len(self.selected_paths)<=0:
+ self.error(_("This extension requires at least one selected path."),"warning")
+ return
+ for layer in self.layers :
+ if layer in self.selected_paths :
+ self.set_tool(layer)
+ if self.tools[layer][0]['diameter']<=0 :
+ self.error(_("Tool diameter must be > 0 but tool's diameter on '%s' layer is not!") % layer.get(inkex.addNS('label','inkscape')),"area_tools_diameter_error")
+
+ for path in self.selected_paths[layer]:
+ print_(("doing path", path.get("style"), path.get("d")))
+
+ area_group = inkex.etree.SubElement( path.getparent(), inkex.addNS('g','svg') )
+
+ d = path.get('d')
+ print_(d)
+ if d==None:
+ print_("omitting non-path")
+ self.error(_("Warning: omitting non-path"),"selection_contains_objects_that_are_not_paths")
+ continue
+ csp = cubicsuperpath.parsePath(d)
+
+ if path.get(inkex.addNS('type','sodipodi'))!="inkscape:offset":
+ print_("Path %s is not an offset. Preparation started." % path.get("id"))
+ # Path is not offset. Preparation will be needed.
+ # Finding top most point in path (min y value)
+
+ min_x,min_y,min_i,min_j,min_t = csp_true_bounds(csp)[1]
+
+ # Reverse path if needed.
+ if min_y!=float("-inf") :
+ # Move outline subpath to the begining of csp
+ subp = csp[min_i]
+ del csp[min_i]
+ j = min_j
+ # Split by the topmost point and join again
+ if min_t in [0,1]:
+ if min_t == 0: j=j-1
+ subp[-1][2], subp[0][0] = subp[-1][1], subp[0][1]
+ subp = [ [subp[j][1], subp[j][1], subp[j][2]] ] + subp[j+1:] + subp[:j] + [ [subp[j][0], subp[j][1], subp[j][1]] ]
+ else:
+ sp1,sp2,sp3 = csp_split(subp[j-1],subp[j],min_t)
+ subp[-1][2], subp[0][0] = subp[-1][1], subp[0][1]
+ subp = [ [ sp2[1], sp2[1],sp2[2] ] ] + [sp3] + subp[j+1:] + subp[:j-1] + [sp1] + [[ sp2[0], sp2[1],sp2[1] ]]
+ csp = [subp] + csp
+ # reverse path if needed
+ if csp_subpath_ccw(csp[0]) :
+ for i in range(len(csp)):
+ n = []
+ for j in csp[i]:
+ n = [ [j[2][:],j[1][:],j[0][:]] ] + n
+ csp[i] = n[:]
+
+
+ d = cubicsuperpath.formatPath(csp)
+ print_(("original d=",d))
+ d = re.sub(r'(?i)(m[^mz]+)',r'\1 Z ',d)
+ d = re.sub(r'(?i)\s*z\s*z\s*',r' Z ',d)
+ d = re.sub(r'(?i)\s*([A-Za-z])\s*',r' \1 ',d)
+ print_(("formatted d=",d))
+ # scale = sqrt(Xscale**2 + Yscale**2) / sqrt(1**2 + 1**2)
+ p0 = self.transform([0,0],layer)
+ p1 = self.transform([0,1],layer)
+ scale = (P(p0)-P(p1)).mag()
+ if scale == 0 : scale = 1.
+ else : scale = 1./scale
+ print_(scale)
+ tool_d = self.tools[layer][0]['diameter']*scale
+ r = self.options.area_inkscape_radius * scale
+ sign=1 if r>0 else -1
+ print_("Tool diameter = %s, r = %s" % (tool_d, r))
+
+ for i in range(self.options.max_area_curves):
+ radius = - tool_d * (i+0.5) * sign
+ if abs(radius)>abs(r):
+ radius = -r
+
+ inkex.etree.SubElement( area_group, inkex.addNS('path','svg'),
+ {
+ inkex.addNS('type','sodipodi'): 'inkscape:offset',
+ inkex.addNS('radius','inkscape'): str(radius),
+ inkex.addNS('original','inkscape'): d,
+ 'style': styles["biarc_style_i"]['area']
+ })
+ print_(("adding curve",area_group,d,styles["biarc_style_i"]['area']))
+ if radius == -r : break
+
+
+################################################################################
+###
+### Engraving
+###
+################################################################################
+ def engraving(self) :
+ if len(self.selected_paths)<=0:
+ self.error(_("This extension requires at least one selected path."),"warning")
+ return
+ if not self.check_dir() : return
+ gcode = ''
+
+ def find_cutter_center((x1,y1),(nx1,ny1), sp1,sp2, tool, t3 = .5):
+ ####################################################################
+ ### To find center of cutter a system of non linear equations
+ ### will be solved using Newton's method
+ ####################################################################
+ bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
+ ax,ay,bx,by,cx,cy,dx,dy=bezmisc.bezierparameterize(bez)
+ fx=ax*(t3*t3*t3)+bx*(t3*t3)+cx*t3+dx
+ fy=ay*(t3*t3*t3)+by*(t3*t3)+cy*t3+dy
+
+ nx2,ny2 = csp_normalized_normal(sp1,sp2,t3)
+ intersection, t1, t2 = straight_segments_intersection([[x1,y1],[x1+nx1,y1+ny1]],[[fx,fy],[fx+nx2,fy+ny2]], False)
+ if not intersection or intersection == "Overlap" :
+ if nx1!=0 :
+ t1 = t2 = (x1-fx)/nx1
+ else :
+ t1 = t2 = (y1-fy)/ny1
+
+ t = [ t1, t2, t3 ]
+ i = 0
+ F = [0.,0.,0.]
+ F1 = [[0.,0.,0.],[0.,0.,0.],[0.,0.,0.]]
+ while i==0 or abs(F[0])+abs(F[1])+math.sqrt(abs(F[2])) >engraving_tolerance and i<10:
+ t1,t2,t3 = t[0],t[1],t[2]
+ fx=ax*(t3*t3*t3)+bx*(t3*t3)+cx*t3+dx
+ fy=ay*(t3*t3*t3)+by*(t3*t3)+cy*t3+dy
+ f1x=3*ax*(t3*t3)+2*bx*t3+cx
+ f1y=3*ay*(t3*t3)+2*by*t3+cy
+ i+=1
+
+ tx = fx-x1-nx1*t1
+ ty = fy-y1-ny1*t1
+
+ F[0] = x1+nx1*t1-fx+t2*f1y
+ F[1] = y1+ny1*t1-fy-t2*f1x
+ F[2] = t1*t1 - tx*tx -ty*ty
+
+ F1[0][0] = nx1
+ F1[0][1] = f1y
+ F1[0][2] = -f1x+t2*(6*ay*t3+2*by)
+
+ F1[1][0] = ny1
+ F1[1][1] = -f1x
+ F1[1][2] = -f1y-t2*(6*ax*t3+2*bx)
+
+ F1[2][0] = 2*t1+2*nx1*tx +2*ny1*ty
+ F1[2][1] = 0
+ F1[2][2] = -2*f1x*tx -2*f1y*ty
+
+ F1 = inv_3x3(F1)
+
+ if ( isnan(F[0]) or isnan(F[1]) or isnan(F[2]) or
+ isinf(F[0]) or isinf(F[1]) or isinf(F[2]) ):
+ return t+[1e100,i]
+
+ if F1!= None :
+ t[0] -= F1[0][0]*F[0] + F1[0][1]*F[1] + F1[0][2]*F[2]
+ t[1] -= F1[1][0]*F[0] + F1[1][1]*F[1] + F1[1][2]*F[2]
+ t[2] -= F1[2][0]*F[0] + F1[2][1]*F[1] + F1[2][2]*F[2]
+ else: break
+
+ return t+[abs(F[0])+abs(F[1])+math.sqrt(abs(F[2])),i]
+ cspe =[]
+ we = []
+ for layer in self.layers :
+ if layer in self.selected_paths :
+ self.set_tool(layer)
+ engraving_group = inkex.etree.SubElement( self.selected_paths[layer][0].getparent(), inkex.addNS('g','svg') )
+ for node in self.selected_paths[layer] :
+ if node.tag == inkex.addNS('path','svg'):
+ cspi = cubicsuperpath.parsePath(node.get('d'))
+
+ for j in xrange(len(cspi)):
+ # Remove zerro length segments
+ i = 1
+ while i<len(cspi[j]):
+ if abs(cspi[j][i-1][1][0]-cspi[j][i][1][0])<engraving_tolerance and abs(cspi[j][i-1][1][1]-cspi[j][i][1][1])<engraving_tolerance:
+ cspi[j][i-1][2] = cspi[j][i][2]
+ del cspi[j][i]
+ else:
+ i += 1
+ for csp in cspi:
+ # Create list containing normlas and points
+ nl = []
+ for i in range(1,len(csp)):
+ n, n1 = [], []
+ sp1, sp2 = csp[i-1], csp[i]
+ for ti in [.0,.25,.75,1.]:
+ # Is following string is nedded or not??? (It makes t depend on form of the curve)
+ #ti = bezmisc.beziertatlength(bez,ti)
+ x1,y1 = csp_at_t(sp1,sp2,ti)
+ nx,ny = csp_normalized_normal(sp1,sp2,ti)
+ n+=[ [ [x1,y1], [nx,ny], False, False, i] ] # [point coordinates, normal, is an inner corner, is an outer corner, csp's index]
+ if ti==1 and i<len(csp)-1:
+ nx2, ny2 = csp_normalized_slope(csp[i],csp[i+1],0)
+ nx2,ny2 = -ny2,nx2
+ ang = ny2*nx-ny*nx2
+ ang1 = 180-math.acos(max(-1,min(1,nx*nx2+ny*ny2)))*180/math.pi
+ if ang > 0 and ang1 < self.options.engraving_sharp_angle_tollerance : # inner angle
+ n[-1][2] = True
+ elif ang < 0 and ang1 < self.options.engraving_sharp_angle_tollerance : # outer angle
+ a = -math.acos(nx*nx2+ny*ny2)
+ for t in [.0,.25,.75,1.]:
+ n1 += [ [ [x1,y1], [nx*math.cos(a*t)-ny*math.sin(a*t),nx*math.sin(a*t)+ny*math.cos(a*t)], False, True, i ] ]
+ nl += [ n ] + ([ n1 ] if n1!=[] else [])
+ # Modify first/last points if curve is closed
+ if abs(csp[-1][1][0]-csp[0][1][0])<engraving_tolerance and abs(csp[-1][1][1]-csp[0][1][1])<engraving_tolerance :
+ x1,y1 = csp_at_t(csp[-2],csp[-1],1)
+ nx,ny = csp_normalized_slope(csp[-2],csp[-1],1)
+ nx,ny = -ny,nx
+ nx2,ny2 = csp_normalized_slope(csp[0],csp[1],0)
+ nx2,ny2 = -ny2,nx2
+ ang = ny2*nx-ny*nx2
+ if ang > 0 and 180-math.acos(nx*nx2+ny*ny2)*180/math.pi < self.options.engraving_sharp_angle_tollerance : # inner angle
+ nl[-1][-1][2] = True
+ elif ang < 0 and 180-math.acos(nx*nx2+ny*ny2)*180/math.pi < self.options.engraving_sharp_angle_tollerance : # outer angle
+ a = -math.acos(nx*nx2+ny*ny2)
+ n1 = []
+ for t in [.0,.25,.75,1.]:
+ n1 += [ [ [x1,y1], [nx*math.cos(a*t)-ny*math.sin(a*t),nx*math.sin(a*t)+ny*math.cos(a*t)], False, True, i ] ]
+ nl += [ n1 ]
+
+
+ print_(("engraving_draw_calculation_paths=",self.options.engraving_draw_calculation_paths))
+ if self.options.engraving_draw_calculation_paths==True:
+ for i in nl:
+ for p in i:
+ inkex.etree.SubElement( engraving_group, inkex.addNS('path','svg'),
+ {
+ "d": "M %f,%f L %f,%f" %(p[0][0],p[0][1],p[0][0]+p[1][0]*10,p[0][1]+p[1][1]*10),
+ 'style': "stroke:#0000ff; stroke-opacity:0.46; stroke-width:0.1; fill:none",
+ "gcodetools": "Engraving calculation paths"
+ })
+
+
+ # Calculate offset points
+ csp_points = []
+ for ki in xrange(len(nl)):
+ p = []
+ for ti in xrange(3) if ki!=len(nl)-1 else xrange(4):
+ n = nl[ki][ti]
+ x1,y1 = n[0]
+ nx,ny = n[1]
+ d, r = 0, float("inf")
+ if ti==0 and nl[ki-1][-1][2] == True or ti==3 and nl[ki][ti][2] == True:
+ # Point is a sharp angle r=0p
+ r = 0
+ else :
+ for j in xrange(0,len(cspi)):
+ for i in xrange(1,len(cspi[j])):
+ d = csp_bound_to_point_distance(cspi[j][i-1], cspi[j][i], [x1,y1])
+ if d >= self.options.engraving_max_dist*2 :
+ r = min(math.sqrt(d/2),r)
+ continue
+ for n1 in xrange(self.options.engraving_newton_iterations):
+ t = find_cutter_center((x1,y1),(nx,ny), cspi[j][i-1], cspi[j][i], self.tools[layer][0], float(n1)/(self.options.engraving_newton_iterations-1))
+ print_(t)
+ if t[0] > engraving_tolerance and 0<=t[2]<=1 and abs(t[3])<engraving_tolerance:
+ print_("!@#!@#!@#!@#!@",t)
+ t3 = t[2]
+ ax,ay,bx,by,cx,cy,dx,dy=bezmisc.bezierparameterize((cspi[j][i-1][1],cspi[j][i-1][2],cspi[j][i][0],cspi[j][i][1]))
+ x2=ax*(t3*t3*t3)+bx*(t3*t3)+cx*t3+dx
+ y2=ay*(t3*t3*t3)+by*(t3*t3)+cy*t3+dy
+ if abs(x2-x1)<engraving_tolerance and abs(y2-y1)<engraving_tolerance:
+ f1x = 3*ax*(t3*t3)+2*bx*t3+cx
+ f1y = 3*ay*(t3*t3)+2*by*t3+cy
+ f2x = 6*ax*t3+2*bx
+ f2y = 6*ay*t3+2*by
+ d = f1x*f2y-f1y*f2x
+ # d = curvature
+ if d!=0 :
+ d = math.sqrt((f1x*f1x+f1y*f1y)**3)/d
+ if d>0:
+ r = min( d,r) if r!=None else d
+ else :
+ r = min(r,self.options.engraving_max_dist) if r!=None else self.options.engraving_max_dist
+ else:
+ r = min(t[0],r) if r!=None else t[0]
+ for j in xrange(0,len(cspi)):
+ for i in xrange(0,len(cspi[j])):
+ x2,y2 = cspi[j][i][1]
+ if (abs(x1-x2)>engraving_tolerance or abs(y1-y2)>engraving_tolerance ) and (x2*nx - x1*nx + y2*ny - y1*ny) != 0:
+ t1 = .5 * ( (x1-x2)**2+(y1-y2)**2 ) / (x2*nx - x1*nx + y2*ny - y1*ny)
+ if t1>0 : r = min(t1,r) if r!=None else t1
+ if self.options.engraving_draw_calculation_paths==True:
+ inkex.etree.SubElement( engraving_group, inkex.addNS('path','svg'),
+ {"gcodetools": "Engraving calculation paths", 'style': "fill:#ff00ff; fill-opacity:0.46; stroke:#000000; stroke-width:0.1;", inkex.addNS('cx','sodipodi'): str(x1+nx*r), inkex.addNS('cy','sodipodi'): str(y1+ny*r), inkex.addNS('rx','sodipodi'): str(1), inkex.addNS('ry','sodipodi'): str(1), inkex.addNS('type','sodipodi'): 'arc'})
+ inkex.etree.SubElement( engraving_group, inkex.addNS('path','svg'),
+ {"gcodetools": "Engraving calculation paths", 'style': "fill:none; fill-opacity:0.46; stroke:#000000; stroke-width:0.1;", inkex.addNS('cx','sodipodi'): str(x1+nx*r), inkex.addNS('cy','sodipodi'): str(y1+ny*r),inkex.addNS('rx','sodipodi'): str(r), inkex.addNS('ry','sodipodi'): str(r), inkex.addNS('type','sodipodi'): 'arc'})
+ r = min(r, self.options.engraving_max_dist)
+ w = min(r, self.tools[layer][0]['diameter'])
+ p += [ [x1+nx*w,y1+ny*w,r,w] ]
+
+
+
+ if len(csp_points)>0 : csp_points[-1] += [p[0]]
+ csp_points += [ p ]
+ # Splitting path to pieces each of them not further from path more than engraving_max_dist
+ engraving_path = [ [] ]
+ for p_ in csp_points :
+ for p in p_:
+ if p[2]<self.options.engraving_max_dist : break
+ if p[2]<self.options.engraving_max_dist: engraving_path[-1] += [p_]
+ else :
+ if engraving_path[-1] != [] : engraving_path += [ [] ]
+ if engraving_path[-1] == [] : del engraving_path[-1]
+
+
+ for csp_points in engraving_path :
+ # Create Path that goes through this points
+ cspm = []
+ w = []
+ m = [[0.0, 0.0, 0.0, 1.0], [0.015625, 0.140625, 0.421875, 0.421875], [0.421875, 0.421875, 0.140625, 0.015625], [1.0, 0.0, 0.0, 0.0]]
+ for p in csp_points:
+ m = numpy.array(m)
+ xi = numpy.array( [p[i][:2] for i in range(4)])
+ sp1,sp2 = [[0.,0.],[0.,0.],[0.,0.]], [[0.,0.],[0.,0.],[0.,0.]]
+ a,b,c,d = numpy.linalg.solve(m, xi).tolist()
+ sp1[1], sp1[0] = d, d
+ sp1[2] = c
+ sp2[0] = b
+ sp2[1], sp2[2] = a, a
+ sp3,sp4,sp5 = csp_split(sp1, sp2, .25)
+ l = cspseglength(sp3,sp4)
+ sp1,sp2,sp4 = csp_split(sp1, sp2, .75)
+ l1 = cspseglength(sp1,sp2)
+ if l1!=0:
+ sp1,sp2,sp3 = csp_splitatlength(sp1, sp2, l/l1)
+ if len(cspm)>0 :
+ cspm[-1][2] = sp1[2]
+ cspm += [sp2[:], sp3[:], sp4[:]]
+ w += [p[i][3] for i in range(1,4)]
+ else :
+ cspm += [sp1[:], sp2[:], sp3[:], sp4[:]]
+ w += [p[i][3] for i in range(4)]
+ if self.options.engraving_draw_calculation_paths==True:
+ node = inkex.etree.SubElement( engraving_group, inkex.addNS('path','svg'), {
+ "d": cubicsuperpath.formatPath([cspm]),
+ 'style': styles["biarc_style_i"]['biarc1'],
+ "gcodetools": "Engraving calculation paths",
+ })
+ for i in xrange(len(cspm)):
+ inkex.etree.SubElement( engraving_group, inkex.addNS('path','svg'),
+ {"gcodetools": "Engraving calculation paths", 'style': "fill:none; fill-opacity:0.46; stroke:#000000; stroke-width:0.1;", inkex.addNS('cx','sodipodi'): str(cspm[i][1][0]), inkex.addNS('cy','sodipodi'): str(cspm[i][1][1]),inkex.addNS('rx','sodipodi'): str(w[i]), inkex.addNS('ry','sodipodi'): str(w[i]), inkex.addNS('type','sodipodi'): 'arc'})
+ cspe += [cspm]
+ we += [w]
+
+ if self.tools[layer][0]['shape'] != "":
+ f = eval('lambda w: ' + self.tools[layer][0]['shape'].strip('"'))
+ else:
+ self.error(_("Tool '%s' has no shape!") % self.tools[layer][0]['name'],"engraving_tools_shape_error")
+ f = lambda w: w
+
+ if cspe!=[]:
+ curve = self.parse_curve(cspe, layer, we, f)
+ self.draw_curve(curve, layer, engraving_group)
+ gcode += self.generate_gcode(curve, layer, self.options.Zsurface)
+
+ if gcode!='' :
+ self.export_gcode(gcode)
+ else : self.error(_("No need to engrave sharp angles."),"warning")
+
+
+################################################################################
+###
+### Orientation
+###
+################################################################################
+ def orientation(self, layer=None) :
+ print_("entering orientations")
+ if layer == None :
+ layer = self.current_layer if self.current_layer is not None else self.document.getroot()
+ if layer in self.orientation_points:
+ self.error(_("Active layer already has orientation points! Remove them or select another layer!"),"active_layer_already_has_orientation_points")
+
+ orientation_group = inkex.etree.SubElement(layer, inkex.addNS('g','svg'), {"gcodetools":"Gcodetools orientation group"})
+ doc_height = inkex.unittouu(self.document.getroot().get('height'))
+ if self.document.getroot().get('height') == "100%" :
+ doc_height = 1052.3622047
+ print_("Overruding height from 100 percents to %s" % doc_height)
+ if self.options.unit == "G21 (All units in mm)" :
+ points = [[0.,0.,self.options.Zsurface],[100.,0.,self.options.Zdepth],[0.,100.,0.]]
+ orientation_scale = 3.5433070660
+ print_("orientation_scale < 0 ===> switching to mm units=%0.10f"%orientation_scale )
+ elif self.options.unit == "G20 (All units in inches)" :
+ points = [[0.,0.,self.options.Zsurface],[5.,0.,self.options.Zdepth],[0.,5.,0.]]
+ orientation_scale = 90
+ print_("orientation_scale < 0 ===> switching to inches units=%0.10f"%orientation_scale )
+ if self.options.orientation_points_count == 2 :
+ points = points[:2]
+ print_(("using orientation scale",orientation_scale,"i=",points))
+ for i in points :
+ si = [i[0]*orientation_scale, i[1]*orientation_scale]
+ g = inkex.etree.SubElement(orientation_group, inkex.addNS('g','svg'), {'gcodetools': "Gcodetools orientation point (%s points)" % self.options.orientation_points_count})
+ inkex.etree.SubElement( g, inkex.addNS('path','svg'),
+ {
+ 'style': "stroke:none;fill:#000000;",
+ 'd':'m %s,%s 2.9375,-6.343750000001 0.8125,1.90625 6.843748640396,-6.84374864039 0,0 0.6875,0.6875 -6.84375,6.84375 1.90625,0.812500000001 z z' % (si[0], -si[1]+doc_height),
+ 'gcodetools': "Gcodetools orientation point arrow"
+ })
+ t = inkex.etree.SubElement( g, inkex.addNS('text','svg'),
+ {
+ 'style': "font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;fill:#000000;fill-opacity:1;stroke:none;",
+ inkex.addNS("space","xml"):"preserve",
+ 'x': str(si[0]+10),
+ 'y': str(-si[1]-10+doc_height),
+ 'gcodetools': "Gcodetools orientation point text"
+ })
+ t.text = "(%s; %s; %s)" % (i[0],i[1],i[2])
+
+
+################################################################################
+###
+### Tools library
+###
+################################################################################
+ def tools_library(self, layer=None) :
+ # Add a tool to the drawing
+ if layer == None :
+ layer = self.current_layer if self.current_layer is not None else self.document.getroot()
+ if layer in self.tools:
+ self.error(_("Active layer already has a tool! Remove it or select another layer!"),"active_layer_already_has_tool")
+
+ if self.options.tools_library_type == "cylinder cutter" :
+ tool = {
+ "name": "Cylindrical cutter",
+ "id": "Cylindrical cutter 0001",
+ "diameter":10,
+ "penetration angle":90,
+ "feed":"400",
+ "penetration feed":"100",
+ "depth step":"1",
+ "tool change gcode":" "
+ }
+ elif self.options.tools_library_type == "lathe cutter" :
+ tool = {
+ "name": "Lathe cutter",
+ "id": "Lathe cutter 0001",
+ "diameter":10,
+ "penetration angle":90,
+ "feed":"400",
+ "passing feed":"800",
+ "fine feed":"100",
+ "penetration feed":"100",
+ "depth step":"1",
+ "tool change gcode":" "
+ }
+ elif self.options.tools_library_type == "cone cutter":
+ tool = {
+ "name": "Cone cutter",
+ "id": "Cone cutter 0001",
+ "diameter":10,
+ "shape":"w",
+ "feed":"400",
+ "penetration feed":"100",
+ "depth step":"1",
+ "tool change gcode":" "
+ }
+ elif self.options.tools_library_type == "tangent knife":
+ tool = {
+ "name": "Tangent knife",
+ "id": "Tangent knife 0001",
+ "feed":"400",
+ "penetration feed":"100",
+ "depth step":"100",
+ "4th axis meaning": "tangent knife",
+ "4th axis scale": 1.,
+ "4th axis offset": 0,
+ "tool change gcode":" "
+ }
+
+ elif self.options.tools_library_type == "plasma cutter":
+ tool = {
+ "name": "Plasma cutter",
+ "id": "Plasma cutter 0001",
+ "diameter":10,
+ "penetration feed":100,
+ "feed":400,
+ "gcode before path":"""G31 Z-100 F500 (find metal)
+G92 Z0 (zerro z)
+G00 Z10 F500 (going up)
+M03 (turn on plasma)
+G04 P0.2 (pause)
+G01 Z1 (going to cutting z)\n""",
+ "gcode after path":"M05 (turn off plasma)\n",
+ }
+ else :
+ tool = self.default_tool
+
+ tool_num = sum([len(self.tools[i]) for i in self.tools])
+ colors = ["00ff00","0000ff","ff0000","fefe00","00fefe", "fe00fe", "fe7e00", "7efe00", "00fe7e", "007efe", "7e00fe", "fe007e"]
+
+ tools_group = inkex.etree.SubElement(layer, inkex.addNS('g','svg'), {'gcodetools': "Gcodetools tool defenition"})
+ bg = inkex.etree.SubElement( tools_group, inkex.addNS('path','svg'),
+ {'style': "fill:#%s;fill-opacity:0.5;stroke:#444444; stroke-width:1px;"%colors[tool_num%len(colors)], "gcodetools":"Gcodetools tool background"})
+
+ y = 0
+ keys = []
+ for key in self.tools_field_order:
+ if key in tool: keys += [key]
+ for key in tool:
+ if key not in keys: keys += [key]
+ for key in keys :
+ g = inkex.etree.SubElement(tools_group, inkex.addNS('g','svg'), {'gcodetools': "Gcodetools tool parameter"})
+
+ t = inkex.etree.SubElement( g, inkex.addNS('text','svg'),
+ {
+ 'style': ("font-size:10px;" if key!="name" else "font-size:20px;") + "font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill:#000000;fill-opacity:1;stroke:none;",
+ inkex.addNS("space","xml"):"preserve",
+ 'x': str(0),
+ 'y': str(y),
+ 'gcodetools': "Gcodetools tool defention field name"
+ })
+ t.text = str(key)
+ v = str(tool[key]).split("\n")
+ t = inkex.etree.SubElement( g, inkex.addNS('text','svg'),
+ {
+ 'style': ("font-size:10px;" if key!="name" else "font-size:20px;") + "font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;fill:#000000;fill-opacity:1;stroke:none;",
+ 'x': str(150),
+ inkex.addNS("space","xml"):"preserve",
+ 'y': str(y),
+ 'gcodetools': "Gcodetools tool defention field value"
+ })
+ for s in v :
+ span = inkex.etree.SubElement( t, inkex.addNS('tspan','svg'),
+ {
+ 'x': str(150),
+ 'y': str(+y),
+ inkex.addNS("role","sodipodi"):"line",
+ 'gcodetools': "Gcodetools tool defention field value"
+ })
+ y += 15 if key!='name' else 20
+ span.text = s
+ bg.set('d',"m -20,-20 l 400,0 0,%f -400,0 z " % (y+50))
+ tool = []
+ tools_group.set("transform", simpletransform.formatTransform([ [1,0,self.view_center[0]-150 ], [0,1,self.view_center[1]] ] ))
+
+
+################################################################################
+###
+### Check tools and OP asignment
+###
+################################################################################
+ def check_tools_and_op(self):
+ if len(self.selected)<=0 :
+ self.error(_("Selection is empty! Will compute whole drawing."),"selection_is_empty_will_comupe_drawing")
+ paths = self.paths
+ else :
+ paths = self.selected_paths
+ # Set group
+ group = inkex.etree.SubElement( self.selected_paths.keys()[0] if len(self.selected_paths.keys())>0 else self.layers[0], inkex.addNS('g','svg') )
+ trans_ = [[1,0.3,0],[0,0.5,0]]
+ self.get_defs()
+ # Add marker to defs if it doesnot exists
+ if "CheckToolsAndOPMarker" not in self.defs :
+ defs = inkex.etree.SubElement( self.document.getroot(), inkex.addNS("defs","svg"))
+ marker = inkex.etree.SubElement( defs, inkex.addNS("marker","svg"), {"id":"CheckToolsAndOPMarker","orient":"auto","refX":"-8","refY":"-2.41063","style":"overflow:visible"})
+ inkex.etree.SubElement( marker, inkex.addNS("path","svg"),
+ { "d":"m -6.55552,-2.41063 0,0 L -13.11104,0 c 1.0473,-1.42323 1.04126,-3.37047 0,-4.82126",
+ "style": "fill:#000044; fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;" }
+ )
+ bounds = [float('inf'),float('inf'),float('-inf'),float('-inf')]
+ tools_bounds = {}
+ for layer in self.layers :
+ if layer in paths :
+ self.set_tool(layer)
+ tool = self.tools[layer][0]
+ tools_bounds[layer] = tools_bounds[layer] if layer in tools_bounds else [float("inf"),float("-inf")]
+ style = simplestyle.formatStyle(tool["style"])
+ for path in paths[layer] :
+ style = "fill:%s; fill-opacity:%s; stroke:#000044; stroke-width:1; marker-mid:url(#CheckToolsAndOPMarker);" % (
+ tool["style"]["fill"] if "fill" in tool["style"] else "#00ff00",
+ tool["style"]["fill-opacity"] if "fill-opacity" in tool["style"] else "0.5")
+ group.insert( 0, inkex.etree.Element(path.tag, path.attrib))
+ new = group.getchildren()[0]
+ new.set("style", style)
+
+ trans = self.get_transforms(path)
+ trans = simpletransform.composeTransform( trans_, trans if trans != [] else [[1.,0.,0.],[0.,1.,0.]])
+ csp = cubicsuperpath.parsePath(path.get("d"))
+ simpletransform.applyTransformToPath(trans,csp)
+ path_bounds = csp_simple_bound(csp)
+ trans = simpletransform.formatTransform(trans)
+ bounds = [min(bounds[0],path_bounds[0]), min(bounds[1],path_bounds[1]), max(bounds[2],path_bounds[2]), max(bounds[3],path_bounds[3])]
+ tools_bounds[layer] = [min(tools_bounds[layer][0], path_bounds[1]), max(tools_bounds[layer][1], path_bounds[3])]
+
+ new.set("transform", trans)
+ trans_[1][2] += 20
+ trans_[1][2] += 100
+
+ for layer in self.layers :
+ if layer in self.tools :
+ if layer in tools_bounds :
+ tool = self.tools[layer][0]
+ g = copy.deepcopy(tool["self_group"])
+ g.attrib["gcodetools"] = "Check tools and OP asignment"
+ trans = [[1,0.3,bounds[2]],[0,0.5,tools_bounds[layer][0]]]
+ g.set("transform",simpletransform.formatTransform(trans))
+ group.insert( 0, g )
+
+
+################################################################################
+### TODO Launch browser on help tab
+################################################################################
+ def help(self):
+ self.error(_("""Tutorials, manuals and support can be found at\nEnglish support forum:\n http://www.cnc-club.ru/gcodetools\nand Russian support forum:\n http://www.cnc-club.ru/gcodetoolsru"""),"warning")
+ return
+
+
+################################################################################
+### Lathe
+################################################################################
+ def generate_lathe_gcode(self, subpath, layer, feed_type) :
+ if len(subpath) <2 : return ""
+ feed = " F %f" % self.tool[feed_type]
+ x,z = self.options.lathe_x_axis_remap, self.options.lathe_z_axis_remap
+ flip_angle = -1 if x.lower()+z.lower() in ["xz", "yx", "zy"] else 1
+ alias = {"X":"I", "Y":"J", "Z":"K", "x":"i", "y":"j", "z":"k"}
+ i_, k_ = alias[x], alias[z]
+ c = [ [subpath[0][1], "move", 0, 0, 0] ]
+ #csp_draw(self.transform_csp([subpath],layer,True), color = "Orange", width = .1)
+ for sp1,sp2 in zip(subpath,subpath[1:]) :
+ c += biarc(sp1,sp2,0,0)
+ for i in range(1,len(c)) : # Just in case check end point of each segment
+ c[i-1][4] = c[i][0][:]
+ c += [ [subpath[-1][1], "end", 0, 0, 0] ]
+ self.draw_curve(c, layer, style = styles["biarc_style_lathe_%s" % feed_type])
+
+ gcode = ("G01 %s %f %s %f" % (x, c[0][4][0], z, c[0][4][1]) ) + feed + "\n" # Just in case move to the start...
+ for s in c :
+ if s[1] == 'line':
+ gcode += ("G01 %s %f %s %f" % (x, s[4][0], z, s[4][1]) ) + feed + "\n"
+ elif s[1] == 'arc':
+ r = [(s[2][0]-s[0][0]), (s[2][1]-s[0][1])]
+ if (r[0]**2 + r[1]**2)>self.options.min_arc_radius:
+ r1, r2 = (P(s[0])-P(s[2])), (P(s[4])-P(s[2]))
+ if abs(r1.mag()-r2.mag()) < 0.001 :
+ gcode += ("G02" if s[3]*flip_angle<0 else "G03") + (" %s %f %s %f %s %f %s %f" % (x,s[4][0],z,s[4][1],i_,(s[2][0]-s[0][0]), k_, (s[2][1]-s[0][1]) ) ) + feed + "\n"
+ else:
+ r = (r1.mag()+r2.mag())/2
+ gcode += ("G02" if s[3]*flip_angle<0 else "G03") + (" %s %f %s %f" % (x,s[4][0],z,y[4][1]) ) + " R%f"%r + feed + "\n"
+ return gcode
+
+
+ def lathe(self):
+ if not self.check_dir() : return
+ x,z = self.options.lathe_x_axis_remap, self.options.lathe_z_axis_remap
+ x = re.sub("^\s*([XYZxyz])\s*$",r"\1",x)
+ z = re.sub("^\s*([XYZxyz])\s*$",r"\1",z)
+ if x not in ["X", "Y", "Z", "x", "y", "z"] or z not in ["X", "Y", "Z", "x", "y", "z"] :
+ self.error(_("Lathe X and Z axis remap should be 'X', 'Y' or 'Z'. Exiting..."),"warning")
+ return
+ if x.lower() == z.lower() :
+ self.error(_("Lathe X and Z axis remap should be the same. Exiting..."),"warning")
+ return
+ if x.lower()+z.lower() in ["xy","yx"] : gcode_plane_selection = "G17 (Using XY plane)\n"
+ if x.lower()+z.lower() in ["xz","zx"] : gcode_plane_selection = "G18 (Using XZ plane)\n"
+ if x.lower()+z.lower() in ["zy","yz"] : gcode_plane_selection = "G19 (Using YZ plane)\n"
+ self.options.lathe_x_axis_remap, self.options.lathe_z_axis_remap = x, z
+
+ paths = self.selected_paths
+ self.tool = []
+ gcode = ""
+ for layer in self.layers :
+ if layer in paths :
+ self.set_tool(layer)
+ if self.tool != self.tools[layer][0] :
+ self.tool = self.tools[layer][0]
+ self.tool["passing feed"] = float(self.tool["passing feed"] if "passing feed" in self.tool else self.tool["feed"])
+ self.tool["feed"] = float(self.tool["feed"])
+ self.tool["fine feed"] = float(self.tool["fine feed"] if "fine feed" in self.tool else self.tool["feed"])
+
+ gcode += ( "(Change tool to %s)\n" % re.sub("\"'\(\)\\\\"," ",self.tool["name"]) ) + self.tool["tool change gcode"] + "\n"
+
+ for path in paths[layer]:
+ csp = self.transform_csp(cubicsuperpath.parsePath(path.get("d")),layer)
+
+ for subpath in csp :
+ # Offset the path if fine cut is defined.
+ fine_cut = subpath[:]
+ if self.options.lathe_fine_cut_width>0 :
+ r = self.options.lathe_fine_cut_width
+ # Close the path to make offset correct
+ bound = csp_simple_bound([subpath])
+ minx,miny,maxx,maxy = csp_true_bounds([subpath])
+ offsetted_subpath = csp_subpath_line_to(subpath[:], [ [subpath[-1][1][0], miny[1]-r*10 ], [subpath[0][1][0], miny[1]-r*10 ], [subpath[0][1][0], subpath[0][1][1] ] ])
+ left,right = subpath[-1][1][0], subpath[0][1][0]
+ if left>right : left, right = right,left
+ offsetted_subpath = csp_offset([offsetted_subpath], r if not csp_subpath_ccw(offsetted_subpath) else -r )
+ offsetted_subpath = csp_clip_by_line(offsetted_subpath, [left,10], [left,0] )
+ offsetted_subpath = csp_clip_by_line(offsetted_subpath, [right,0], [right,10] )
+ offsetted_subpath = csp_clip_by_line(offsetted_subpath, [0, miny[1]-r], [10, miny[1]-r] )
+ #csp_draw(self.transform_csp(offsetted_subpath,layer,True), color = "Green", width = 1)
+ # Join offsetted_subpath together
+ # Hope there wont be any cicles
+ subpath = csp_join_subpaths(offsetted_subpath)[0]
+
+ # Create solid object from path and lathe_width
+ bound = csp_simple_bound([subpath])
+ top_start, top_end = [subpath[0][1][0], self.options.lathe_width+self.options.Zsafe+self.options.lathe_fine_cut_width], [subpath[-1][1][0], self.options.lathe_width+self.options.Zsafe+self.options.lathe_fine_cut_width]
+
+ gcode += ("G01 %s %f F %f \n" % (z, top_start[1], self.tool["passing feed"]) )
+ gcode += ("G01 %s %f %s %f F %f \n" % (x, top_start[0], z, top_start[1], self.tool["passing feed"]) )
+
+ subpath = csp_concat_subpaths(csp_subpath_line_to([],[top_start,subpath[0][1]]), subpath)
+ subpath = csp_subpath_line_to(subpath,[top_end,top_start])
+
+
+ width = max(0, self.options.lathe_width - max(0, bound[1]) )
+ step = self.tool['depth step']
+ steps = int(math.ceil(width/step))
+ for i in range(steps+1):
+ current_width = self.options.lathe_width - step*i
+ intersections = []
+ for j in range(1,len(subpath)) :
+ sp1,sp2 = subpath[j-1], subpath[j]
+ intersections += [[j,k] for k in csp_line_intersection([bound[0]-10,current_width], [bound[2]+10,current_width], sp1, sp2)]
+ intersections += [[j,k] for k in csp_line_intersection([bound[0]-10,current_width+step], [bound[2]+10,current_width+step], sp1, sp2)]
+ parts = csp_subpath_split_by_points(subpath,intersections)
+ for part in parts :
+ minx,miny,maxx,maxy = csp_true_bounds([part])
+ y = (maxy[1]+miny[1])/2
+ if y > current_width+step :
+ gcode += self.generate_lathe_gcode(part,layer,"passing feed")
+ elif current_width <= y <= current_width+step :
+ gcode += self.generate_lathe_gcode(part,layer,"feed")
+ else :
+ # full step cut
+ part = csp_subpath_line_to([], [part[0][1], part[-1][1]] )
+ gcode += self.generate_lathe_gcode(part,layer,"feed")
+
+ top_start, top_end = [fine_cut[0][1][0], self.options.lathe_width+self.options.Zsafe+self.options.lathe_fine_cut_width], [fine_cut[-1][1][0], self.options.lathe_width+self.options.Zsafe+self.options.lathe_fine_cut_width]
+ gcode += "\n(Fine cutting start)\n(Calculating fine cut using %s)\n"%self.options.lathe_create_fine_cut_using
+ for i in range(self.options.lathe_fine_cut_count) :
+ width = self.options.lathe_fine_cut_width*(1-float(i+1)/self.options.lathe_fine_cut_count )
+ if width == 0 :
+ current_pass = fine_cut
+ else :
+ if self.options.lathe_create_fine_cut_using == "Move path" :
+ current_pass = [ [ [i2[0],i2[1]+width] for i2 in i1] for i1 in fine_cut]
+ else :
+ minx,miny,maxx,maxy = csp_true_bounds([fine_cut])
+ offsetted_subpath = csp_subpath_line_to(fine_cut[:], [ [fine_cut[-1][1][0], miny[1]-r*10 ], [fine_cut[0][1][0], miny[1]-r*10 ], [fine_cut[0][1][0], fine_cut[0][1][1] ] ])
+ left,right = fine_cut[-1][1][0], fine_cut[0][1][0]
+ if left>right : left, right = right,left
+ offsetted_subpath = csp_offset([offsetted_subpath], width if not csp_subpath_ccw(offsetted_subpath) else -width )
+ offsetted_subpath = csp_clip_by_line(offsetted_subpath, [left,10], [left,0] )
+ offsetted_subpath = csp_clip_by_line(offsetted_subpath, [right,0], [right,10] )
+ offsetted_subpath = csp_clip_by_line(offsetted_subpath, [0, miny[1]-r], [10, miny[1]-r] )
+ current_pass = csp_join_subpaths(offsetted_subpath)[0]
+
+
+ gcode += "\n(Fine cut %i-th cicle start)\n"%(i+1)
+ gcode += ("G01 %s %f %s %f F %f \n" % (x, top_start[0], z, top_start[1], self.tool["passing feed"]) )
+ gcode += ("G01 %s %f %s %f F %f \n" % (x, current_pass[0][1][0], z, current_pass[0][1][1]+self.options.lathe_fine_cut_width, self.tool["passing feed"]) )
+ gcode += ("G01 %s %f %s %f F %f \n" % (x, current_pass[0][1][0], z, current_pass[0][1][1], self.tool["fine feed"]) )
+
+ gcode += self.generate_lathe_gcode(current_pass,layer,"fine feed")
+ gcode += ("G01 %s %f F %f \n" % (z, top_start[1], self.tool["passing feed"]) )
+ gcode += ("G01 %s %f %s %f F %f \n" % (x, top_start[0], z, top_start[1], self.tool["passing feed"]) )
+
+
+
+ self.export_gcode(gcode)
+
+
+ def update(self) :
+ try :
+ import urllib
+ f = urllib.urlopen("http://www.cnc-club.ru/gcodetools_latest_version", proxies = urllib.getproxies())
+ a = f.read()
+ for s in a.split("\n") :
+ r = re.search(r"Gcodetools\s+latest\s+version\s*=\s*(.*)",s)
+ if r :
+ ver = r.group(1).strip()
+ if ver != gcodetools_current_version :
+ self.error("There is a newer version of Gcodetools you can get it at: \nhttp://www.cnc-club.ru/gcodetools (English version). \nhttp://www.cnc-club.ru/gcodetools_ru (Russian version). ","Warning")
+ else :
+ self.error("You are currently using latest stable version of Gcodetools.","Warning")
+ return
+ self.error("Can not check the latest version. You can check it manualy at \nhttp://www.cnc-club.ru/gcodetools (English version). \nhttp://www.cnc-club.ru/gcodetools_ru (Russian version). \nCurrent version is Gcodetools %s"%gcodetools_current_version,"Warning")
+ except :
+ self.error("Can not check the latest version. You can check it manualy at \nhttp://www.cnc-club.ru/gcodetools (English version). \nhttp://www.cnc-club.ru/gcodetools_ru (Russian version). \nCurrent version is Gcodetools %s"%gcodetools_current_version,"Warning")
+
+
+################################################################################
+###
+### Effect
+###
+### Main function of Gcodetools class
+###
+################################################################################
+ def effect(self) :
+ global options
+ options = self.options
+ options.self = self
+ options.doc_root = self.document.getroot()
+
+ # define print_ function
+ global print_
+ if self.options.log_create_log :
+ try :
+ if os.path.isfile(self.options.log_filename) : os.remove(self.options.log_filename)
+ f = open(self.options.log_filename,"a")
+ f.write("Gcodetools log file.\nStarted at %s.\n%s\n" % (time.strftime("%d.%m.%Y %H:%M:%S"),options.log_filename))
+ f.write("%s tab is active.\n" % self.options.active_tab)
+ f.close()
+ except :
+ print_ = lambda *x : None
+ else : print_ = lambda *x : None
+ if self.options.active_tab == '"help"' :
+ self.help()
+ return
+ elif self.options.active_tab not in ['"dxfpoints"','"path-to-gcode"', '"area"', '"area_artefacts"', '"engraving"', '"orientation"', '"tools_library"', '"lathe"', '"offset"', '"arrangement"', '"update"']:
+ self.error(_("Select one of the active tabs - Path to Gcode, Area, Engraving, DXF points, Orientation, Offset, Lathe or Tools library."),"error")
+ else:
+ # Get all Gcodetools data from the scene.
+ self.get_info()
+ if self.options.active_tab in ['"dxfpoints"','"path-to-gcode"', '"area"', '"area_artefacts"', '"engraving"', '"lathe"']:
+ if self.orientation_points == {} :
+ self.error(_("Orientation points have not been defined! A default set of orientation points has been automatically added."),"warning")
+ self.orientation( self.layers[min(1,len(self.layers)-1)] )
+ self.get_info()
+ if self.tools == {} :
+ self.error(_("Cutting tool has not been defined! A default tool has been automatically added."),"warning")
+ self.options.tools_library_type = "default"
+ self.tools_library( self.layers[min(1,len(self.layers)-1)] )
+ self.get_info()
+ if self.options.active_tab == '"path-to-gcode"':
+ self.path_to_gcode()
+ elif self.options.active_tab == '"area"':
+ self.area()
+ elif self.options.active_tab == '"area_artefacts"':
+ self.area_artefacts()
+ elif self.options.active_tab == '"dxfpoints"':
+ self.dxfpoints()
+ elif self.options.active_tab == '"engraving"':
+ self.engraving()
+ elif self.options.active_tab == '"orientation"':
+ self.orientation()
+ elif self.options.active_tab == '"tools_library"':
+ if self.options.tools_library_type != "check":
+ self.tools_library()
+ else :
+ self.check_tools_and_op()
+ elif self.options.active_tab == '"lathe"':
+ self.lathe()
+ elif self.options.active_tab == '"update"':
+ self.update()
+ elif self.options.active_tab == '"offset"':
+ if self.options.offset_just_get_distance :
+ for layer in self.selected_paths :
+ if len(self.selected_paths[layer]) == 2 :
+ csp1, csp2 = cubicsuperpath.parsePath(self.selected_paths[layer][0].get("d")), cubicsuperpath.parsePath(self.selected_paths[layer][1].get("d"))
+ dist = csp_to_csp_distance(csp1,csp2)
+ print_(dist)
+ draw_pointer( list(csp_at_t(csp1[dist[1]][dist[2]-1],csp1[dist[1]][dist[2]],dist[3]))
+ +list(csp_at_t(csp2[dist[4]][dist[5]-1],csp2[dist[4]][dist[5]],dist[6])),"red","line", comment = math.sqrt(dist[0]))
+ return
+ if self.options.offset_step == 0 : self.options.offset_step = self.options.offset_radius
+ if self.options.offset_step*self.options.offset_radius <0 : self.options.offset_step *= -1
+ time_ = time.time()
+ offsets_count = 0
+ for layer in self.selected_paths :
+ for path in self.selected_paths[layer] :
+
+ offset = self.options.offset_step/2
+ while abs(offset) <= abs(self.options.offset_radius) :
+ offset_ = csp_offset(cubicsuperpath.parsePath(path.get("d")), offset)
+ offsets_count += 1
+ if offset_ != [] :
+ for iii in offset_ :
+ csp_draw([iii], color="Green", width=1)
+ #print_(offset_)
+ else :
+ print_("------------Reached empty offset at radius %s"% offset )
+ break
+ offset += self.options.offset_step
+ print_()
+ print_("-----------------------------------------------------------------------------------")
+ print_("-----------------------------------------------------------------------------------")
+ print_("-----------------------------------------------------------------------------------")
+ print_()
+ print_("Done in %s"%(time.time()-time_))
+ print_("Total offsets count %s"%offsets_count)
+ elif self.options.active_tab == '"arrangement"':
+ self.arrangement()
+#
+e = Gcodetools()
+e.affect()
+
diff --git a/share/extensions/gcodetools_all_in_one.inx b/share/extensions/gcodetools_all_in_one.inx
--- /dev/null
@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
+ <_name>All in one</_name>
+ <id>ru.cnc-club.filter.gcodetools_ptg_area_area_artefacts_engraving_dxfpoints_tools_library_orientation</id>
+ <dependency type="executable" location="extensions">gcodetools.py</dependency>
+ <dependency type="executable" location="extensions">inkex.py</dependency>
+ <param name='active-tab' type="notebook">
+
+ <page name='path-to-gcode' _gui-text='Path to Gcode'>
+ <param name="biarc-tolerance" type='float' precision="5" _gui-text='Biarc interpolation tolerance:'>1</param>
+ <param name="biarc-max-split-depth" type="int" _gui-text="Maximum splitting depth:">4</param>
+ <_param name="help" type="description">
+Biarc interpolation tolerance is the maximum distance between path and its approximation.
+The segment will be split into two segments if the distance between path's segment and it's approximation exceeds biarc interpolation tolerance.
+</_param>
+ </page>
+
+ <page name='area' _gui-text='Area'>
+ <param name="max-area-curves" type="int" min="0" max="1000" _gui-text="Maximum area cutting curves:">100</param>
+ <param name="area-inkscape-radius" type="float" min="-1000" max="1000" _gui-text="Area width:">-10</param>
+
+ <_param name="help" type="description">
+"Create area offset": creates several Inkscape path offsets to fill original path's area up to "Area radius" value.
+
+Outlines start from "1/2 D" up to "Area width" total width with "D" steps where D is taken from the nearest tool definition ("Tool diameter" value).
+Only one offset will be created if the "Area width" is equal to "1/2 D".
+ </_param>
+ </page>
+
+ <page name='area_artefacts' _gui-text='Area artefacts'>
+ <param name="area-find-artefacts-diameter" type="float" min="0.01" max="1000" _gui-text="Artefact diameter:">5.0</param>
+ <param name="area-find-artefacts-action" type="optiongroup" _gui-text="Action:">
+ <_option value="mark with an arrow">mark with an arrow</_option>
+ <_option value="mark with style">mark with style</_option>
+ <_option value="delete">delete</_option>
+ </param>
+ <_param name="help" type="description">
+Usage:
+1. Select all Area Offsets (gray outlines)
+2. Object/Ungroup (Shift+Ctrl+G)
+3. Press Apply
+
+Suspected small objects will be marked out by colored arrows.
+ </_param>
+ </page>
+
+ <page name='engraving' _gui-text='Engraving'>
+ <param name="engraving-sharp-angle-tollerance" type="float" precision="5" min="0" max="180" _gui-text="Sharp angle tolerance:">150</param>
+ <param name="engraving-max-dist" type="float" precision="5" min="0" max="1000" _gui-text="Maximum distance for engraving:">10</param>
+ <param name="engraving-newton-iterations" type="int" min="2" max="10" _gui-text="Number of sample points used to calculate distance:">4</param>
+ <param name="engraving-draw-calculation-paths" type="boolean" _gui-text="Draw additional graphics to debug engraving path:">false</param>
+
+ <_param name="help" type="description">
+This function creates path to engrave sharp angles.
+Cutter's shape function is defined by the tool. Some simple shapes:
+
+cone....(45 degrees)...........: w
+cone....(height/diameter=10/3).: 10/3 w
+sphere..("r" diameter).........: math.sqrt(max(0,r**2-w**2))
+ellipse.(R1=r and R2=r*4r).....: math.sqrt(max(0,r**2-w**2))*4</_param>
+ </page>
+
+ <page name='dxfpoints' _gui-text='DXF points'>
+ <_param name="help" type="description">
+
+Convert selected objects to drill points (as dxf_import plugin does). Also you can save original shape. Only the start point of each curve will be used.
+
+Also you can manually select object, open XML editor (Shift+Ctrl+X) and add or remove XML tag 'dxfpoint' with any value.
+ </_param>
+ <param type='optiongroup' name='dxfpoints-action' _gui-text="Convert selection:">
+<_option value='save'>set as dxfpoint and save shape</_option>
+<_option value='replace'>set as dxfpoint and draw arrow</_option>
+<_option value='clear'>clear dxfpoint sign</_option>
+ </param>
+
+ </page>
+
+ <page name='tools_library' _gui-text='Tools library'>
+
+ <param type='optiongroup' name='tools-library-type' _gui-text="Tools type:">
+<_option value='default tool'>default</_option>
+<_option value='cylinder cutter'>cylinder</_option>
+<_option value='cone cutter'>cone</_option>
+<_option value='plasma cutter'>plasma</_option>
+<_option value='tangent knife'>tangent knife</_option>
+<_option value='lathe cutter'>lathe cutter</_option>
+
+<_option value='check'>Just check tools</_option>
+
+ </param>
+
+ <_param name="help" type="description">
+Selected tool type fills appropriate default values. You can change these values using the Text tool later on.
+
+The topmost (z order) tool in the active layer is used. If there is no tool inside the current layer it is taken from the upper layer.
+
+Press Apply to create new tool.
+ </_param>
+ </page>
+
+ <page name='orientation' _gui-text='Orientation'>
+
+ <param name="orientation-points-count" type="optiongroup" _gui-text="Orientation type:">
+<_option value="2">2-points mode
+(move and rotate,
+maintained aspect ratio X/Y)</_option>
+<_option value="3">3-points mode
+(move, rotate and mirror,
+different X/Y scale)</_option>
+ </param>
+ <param name="Zsurface" type="float" precision="5" min="-1000" max="1000" _gui-text="Z surface:">0</param>
+ <param name="Zdepth" type="float" precision="5" min="-1000" max="1000" _gui-text="Z depth:">-1</param>
+ <param name="unit" type="enum" _gui-text="Units (mm or in):">
+ <item value="G21 (All units in mm)">mm</item>
+ <item value="G20 (All units in inches)">in</item>
+ </param>
+
+ <_param name="help" type="description">
+Orientation points are used to calculate transformation (offset,scale,mirror,rotation in XY plane) of the path.
+3-points mode only: do not put all three into one line (use 2-points mode instead).
+
+You can modify Z surface, Z depth values later using text tool (3rd coordinates).
+
+If there are no orientation points inside current layer they are taken from the upper layer.
+
+Do not ungroup orientation points! You can select them using double click to enter the group or by Ctrl+Click.
+
+Now press apply to create control points (independent set for each layer).
+ </_param>
+ </page>
+
+ <page name='options' _gui-text='Options'>
+ <param name="Zscale" type="float" precision="5" min="-100000" max="100000" _gui-text="Scale along Z axis:">1</param>
+ <param name="Zoffset" type="float" precision="5" min="-100000" max="100000" _gui-text="Offset along Z axis:">0.0</param>
+ <param name="auto_select_paths" type="boolean" _gui-text="Select all paths if nothing is selected">true</param>
+ <param name="min-arc-radius" type="float" precision="5" min="-1000" max="1000" _gui-text="Minimum arc radius:">0.05</param>
+ </page>
+
+ <page name='preferences' _gui-text='Preferences'>
+ <param name="filename" type="string" _gui-text="File:">output.ngc</param>
+ <param name="add-numeric-suffix-to-filename" type="boolean" _gui-text="Add numeric suffix to filename">true</param>
+
+ <param name="directory" type="string" _gui-text="Directory:">/home</param>
+
+ <param name="Zsafe" type="float" precision="5" min="-1000" max="1000" _gui-text="Z safe height for G00 move over blank:">5</param>
+ <param name="unit" type="enum" _gui-text="Units (mm or in):">
+ <item value="G21 (All units in mm)">mm</item>
+ <item value="G20 (All units in inches)">in</item>
+ </param>
+ <param name="postprocessor" type="enum" _gui-text="Post-processor:">
+ <item value=" ">None</item>
+ <item value="parameterize();">Parameterize Gcode</item>
+ <item value="flip(y);parameterize();">Flip y axis and parameterize Gcode</item>
+ <item value="round(4);">Round all values to 4 digits</item>
+ </param>
+ <param name="postprocessor-custom" type="string" _gui-text="Additional post-processor:"></param>
+
+
+ <param name="create-log" type="boolean" _gui-text="Generate log file">false</param>
+ <param name="log-filename" type="string" _gui-text="Full path to log file:"></param>
+
+ </page>
+
+ <page name='help' _gui-text='Help'>
+ <_param name="fullhelp" type="description">
+Gcodetools plug-in: converts paths to Gcode (using circular interpolation), makes offset paths and engraves sharp corners using cone cutters.
+This plug-in calculates Gcode for paths using circular interpolation or linear motion when needed.
+
+Tutorials, manuals and support can be found at
+English support forum:
+ http://www.cnc-club.ru/gcodetools
+
+and Russian support forum:
+ http://www.cnc-club.ru/gcodetoolsru
+
+Credits: Nick Drobchenko, Vladimir Kalyaev, John Brooker, Henry Nicolas.
+
+Gcodetools ver. 1.6.01
+</_param>
+
+ </page>
+
+ </param>
+ <effect>
+ <effects-menu>
+ <submenu _name="Gcodetools"/>
+ </effects-menu>
+ <object-type>path</object-type>
+ </effect>
+ <script>
+ <command reldir="extensions" interpreter="python">gcodetools.py</command>
+ </script>
+
+</inkscape-extension>
diff --git a/share/extensions/gcodetools_area.inx b/share/extensions/gcodetools_area.inx
--- /dev/null
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
+ <_name>Area</_name>
+ <id>ru.cnc-club.filter.gcodetools_area_area_artefacts_ptg</id>
+ <dependency type="executable" location="extensions">gcodetools.py</dependency>
+ <dependency type="executable" location="extensions">inkex.py</dependency>
+ <param name='active-tab' type="notebook">
+
+ <page name='area' _gui-text='Area'>
+ <param name="max-area-curves" type="int" min="0" max="1000" _gui-text="Maximum area cutting curves:">100</param>
+ <param name="area-inkscape-radius" type="float" min="-1000" max="1000" _gui-text="Area width:">-10</param>
+
+ <_param name="help" type="description">
+"Create area offset": creates several Inkscape path offsets to fill original path's area up to "Area radius" value.
+
+Outlines start from "1/2 D" up to "Area width" total width with "D" steps where D is taken from the nearest tool definition ("Tool diameter" value).
+Only one offset will be created if the "Area width" is equal to "1/2 D".
+ </_param>
+ </page>
+
+ <page name='area_artefacts' _gui-text='Area artefacts'>
+ <param name="area-find-artefacts-diameter" type="float" min="0.01" max="1000" _gui-text="Artefact diameter:">5.0</param>
+ <param name="area-find-artefacts-action" type="optiongroup" _gui-text="Action:">
+ <_option value="mark with an arrow">mark with an arrow</_option>
+ <_option value="mark with style">mark with style</_option>
+ <_option value="delete">delete</_option>
+ </param>
+ <_param name="help" type="description">
+Usage:
+1. Select all Area Offsets (gray outlines)
+2. Object/Ungroup (Shift+Ctrl+G)
+3. Press Apply
+
+Suspected small objects will be marked out by colored arrows.
+ </_param>
+ </page>
+
+ <page name='path-to-gcode' _gui-text='Path to Gcode'>
+ <param name="biarc-tolerance" type='float' precision="5" _gui-text='Biarc interpolation tolerance:'>1</param>
+ <param name="biarc-max-split-depth" type="int" _gui-text="Maximum splitting depth:">4</param>
+ <_param name="help" type="description">
+Biarc interpolation tolerance is the maximum distance between path and its approximation.
+The segment will be split into two segments if the distance between path's segment and it's approximation exceeds biarc interpolation tolerance.
+</_param>
+ </page>
+
+ <page name='options' _gui-text='Options'>
+ <param name="Zscale" type="float" precision="5" min="-100000" max="100000" _gui-text="Scale along Z axis:">1</param>
+ <param name="Zoffset" type="float" precision="5" min="-100000" max="100000" _gui-text="Offset along Z axis:">0.0</param>
+ <param name="auto_select_paths" type="boolean" _gui-text="Select all paths if nothing is selected">true</param>
+ <param name="min-arc-radius" type="float" precision="5" min="-1000" max="1000" _gui-text="Minimum arc radius:">0.05</param>
+ </page>
+
+ <page name='preferences' _gui-text='Preferences'>
+ <param name="filename" type="string" _gui-text="File:">output.ngc</param>
+ <param name="add-numeric-suffix-to-filename" type="boolean" _gui-text="Add numeric suffix to filename">true</param>
+
+ <param name="directory" type="string" _gui-text="Directory:">/home</param>
+
+ <param name="Zsafe" type="float" precision="5" min="-1000" max="1000" _gui-text="Z safe height for G00 move over blank:">5</param>
+ <param name="unit" type="enum" _gui-text="Units (mm or in):">
+ <item value="G21 (All units in mm)">mm</item>
+ <item value="G20 (All units in inches)">in</item>
+ </param>
+ <param name="postprocessor" type="enum" _gui-text="Post-processor:">
+ <item value=" ">None</item>
+ <item value="parameterize();">Parameterize Gcode</item>
+ <item value="flip(y);parameterize();">Flip y axis and parameterize Gcode</item>
+ <item value="round(4);">Round all values to 4 digits</item>
+ </param>
+ <param name="postprocessor-custom" type="string" _gui-text="Additional post-processor:"></param>
+
+
+ <param name="create-log" type="boolean" _gui-text="Generate log file">false</param>
+ <param name="log-filename" type="string" _gui-text="Full path to log file:"></param>
+
+ </page>
+
+ <page name='help' _gui-text='Help'>
+ <_param name="fullhelp" type="description">
+Gcodetools plug-in: converts paths to Gcode (using circular interpolation), makes offset paths and engraves sharp corners using cone cutters.
+This plug-in calculates Gcode for paths using circular interpolation or linear motion when needed.
+
+Tutorials, manuals and support can be found at
+English support forum:
+ http://www.cnc-club.ru/gcodetools
+
+and Russian support forum:
+ http://www.cnc-club.ru/gcodetoolsru
+
+Credits: Nick Drobchenko, Vladimir Kalyaev, John Brooker, Henry Nicolas.
+
+Gcodetools ver. 1.6.01
+</_param>
+
+ </page>
+
+ </param>
+ <effect>
+ <effects-menu>
+ <submenu _name="Gcodetools"/>
+ </effects-menu>
+ <object-type>path</object-type>
+ </effect>
+ <script>
+ <command reldir="extensions" interpreter="python">gcodetools.py</command>
+ </script>
+
+</inkscape-extension>
diff --git a/share/extensions/gcodetools_check_for_updates.inx b/share/extensions/gcodetools_check_for_updates.inx
--- /dev/null
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
+ <_name>Check for updates</_name>
+ <id>ru.cnc-club.filter.gcodetools_update_no_options_no_preferences</id>
+ <dependency type="executable" location="extensions">gcodetools.py</dependency>
+ <dependency type="executable" location="extensions">inkex.py</dependency>
+ <param name='active-tab' type="notebook">
+
+ <page name='update' _gui-text='Check for updates'>
+ <_param name="help" type="description">Check for Gcodetools latest stable version and try to get the updates.</_param>
+ </page>
+
+ <page name='help' _gui-text='Help'>
+ <_param name="fullhelp" type="description">
+Gcodetools plug-in: converts paths to Gcode (using circular interpolation), makes offset paths and engraves sharp corners using cone cutters.
+This plug-in calculates Gcode for paths using circular interpolation or linear motion when needed.
+
+Tutorials, manuals and support can be found at
+English support forum:
+ http://www.cnc-club.ru/gcodetools
+
+and Russian support forum:
+ http://www.cnc-club.ru/gcodetoolsru
+
+Credits: Nick Drobchenko, Vladimir Kalyaev, John Brooker, Henry Nicolas.
+
+Gcodetools ver. 1.6.01
+</_param>
+
+ </page>
+
+ </param>
+ <effect>
+ <effects-menu>
+ <submenu _name="Gcodetools"/>
+ </effects-menu>
+ <object-type>path</object-type>
+ </effect>
+ <script>
+ <command reldir="extensions" interpreter="python">gcodetools.py</command>
+ </script>
+
+</inkscape-extension>
diff --git a/share/extensions/gcodetools_dxf_points.inx b/share/extensions/gcodetools_dxf_points.inx
--- /dev/null
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
+ <_name>DXF Points</_name>
+ <id>ru.cnc-club.filter.gcodetools_dxfpoints_no_options</id>
+ <dependency type="executable" location="extensions">gcodetools.py</dependency>
+ <dependency type="executable" location="extensions">inkex.py</dependency>
+ <param name='active-tab' type="notebook">
+
+ <page name='dxfpoints' _gui-text='DXF points'>
+ <_param name="help" type="description">
+
+Convert selected objects to drill points (as dxf_import plugin does). Also you can save original shape. Only the start point of each curve will be used.
+
+Also you can manually select object, open XML editor (Shift+Ctrl+X) and add or remove XML tag 'dxfpoint' with any value.
+ </_param>
+ <param type='optiongroup' name='dxfpoints-action' _gui-text="Convert selection:">
+<_option value='save'>set as dxfpoint and save shape</_option>
+<_option value='replace'>set as dxfpoint and draw arrow</_option>
+<_option value='clear'>clear dxfpoint sign</_option>
+ </param>
+
+ </page>
+
+ <page name='preferences' _gui-text='Preferences'>
+ <param name="filename" type="string" _gui-text="File:">output.ngc</param>
+ <param name="add-numeric-suffix-to-filename" type="boolean" _gui-text="Add numeric suffix to filename">true</param>
+
+ <param name="directory" type="string" _gui-text="Directory:">/home</param>
+
+ <param name="Zsafe" type="float" precision="5" min="-1000" max="1000" _gui-text="Z safe height for G00 move over blank:">5</param>
+ <param name="unit" type="enum" _gui-text="Units (mm or in):">
+ <item value="G21 (All units in mm)">mm</item>
+ <item value="G20 (All units in inches)">in</item>
+ </param>
+ <param name="postprocessor" type="enum" _gui-text="Post-processor:">
+ <item value=" ">None</item>
+ <item value="parameterize();">Parameterize Gcode</item>
+ <item value="flip(y);parameterize();">Flip y axis and parameterize Gcode</item>
+ <item value="round(4);">Round all values to 4 digits</item>
+ </param>
+ <param name="postprocessor-custom" type="string" _gui-text="Additional post-processor:"></param>
+
+
+ <param name="create-log" type="boolean" _gui-text="Generate log file">false</param>
+ <param name="log-filename" type="string" _gui-text="Full path to log file:"></param>
+
+ </page>
+
+ <page name='help' _gui-text='Help'>
+ <_param name="fullhelp" type="description">
+Gcodetools plug-in: converts paths to Gcode (using circular interpolation), makes offset paths and engraves sharp corners using cone cutters.
+This plug-in calculates Gcode for paths using circular interpolation or linear motion when needed.
+
+Tutorials, manuals and support can be found at
+English support forum:
+ http://www.cnc-club.ru/gcodetools
+
+and Russian support forum:
+ http://www.cnc-club.ru/gcodetoolsru
+
+Credits: Nick Drobchenko, Vladimir Kalyaev, John Brooker, Henry Nicolas.
+
+Gcodetools ver. 1.6.01
+</_param>
+
+ </page>
+
+ </param>
+ <effect>
+ <effects-menu>
+ <submenu _name="Gcodetools"/>
+ </effects-menu>
+ <object-type>path</object-type>
+ </effect>
+ <script>
+ <command reldir="extensions" interpreter="python">gcodetools.py</command>
+ </script>
+
+</inkscape-extension>
diff --git a/share/extensions/gcodetools_engraving.inx b/share/extensions/gcodetools_engraving.inx
--- /dev/null
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
+ <_name>Engraving</_name>
+ <id>ru.cnc-club.filter.gcodetools_engraving</id>
+ <dependency type="executable" location="extensions">gcodetools.py</dependency>
+ <dependency type="executable" location="extensions">inkex.py</dependency>
+ <param name='active-tab' type="notebook">
+
+ <page name='engraving' _gui-text='Engraving'>
+ <param name="engraving-sharp-angle-tollerance" type="float" precision="5" min="0" max="180" _gui-text="Sharp angle tolerance:">150</param>
+ <param name="engraving-max-dist" type="float" precision="5" min="0" max="1000" _gui-text="Maximum distance for engraving:">10</param>
+ <param name="engraving-newton-iterations" type="int" min="2" max="10" _gui-text="Number of sample points used to calculate distance:">4</param>
+ <param name="engraving-draw-calculation-paths" type="boolean" _gui-text="Draw additional graphics to debug engraving path">false</param>
+
+ <_param name="help" type="description">
+This function creates path to engrave sharp angles.
+Cutter's shape function is defined by the tool. Some simple shapes:
+
+cone....(45 degrees)...........: w
+cone....(height/diameter=10/3).: 10/3 w
+sphere..("r" diameter).........: math.sqrt(max(0,r**2-w**2))
+ellipse.(R1=r and R2=r*4r).....: math.sqrt(max(0,r**2-w**2))*4</_param>
+ </page>
+
+ <page name='options' _gui-text='Options'>
+ <param name="Zscale" type="float" precision="5" min="-100000" max="100000" _gui-text="Scale along Z axis:">1</param>
+ <param name="Zoffset" type="float" precision="5" min="-100000" max="100000" _gui-text="Offset along Z axis:">0.0</param>
+ <param name="auto_select_paths" type="boolean" _gui-text="Select all paths if nothing is selected">true</param>
+ <param name="min-arc-radius" type="float" precision="5" min="-1000" max="1000" _gui-text="Minimum arc radius:">0.05</param>
+ </page>
+
+ <page name='preferences' _gui-text='Preferences'>
+ <param name="filename" type="string" _gui-text="File:">output.ngc</param>
+ <param name="add-numeric-suffix-to-filename" type="boolean" _gui-text="Add numeric suffix to filename">true</param>
+
+ <param name="directory" type="string" _gui-text="Directory:">/home</param>
+
+ <param name="Zsafe" type="float" precision="5" min="-1000" max="1000" _gui-text="Z safe height for G00 move over blank:">5</param>
+ <param name="unit" type="enum" _gui-text="Units (mm or in):">
+ <item value="G21 (All units in mm)">mm</item>
+ <item value="G20 (All units in inches)">in</item>
+ </param>
+ <param name="postprocessor" type="enum" _gui-text="Post-processor:">
+ <item value=" ">None</item>
+ <item value="parameterize();">Parameterize Gcode</item>
+ <item value="flip(y);parameterize();">Flip y axis and parameterize Gcode</item>
+ <item value="round(4);">Round all values to 4 digits</item>
+ </param>
+ <param name="postprocessor-custom" type="string" _gui-text="Additional post-processor:"></param>
+
+
+ <param name="create-log" type="boolean" _gui-text="Generate log file">false</param>
+ <param name="log-filename" type="string" _gui-text="Full path to log file:"></param>
+
+ </page>
+
+ <page name='help' _gui-text='Help'>
+ <_param name="fullhelp" type="description">
+Gcodetools plug-in: converts paths to Gcode (using circular interpolation), makes offset paths and engraves sharp corners using cone cutters.
+This plug-in calculates Gcode for paths using circular interpolation or linear motion when needed.
+
+Tutorials, manuals and support can be found at
+English support forum:
+ http://www.cnc-club.ru/gcodetools
+
+and Russian support forum:
+ http://www.cnc-club.ru/gcodetoolsru
+
+Credits: Nick Drobchenko, Vladimir Kalyaev, John Brooker, Henry Nicolas.
+
+Gcodetools ver. 1.6.01
+</_param>
+
+ </page>
+
+ </param>
+ <effect>
+ <effects-menu>
+ <submenu _name="Gcodetools"/>
+ </effects-menu>
+ <object-type>path</object-type>
+ </effect>
+ <script>
+ <command reldir="extensions" interpreter="python">gcodetools.py</command>
+ </script>
+
+</inkscape-extension>
diff --git a/share/extensions/gcodetools_lathe.inx b/share/extensions/gcodetools_lathe.inx
--- /dev/null
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
+ <_name>Lathe</_name>
+ <id>ru.cnc-club.filter.gcodetools_lathe_ptg</id>
+ <dependency type="executable" location="extensions">gcodetools.py</dependency>
+ <dependency type="executable" location="extensions">inkex.py</dependency>
+ <param name='active-tab' type="notebook">
+
+ <page name='lathe' _gui-text='Lathe'>
+ <param name="lathe-width" type="float" precision="5" min="0" max="1000" _gui-text="Lathe width:">10</param>
+ <param name="lathe-fine-cut-width" type="float" precision="5" min="0" max="1000" _gui-text="Fine cut width:">1</param>
+ <param name="lathe-fine-cut-count" type="int" min="0" max="1000" _gui-text="Fine cut count:">1</param>
+ <param name="lathe-create-fine-cut-using" _gui-text="Create fine cut using:" type="optiongroup" appearance="minimal">
+ <option value="Move path">Move path</option>
+ <option value="Offset path">Offset path</option>
+ </param>
+ <param name="lathe-x-axis-remap" type="string" _gui-text="Lathe X axis remap:">X</param>
+ <param name="lathe-z-axis-remap" type="string" _gui-text="Lathe Z axis remap:">Z</param>
+
+
+ </page>
+
+ <page name='path-to-gcode' _gui-text='Path to Gcode'>
+ <param name="biarc-tolerance" type='float' precision="5" _gui-text='Biarc interpolation tolerance:'>1</param>
+ <param name="biarc-max-split-depth" type="int" _gui-text="Maximum splitting depth:">4</param>
+ <_param name="help" type="description">
+Biarc interpolation tolerance is the maximum distance between path and its approximation.
+The segment will be split into two segments if the distance between path's segment and it's approximation exceeds biarc interpolation tolerance.
+</_param>
+ </page>
+
+ <page name='options' _gui-text='Options'>
+ <param name="Zscale" type="float" precision="5" min="-100000" max="100000" _gui-text="Scale along Z axis:">1</param>
+ <param name="Zoffset" type="float" precision="5" min="-100000" max="100000" _gui-text="Offset along Z axis:">0.0</param>
+ <param name="auto_select_paths" type="boolean" _gui-text="Select all paths if nothing is selected">true</param>
+ <param name="min-arc-radius" type="float" precision="5" min="-1000" max="1000" _gui-text="Minimum arc radius:">0.05</param>
+ </page>
+
+ <page name='preferences' _gui-text='Preferences'>
+ <param name="filename" type="string" _gui-text="File">output.ngc</param>
+ <param name="add-numeric-suffix-to-filename" type="boolean" _gui-text="Add numeric suffix to filename">true</param>
+
+ <param name="directory" type="string" _gui-text="Directory:">/home</param>
+
+ <param name="Zsafe" type="float" precision="5" min="-1000" max="1000" _gui-text="Z safe height for G00 move over blank:">5</param>
+ <param name="unit" type="enum" _gui-text="Units (mm or in):">
+ <item value="G21 (All units in mm)">mm</item>
+ <item value="G20 (All units in inches)">in</item>
+ </param>
+ <param name="postprocessor" type="enum" _gui-text="Post-processor:">
+ <item value=" ">None</item>
+ <item value="parameterize();">Parameterize Gcode</item>
+ <item value="flip(y);parameterize();">Flip y axis and parameterize Gcode</item>
+ <item value="round(4);">Round all values to 4 digits</item>
+ </param>
+ <param name="postprocessor-custom" type="string" _gui-text="Additional post-processor:"></param>
+
+
+ <param name="create-log" type="boolean" _gui-text="Generate log file">false</param>
+ <param name="log-filename" type="string" _gui-text="Full path to log file:"></param>
+
+ </page>
+
+ <page name='help' _gui-text='Help'>
+ <_param name="fullhelp" type="description">
+Gcodetools plug-in: converts paths to Gcode (using circular interpolation), makes offset paths and engraves sharp corners using cone cutters.
+This plug-in calculates Gcode for paths using circular interpolation or linear motion when needed.
+
+Tutorials, manuals and support can be found at
+English support forum:
+ http://www.cnc-club.ru/gcodetools
+
+and Russian support forum:
+ http://www.cnc-club.ru/gcodetoolsru
+
+Credits: Nick Drobchenko, Vladimir Kalyaev, John Brooker, Henry Nicolas.
+
+Gcodetools ver. 1.6.01
+</_param>
+
+ </page>
+
+ </param>
+ <effect>
+ <effects-menu>
+ <submenu _name="Gcodetools"/>
+ </effects-menu>
+ <object-type>path</object-type>
+ </effect>
+ <script>
+ <command reldir="extensions" interpreter="python">gcodetools.py</command>
+ </script>
+
+</inkscape-extension>
diff --git a/share/extensions/gcodetools_orientation_points.inx b/share/extensions/gcodetools_orientation_points.inx
--- /dev/null
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
+ <_name>Orientation points</_name>
+ <id>ru.cnc-club.filter.gcodetools_orientation_no_options_no_preferences</id>
+ <dependency type="executable" location="extensions">gcodetools.py</dependency>
+ <dependency type="executable" location="extensions">inkex.py</dependency>
+ <param name='active-tab' type="notebook">
+
+ <page name='orientation' _gui-text='Orientation'>
+
+ <param name="orientation-points-count" type="optiongroup" _gui-text="Orientation type:">
+<_option value="2">2-points mode
+(move and rotate,
+maintained aspect ratio X/Y)</_option>
+<_option value="3">3-points mode
+(move, rotate and mirror,
+different X/Y scale)</_option>
+ </param>
+ <param name="Zsurface" type="float" precision="5" min="-1000" max="1000" _gui-text="Z surface:">0</param>
+ <param name="Zdepth" type="float" precision="5" min="-1000" max="1000" _gui-text="Z depth:">-1</param>
+ <param name="unit" type="enum" _gui-text="Units (mm or in):">
+ <item value="G21 (All units in mm)">mm</item>
+ <item value="G20 (All units in inches)">in</item>
+ </param>
+
+ <_param name="help" type="description">
+Orientation points are used to calculate transformation (offset,scale,mirror,rotation in XY plane) of the path.
+3-points mode only: do not put all three into one line (use 2-points mode instead).
+
+You can modify Z surface, Z depth values later using text tool (3rd coordinates).
+
+If there are no orientation points inside current layer they are taken from the upper layer.
+
+Do not ungroup orientation points! You can select them using double click to enter the group or by Ctrl+Click.
+
+Now press apply to create control points (independent set for each layer).
+ </_param>
+ </page>
+
+ <page name='help' _gui-text='Help'>
+ <_param name="fullhelp" type="description">
+Gcodetools plug-in: converts paths to Gcode (using circular interpolation), makes offset paths and engraves sharp corners using cone cutters.
+This plug-in calculates Gcode for paths using circular interpolation or linear motion when needed.
+
+Tutorials, manuals and support can be found at
+English support forum:
+ http://www.cnc-club.ru/gcodetools
+
+and Russian support forum:
+ http://www.cnc-club.ru/gcodetoolsru
+
+Credits: Nick Drobchenko, Vladimir Kalyaev, John Brooker, Henry Nicolas.
+
+Gcodetools ver. 1.6.01
+</_param>
+
+ </page>
+
+ </param>
+ <effect>
+ <effects-menu>
+ <submenu _name="Gcodetools"/>
+ </effects-menu>
+ <object-type>path</object-type>
+ </effect>
+ <script>
+ <command reldir="extensions" interpreter="python">gcodetools.py</command>
+ </script>
+
+</inkscape-extension>
diff --git a/share/extensions/gcodetools_path_to_gcode.inx b/share/extensions/gcodetools_path_to_gcode.inx
--- /dev/null
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
+ <_name>Path to Gcode</_name>
+ <id>ru.cnc-club.filter.gcodetools_ptg</id>
+ <dependency type="executable" location="extensions">gcodetools.py</dependency>
+ <dependency type="executable" location="extensions">inkex.py</dependency>
+ <param name='active-tab' type="notebook">
+
+ <page name='path-to-gcode' _gui-text='Path to Gcode'>
+ <param name="biarc-tolerance" type='float' precision="5" _gui-text='Biarc interpolation tolerance:'>1</param>
+ <param name="biarc-max-split-depth" type="int" _gui-text="Maximum splitting depth:">4</param>
+ <_param name="help" type="description">
+Biarc interpolation tolerance is the maximum distance between path and its approximation.
+The segment will be split into two segments if the distance between path's segment and it's approximation exceeds biarc interpolation tolerance.
+</_param>
+ </page>
+
+ <page name='options' _gui-text='Options'>
+ <param name="Zscale" type="float" precision="5" min="-100000" max="100000" _gui-text="Scale along Z axis:">1</param>
+ <param name="Zoffset" type="float" precision="5" min="-100000" max="100000" _gui-text="Offset along Z axis:">0.0</param>
+ <param name="auto_select_paths" type="boolean" _gui-text="Select all paths if nothing is selected">true</param>
+ <param name="min-arc-radius" type="float" precision="5" min="-1000" max="1000" _gui-text="Minimum arc radius:">0.05</param>
+ </page>
+
+ <page name='preferences' _gui-text='Preferences'>
+ <param name="filename" type="string" _gui-text="File:">output.ngc</param>
+ <param name="add-numeric-suffix-to-filename" type="boolean" _gui-text="Add numeric suffix to filename">true</param>
+
+ <param name="directory" type="string" _gui-text="Directory:">/home</param>
+
+ <param name="Zsafe" type="float" precision="5" min="-1000" max="1000" _gui-text="Z safe height for G00 move over blank:">5</param>
+ <param name="unit" type="enum" _gui-text="Units (mm or in):">
+ <item value="G21 (All units in mm)">mm</item>
+ <item value="G20 (All units in inches)">in</item>
+ </param>
+ <param name="postprocessor" type="enum" _gui-text="Post-processor:">
+ <item value=" ">None</item>
+ <item value="parameterize();">Parameterize Gcode</item>
+ <item value="flip(y);parameterize();">Flip y axis and parameterize Gcode</item>
+ <item value="round(4);">Round all values to 4 digits</item>
+ </param>
+ <param name="postprocessor-custom" type="string" _gui-text="Additional post-processor:"></param>
+
+
+ <param name="create-log" type="boolean" _gui-text="Generate log file">false</param>
+ <param name="log-filename" type="string" _gui-text="Full path to log file:"></param>
+
+ </page>
+
+ <page name='help' _gui-text='Help'>
+ <_param name="fullhelp" type="description">
+Gcodetools plug-in: converts paths to Gcode (using circular interpolation), makes offset paths and engraves sharp corners using cone cutters.
+This plug-in calculates Gcode for paths using circular interpolation or linear motion when needed.
+
+Tutorials, manuals and support can be found at
+English support forum:
+ http://www.cnc-club.ru/gcodetools
+
+and Russian support forum:
+ http://www.cnc-club.ru/gcodetoolsru
+
+Credits: Nick Drobchenko, Vladimir Kalyaev, John Brooker, Henry Nicolas.
+
+Gcodetools ver. 1.6.01
+</_param>
+
+ </page>
+
+ </param>
+ <effect>
+ <effects-menu>
+ <submenu _name="Gcodetools"/>
+ </effects-menu>
+ <object-type>path</object-type>
+ </effect>
+ <script>
+ <command reldir="extensions" interpreter="python">gcodetools.py</command>
+ </script>
+
+</inkscape-extension>
diff --git a/share/extensions/gcodetools_tools_library.inx b/share/extensions/gcodetools_tools_library.inx
--- /dev/null
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
+ <_name>Tools library</_name>
+ <id>ru.cnc-club.filter.gcodetools_tools_library_no_options_no_preferences</id>
+ <dependency type="executable" location="extensions">gcodetools.py</dependency>
+ <dependency type="executable" location="extensions">inkex.py</dependency>
+ <param name='active-tab' type="notebook">
+
+ <page name='tools_library' _gui-text='Tools library'>
+
+ <param type='optiongroup' name='tools-library-type' _gui-text="Tools type:">
+<_option value='default tool'>default</_option>
+<_option value='cylinder cutter'>cylinder</_option>
+<_option value='cone cutter'>cone</_option>
+<_option value='plasma cutter'>plasma</_option>
+<_option value='tangent knife'>tangent knife</_option>
+<_option value='lathe cutter'>lathe cutter</_option>
+
+<_option value='check'>Just check tools</_option>
+
+ </param>
+
+ <_param name="help" type="description">
+Selected tool type fills appropriate default values. You can change these values using the Text tool later on.
+
+The topmost (z order) tool in the active layer is used. If there is no tool inside the current layer it is taken from the upper layer.
+
+Press Apply to create new tool.
+ </_param>
+ </page>
+
+ <page name='help' _gui-text='Help'>
+ <_param name="fullhelp" type="description">
+Gcodetools plug-in: converts paths to Gcode (using circular interpolation), makes offset paths and engraves sharp corners using cone cutters.
+This plug-in calculates Gcode for paths using circular interpolation or linear motion when needed.
+
+Tutorials, manuals and support can be found at
+English support forum:
+ http://www.cnc-club.ru/gcodetools
+
+and Russian support forum:
+ http://www.cnc-club.ru/gcodetoolsru
+
+Credits: Nick Drobchenko, Vladimir Kalyaev, John Brooker, Henry Nicolas.
+
+Gcodetools ver. 1.6.01
+</_param>
+
+ </page>
+
+ </param>
+ <effect>
+ <effects-menu>
+ <submenu _name="Gcodetools"/>
+ </effects-menu>
+ <object-type>path</object-type>
+ </effect>
+ <script>
+ <command reldir="extensions" interpreter="python">gcodetools.py</command>
+ </script>
+
+</inkscape-extension>