From: JazzyNico Date: Fri, 8 Oct 2010 10:44:19 +0000 (+0200) Subject: Extensions. New Gcode tools extension (Bug #654528). X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=140cab8883269a4e3603c1861bb20690c8db4564;p=inkscape.git Extensions. New Gcode tools extension (Bug #654528). --- diff --git a/po/POTFILES.in b/po/POTFILES.in index 83da4321b..6bd2da756 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -144,6 +144,7 @@ share/extensions/dxf_outlines.py 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 @@ -508,6 +509,15 @@ share/extensions/web-transmit-att.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 a2de67bed..34a16356f 100644 --- a/po/inkscape.pot +++ b/po/inkscape.pot @@ -8,7 +8,7 @@ msgid "" 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 \n" "Language-Team: LANGUAGE \n" @@ -238,6 +238,15 @@ msgstr "" #: ../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 "" @@ -264,6 +273,11 @@ 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 "" @@ -2238,6 +2252,8 @@ msgid "Color Markers to Match Stroke" 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 "" @@ -4067,6 +4083,169 @@ 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 "" @@ -6319,7 +6498,7 @@ msgid "Defines the direction and magnitude of the extrusion" msgstr "" #: ../src/sp-flowtext.cpp:378 ../src/sp-text.cpp:427 -#: ../src/text-context.cpp:1604 +#: ../src/text-context.cpp:1628 msgid " [truncated]" msgstr "" @@ -7797,7 +7976,7 @@ 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 "" @@ -8484,7 +8663,7 @@ msgid "" "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 " @@ -10509,6 +10688,8 @@ msgstr "" #. 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 "" @@ -12757,7 +12938,7 @@ msgstr "" msgid "Nothing 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" @@ -13333,12 +13514,12 @@ msgstr "" msgid "Rotate: %0.2f°; with Ctrl to snap angle" msgstr "" -#: ../src/seltrans.cpp:1367 +#: ../src/seltrans.cpp:1364 #, c-format msgid "Move center to %s, %s" msgstr "" -#: ../src/seltrans.cpp:1542 +#: ../src/seltrans.cpp:1539 #, c-format msgid "" "Move by %s, %s; with Ctrl to restrict to horizontal/vertical; " @@ -13878,120 +14059,120 @@ msgstr "" msgid "Unicode (Enter 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 (Enter to finish): " msgstr "" -#: ../src/text-context.cpp:656 +#: ../src/text-context.cpp:668 #, c-format msgid "Flowed text frame: %s × %s" msgstr "" -#: ../src/text-context.cpp:688 +#: ../src/text-context.cpp:714 msgid "Type text; Enter 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 too small 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); Enter 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); Enter 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 "" "Click to select or create text, drag 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 "" diff --git a/share/extensions/Makefile.am b/share/extensions/Makefile.am index f621837f7..d77ac1fec 100644 --- a/share/extensions/Makefile.am +++ b/share/extensions/Makefile.am @@ -62,6 +62,7 @@ extensions = \ fractalize.py \ funcplot.py \ gears.py\ + gcodetools.py\ generate_voronoi.py \ gimp_xcf.py \ grid_cartesian.py \ @@ -218,6 +219,15 @@ modules = \ 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 new file mode 100644 index 000000000..abf68981d --- /dev/null +++ b/share/extensions/gcodetools.py @@ -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]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 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]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))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 j0, 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)=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 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 (p0a1000000 or abs(R.y)>1000000 or (R-P0).mag options.biarc_tolerance and depthls : + 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]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)=0 and cos>0 : return sin0 and cos<=0 : return sin>sin1 + if sin<=0 and cos<0 : return sin>sin1 + if sin<0 and cos>=0 : return sin 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_gene0: + 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 iself.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]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 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]) 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])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]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 new file mode 100644 index 000000000..0f566fc7b --- /dev/null +++ b/share/extensions/gcodetools_all_in_one.inx @@ -0,0 +1,194 @@ + + + <_name>All in one + ru.cnc-club.filter.gcodetools_ptg_area_area_artefacts_engraving_dxfpoints_tools_library_orientation + gcodetools.py + inkex.py + + + + 1 + 4 + <_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. + + + + + 100 + -10 + + <_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". + + + + + 5.0 + + <_option value="mark with an arrow">mark with an arrow + <_option value="mark with style">mark with style + <_option value="delete">delete + + <_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. + + + + + 150 + 10 + 4 + false + + <_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 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. + + +<_option value='save'>set as dxfpoint and save shape +<_option value='replace'>set as dxfpoint and draw arrow +<_option value='clear'>clear dxfpoint sign + + + + + + + +<_option value='default tool'>default +<_option value='cylinder cutter'>cylinder +<_option value='cone cutter'>cone +<_option value='plasma cutter'>plasma +<_option value='tangent knife'>tangent knife +<_option value='lathe cutter'>lathe cutter + +<_option value='check'>Just check tools + + + + <_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. + + + + + + +<_option value="2">2-points mode +(move and rotate, +maintained aspect ratio X/Y) +<_option value="3">3-points mode +(move, rotate and mirror, +different X/Y scale) + + 0 + -1 + + mm + in + + + <_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). + + + + + 1 + 0.0 + true + 0.05 + + + + output.ngc + true + + /home + + 5 + + mm + in + + + None + Parameterize Gcode + Flip y axis and parameterize Gcode + Round all values to 4 digits + + + + + false + + + + + + <_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 + + + + + + + + + + path + + + + diff --git a/share/extensions/gcodetools_area.inx b/share/extensions/gcodetools_area.inx new file mode 100644 index 000000000..718ae0d9f --- /dev/null +++ b/share/extensions/gcodetools_area.inx @@ -0,0 +1,109 @@ + + + <_name>Area + ru.cnc-club.filter.gcodetools_area_area_artefacts_ptg + gcodetools.py + inkex.py + + + + 100 + -10 + + <_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". + + + + + 5.0 + + <_option value="mark with an arrow">mark with an arrow + <_option value="mark with style">mark with style + <_option value="delete">delete + + <_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. + + + + + 1 + 4 + <_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. + + + + + 1 + 0.0 + true + 0.05 + + + + output.ngc + true + + /home + + 5 + + mm + in + + + None + Parameterize Gcode + Flip y axis and parameterize Gcode + Round all values to 4 digits + + + + + false + + + + + + <_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 + + + + + + + + + + path + + + + diff --git a/share/extensions/gcodetools_check_for_updates.inx b/share/extensions/gcodetools_check_for_updates.inx new file mode 100644 index 000000000..728f5d1e6 --- /dev/null +++ b/share/extensions/gcodetools_check_for_updates.inx @@ -0,0 +1,43 @@ + + + <_name>Check for updates + ru.cnc-club.filter.gcodetools_update_no_options_no_preferences + gcodetools.py + inkex.py + + + + <_param name="help" type="description">Check for Gcodetools latest stable version and try to get the updates. + + + + <_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 + + + + + + + + + + path + + + + diff --git a/share/extensions/gcodetools_dxf_points.inx b/share/extensions/gcodetools_dxf_points.inx new file mode 100644 index 000000000..7283853f9 --- /dev/null +++ b/share/extensions/gcodetools_dxf_points.inx @@ -0,0 +1,79 @@ + + + <_name>DXF Points + ru.cnc-club.filter.gcodetools_dxfpoints_no_options + gcodetools.py + inkex.py + + + + <_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. + + +<_option value='save'>set as dxfpoint and save shape +<_option value='replace'>set as dxfpoint and draw arrow +<_option value='clear'>clear dxfpoint sign + + + + + + output.ngc + true + + /home + + 5 + + mm + in + + + None + Parameterize Gcode + Flip y axis and parameterize Gcode + Round all values to 4 digits + + + + + false + + + + + + <_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 + + + + + + + + + + path + + + + diff --git a/share/extensions/gcodetools_engraving.inx b/share/extensions/gcodetools_engraving.inx new file mode 100644 index 000000000..8645faa27 --- /dev/null +++ b/share/extensions/gcodetools_engraving.inx @@ -0,0 +1,87 @@ + + + <_name>Engraving + ru.cnc-club.filter.gcodetools_engraving + gcodetools.py + inkex.py + + + + 150 + 10 + 4 + false + + <_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 + + + + 1 + 0.0 + true + 0.05 + + + + output.ngc + true + + /home + + 5 + + mm + in + + + None + Parameterize Gcode + Flip y axis and parameterize Gcode + Round all values to 4 digits + + + + + false + + + + + + <_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 + + + + + + + + + + path + + + + diff --git a/share/extensions/gcodetools_lathe.inx b/share/extensions/gcodetools_lathe.inx new file mode 100644 index 000000000..933073649 --- /dev/null +++ b/share/extensions/gcodetools_lathe.inx @@ -0,0 +1,94 @@ + + + <_name>Lathe + ru.cnc-club.filter.gcodetools_lathe_ptg + gcodetools.py + inkex.py + + + + 10 + 1 + 1 + + + + + X + Z + + + + + + 1 + 4 + <_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. + + + + + 1 + 0.0 + true + 0.05 + + + + output.ngc + true + + /home + + 5 + + mm + in + + + None + Parameterize Gcode + Flip y axis and parameterize Gcode + Round all values to 4 digits + + + + + false + + + + + + <_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 + + + + + + + + + + path + + + + diff --git a/share/extensions/gcodetools_orientation_points.inx b/share/extensions/gcodetools_orientation_points.inx new file mode 100644 index 000000000..997b24d9b --- /dev/null +++ b/share/extensions/gcodetools_orientation_points.inx @@ -0,0 +1,70 @@ + + + <_name>Orientation points + ru.cnc-club.filter.gcodetools_orientation_no_options_no_preferences + gcodetools.py + inkex.py + + + + + +<_option value="2">2-points mode +(move and rotate, +maintained aspect ratio X/Y) +<_option value="3">3-points mode +(move, rotate and mirror, +different X/Y scale) + + 0 + -1 + + mm + in + + + <_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 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 + + + + + + + + + + path + + + + diff --git a/share/extensions/gcodetools_path_to_gcode.inx b/share/extensions/gcodetools_path_to_gcode.inx new file mode 100644 index 000000000..1be758370 --- /dev/null +++ b/share/extensions/gcodetools_path_to_gcode.inx @@ -0,0 +1,80 @@ + + + <_name>Path to Gcode + ru.cnc-club.filter.gcodetools_ptg + gcodetools.py + inkex.py + + + + 1 + 4 + <_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. + + + + + 1 + 0.0 + true + 0.05 + + + + output.ngc + true + + /home + + 5 + + mm + in + + + None + Parameterize Gcode + Flip y axis and parameterize Gcode + Round all values to 4 digits + + + + + false + + + + + + <_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 + + + + + + + + + + path + + + + diff --git a/share/extensions/gcodetools_tools_library.inx b/share/extensions/gcodetools_tools_library.inx new file mode 100644 index 000000000..4d15dc52c --- /dev/null +++ b/share/extensions/gcodetools_tools_library.inx @@ -0,0 +1,62 @@ + + + <_name>Tools library + ru.cnc-club.filter.gcodetools_tools_library_no_options_no_preferences + gcodetools.py + inkex.py + + + + + +<_option value='default tool'>default +<_option value='cylinder cutter'>cylinder +<_option value='cone cutter'>cone +<_option value='plasma cutter'>plasma +<_option value='tangent knife'>tangent knife +<_option value='lathe cutter'>lathe cutter + +<_option value='check'>Just check tools + + + + <_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 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 + + + + + + + + + + path + + + +