add back more extensions
This commit is contained in:
parent
196337a7bc
commit
bc2301079d
12
extensions/fablabchemnitz/generate_palette/.editorconfig
Normal file
12
extensions/fablabchemnitz/generate_palette/.editorconfig
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Generate Palette</name>
|
||||||
|
<id>fablabchemnitz.de.generate_palette</id>
|
||||||
|
<label>Select a set of objects and create a custom color palette.</label>
|
||||||
|
<label appearance="header">Palette Properties</label>
|
||||||
|
<label>Palette Name</label>
|
||||||
|
<param name="name" type="string" gui-text=" " />
|
||||||
|
<vbox>
|
||||||
|
<param name="property" type="optiongroup" appearance="combo" gui-text="Color Property">
|
||||||
|
<option value="fill">Fill Color</option>
|
||||||
|
<option value="stroke">Stroke Color</option>
|
||||||
|
<option value="both">Both</option>
|
||||||
|
</param>
|
||||||
|
</vbox>
|
||||||
|
<label appearance="header">Options</label>
|
||||||
|
<param name="default" type="bool" gui-text="Include default grays">false</param>
|
||||||
|
<param name="replace" type="bool" gui-text="Replace existing palette">false</param>
|
||||||
|
<param name="sort" type="optiongroup" appearance="combo" gui-text="Sort colors">
|
||||||
|
<option value="selection">Selection / Z-index</option>
|
||||||
|
<option value="hsl">By HSL values</option>
|
||||||
|
<option value="rgb">By RGB values</option>
|
||||||
|
<option value="x_location">X Location</option>
|
||||||
|
<option value="y_location">Y Location</option>
|
||||||
|
</param>
|
||||||
|
<spacer />
|
||||||
|
<hbox>
|
||||||
|
<image>info.svg</image>
|
||||||
|
<label>Don't forget to restart Inkscape</label>
|
||||||
|
</hbox>
|
||||||
|
<effect needs-live-preview="false">
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Colors/Gradients/Filters"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">generate_palette.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
180
extensions/fablabchemnitz/generate_palette/generate_palette.py
Normal file
180
extensions/fablabchemnitz/generate_palette/generate_palette.py
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import inkex
|
||||||
|
|
||||||
|
def log(text):
|
||||||
|
inkex.utils.debug(text)
|
||||||
|
|
||||||
|
|
||||||
|
def abort(text):
|
||||||
|
inkex.errormsg(_(text))
|
||||||
|
exit()
|
||||||
|
|
||||||
|
|
||||||
|
class GeneratePalette(inkex.EffectExtension):
|
||||||
|
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument('--name', help='Palette name')
|
||||||
|
pars.add_argument('--property', help='Color property')
|
||||||
|
pars.add_argument('--default', type=inkex.Boolean, help='Default grays')
|
||||||
|
pars.add_argument('--sort', help='Sort type')
|
||||||
|
pars.add_argument('--replace', type=inkex.Boolean, dest='replace', help='Replace existing')
|
||||||
|
|
||||||
|
|
||||||
|
def get_palettes_path(self):
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
path = os.path.join(os.environ['APPDATA'], 'inkscape', 'palettes')
|
||||||
|
else:
|
||||||
|
path = os.environ.get('XDG_CONFIG_HOME', '~/.config')
|
||||||
|
path = os.path.join(path, 'inkscape', 'palettes')
|
||||||
|
|
||||||
|
return os.path.expanduser(path)
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_path(self):
|
||||||
|
name = str(self.options.name).replace(' ', '-')
|
||||||
|
return "%s/%s.gpl" % (self.palettes_path, name)
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_colors(self):
|
||||||
|
colors = [
|
||||||
|
" 0 0 0 Black",
|
||||||
|
" 26 26 26 90% Gray",
|
||||||
|
" 51 51 51 80% Gray",
|
||||||
|
" 77 77 77 70% Gray",
|
||||||
|
"102 102 102 60% Gray",
|
||||||
|
"128 128 128 50% Gray",
|
||||||
|
"153 153 153 40% Gray",
|
||||||
|
"179 179 179 30% Gray",
|
||||||
|
"204 204 204 20% Gray",
|
||||||
|
"230 230 230 10% Gray",
|
||||||
|
"236 236 236 7.5% Gray",
|
||||||
|
"242 242 242 5% Gray",
|
||||||
|
"249 249 249 2.5% Gray",
|
||||||
|
"255 255 255 White"
|
||||||
|
]
|
||||||
|
|
||||||
|
return colors if self.options.default else []
|
||||||
|
|
||||||
|
|
||||||
|
def get_node_prop(self, node, property):
|
||||||
|
attr = node.attrib.get('style')
|
||||||
|
style = dict(inkex.Style.parse_str(attr))
|
||||||
|
|
||||||
|
return style.get(property, 'none')
|
||||||
|
|
||||||
|
|
||||||
|
def get_node_index(self, item):
|
||||||
|
node = item[1]
|
||||||
|
id = node.attrib.get('id')
|
||||||
|
|
||||||
|
return self.options.ids.index(id)
|
||||||
|
|
||||||
|
|
||||||
|
def get_node_x(self, item):
|
||||||
|
node = item[1]
|
||||||
|
return node.bounding_box().center_x
|
||||||
|
|
||||||
|
|
||||||
|
def get_node_y(self, item):
|
||||||
|
node = item[1]
|
||||||
|
return node.bounding_box().center_y
|
||||||
|
|
||||||
|
|
||||||
|
def get_formatted_color(self, color):
|
||||||
|
rgb = inkex.Color(color).to_rgb()
|
||||||
|
|
||||||
|
if self.options.sort == 'hsl':
|
||||||
|
key = inkex.Color(color).to_hsl()
|
||||||
|
key = "{:03d}{:03d}{:03d}".format(*key)
|
||||||
|
else:
|
||||||
|
key = "{:03d}{:03d}{:03d}".format(*rgb)
|
||||||
|
|
||||||
|
rgb = "{:3d} {:3d} {:3d}".format(*rgb)
|
||||||
|
color = str(color).upper()
|
||||||
|
name = str(inkex.Color(color).to_named()).upper()
|
||||||
|
|
||||||
|
if name != color:
|
||||||
|
name = "%s (%s)" % (name.capitalize(), color)
|
||||||
|
|
||||||
|
return "%s %s %s" % (key, rgb, name)
|
||||||
|
|
||||||
|
|
||||||
|
def get_selected_colors(self):
|
||||||
|
colors = []
|
||||||
|
selected = list(self.svg.selected.items())
|
||||||
|
|
||||||
|
if self.options.sort == 'y_location':
|
||||||
|
selected.sort(key=self.get_node_x)
|
||||||
|
selected.sort(key=self.get_node_y)
|
||||||
|
elif self.options.sort == 'x_location':
|
||||||
|
selected.sort(key=self.get_node_y)
|
||||||
|
selected.sort(key=self.get_node_x)
|
||||||
|
else:
|
||||||
|
selected.sort(key=self.get_node_index)
|
||||||
|
|
||||||
|
for id, node in selected:
|
||||||
|
if self.options.property in ['fill', 'both']:
|
||||||
|
fill = self.get_node_prop(node, 'fill')
|
||||||
|
|
||||||
|
if inkex.colors.is_color(fill):
|
||||||
|
if fill != 'none' and fill not in colors:
|
||||||
|
colors.append(fill)
|
||||||
|
|
||||||
|
if self.options.property in ['stroke', 'both']:
|
||||||
|
stroke = self.get_node_prop(node, 'stroke')
|
||||||
|
|
||||||
|
if inkex.colors.is_color(stroke):
|
||||||
|
if stroke != 'none' and stroke not in colors:
|
||||||
|
colors.append(stroke)
|
||||||
|
|
||||||
|
colors = list(map(self.get_formatted_color, colors))
|
||||||
|
|
||||||
|
if self.options.sort == 'hsl' or self.options.sort == 'rgb':
|
||||||
|
colors.sort()
|
||||||
|
|
||||||
|
return list(map(lambda x : x[11:], colors))
|
||||||
|
|
||||||
|
|
||||||
|
def write_palette(self):
|
||||||
|
file = open(self.file_path, 'w')
|
||||||
|
try:
|
||||||
|
file.write("GIMP Palette\n")
|
||||||
|
file.write("Name: %s\n" % self.options.name)
|
||||||
|
file.write("#\n# Generated with Inkscape Generate Palette\n#\n")
|
||||||
|
|
||||||
|
for color in self.default_colors:
|
||||||
|
file.write("%s\n" % color)
|
||||||
|
|
||||||
|
for color in self.selected_colors:
|
||||||
|
if color[:11] not in list(map(lambda x : x[:11], self.default_colors)):
|
||||||
|
file.write("%s\n" % color)
|
||||||
|
finally:
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
self.palettes_path = self.get_palettes_path()
|
||||||
|
self.file_path = self.get_file_path()
|
||||||
|
self.default_colors = self.get_default_colors()
|
||||||
|
self.selected_colors = self.get_selected_colors()
|
||||||
|
|
||||||
|
if not self.options.replace and os.path.exists(self.file_path):
|
||||||
|
abort('Palette already exists!')
|
||||||
|
|
||||||
|
if not self.options.name:
|
||||||
|
abort('Please enter a palette name.')
|
||||||
|
|
||||||
|
if len(self.options.ids) < 2:
|
||||||
|
abort('Please select at least 2 objects.')
|
||||||
|
|
||||||
|
if not len(self.selected_colors):
|
||||||
|
abort('No colors found in selected objects!')
|
||||||
|
|
||||||
|
self.write_palette()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
GeneratePalette().run()
|
1
extensions/fablabchemnitz/generate_palette/info.svg
Normal file
1
extensions/fablabchemnitz/generate_palette/info.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg"><path d="m6 1c2.757 0 5 2.243 5 5s-2.243 5-5 5-5-2.243-5-5 2.243-5 5-5zm0-1c-3.3135 0-6 2.6865-6 6s2.6865 6 6 6 6-2.6865 6-6-2.6865-6-6-6zm-.0005 2.875c.345 0 .6255.28.6255.625s-.2805.625-.6255.625-.6245-.28-.6245-.625.2795-.625.6245-.625zm1.0005 6.125h-2v-.5c.242-.0895.5-.1005.5-.3675v-2.2335c0-.267-.258-.309-.5-.3985v-.5h1.5v3.1325c0 .2675.2585.279.5.3675z"/></svg>
|
After Width: | Height: | Size: 452 B |
25
extensions/fablabchemnitz/generate_palette/meta.json
Normal file
25
extensions/fablabchemnitz/generate_palette/meta.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Generate Palette",
|
||||||
|
"id": "fablabchemnitz.de.generate_palette",
|
||||||
|
"path": "generate_palette",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Generate",
|
||||||
|
"original_id": "hardpixel.eu.generate_palette",
|
||||||
|
"license": "GNU GPL v3",
|
||||||
|
"license_url": "https://github.com/olibia/inkscape-generate-palette/blob/master/LICENSE",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/generate_palette",
|
||||||
|
"fork_url": "https://github.com/olibia/inkscape-generate-palette",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Generate+Palette",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/olibia",
|
||||||
|
"github.com/opsaaaaa",
|
||||||
|
"github.com/deslomator",
|
||||||
|
"github.com/dclemmon",
|
||||||
|
"github.com/speleo3",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
29
extensions/fablabchemnitz/glyph_ids/get_glyph_ids.inx
Normal file
29
extensions/fablabchemnitz/glyph_ids/get_glyph_ids.inx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Glyph IDs - Get</name>
|
||||||
|
<id>fablabchemnitz.de.glyph_ids.get_glyph_ids</id>
|
||||||
|
<param type="notebook" name="tab">
|
||||||
|
<page name="getGlyphIDs" gui-text="Glyph IDs - Get">
|
||||||
|
<label>Get all glyph ids (all path ids in layer with id = glyph) and combine to a string.</label>
|
||||||
|
<label>This string will be saved into a text element in a new layer 'glyphIds'.</label>
|
||||||
|
<label>Use this string when setting the ids (Glyph IDs - set) before generating your new font as the ids might get lost during path operations</label>
|
||||||
|
</page>
|
||||||
|
<page name="help" gui-text="Information">
|
||||||
|
<label appearance="header">For more information</label>
|
||||||
|
<label appearance="url">https://gitlab.com/EllenWasbo/inkscape-extension-getsetGlyphIDs</label>
|
||||||
|
<label>and</label>
|
||||||
|
<label appearance="url">http://cutlings.wasbo.net/inkscape-extension-automate-glyph-ids/</label>
|
||||||
|
</page>
|
||||||
|
</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Text" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">get_glyph_ids.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
66
extensions/fablabchemnitz/glyph_ids/get_glyph_ids.py
Normal file
66
extensions/fablabchemnitz/glyph_ids/get_glyph_ids.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Copyright (C) 2020 Ellen Wasboe, ellen@wasbo.net
|
||||||
|
#
|
||||||
|
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
"""
|
||||||
|
Get path ids of all selected paths should be of all paths in "Glyphs" layer
|
||||||
|
Put all ids into a continueous string (no separation character) and paste as text element in layer Ids at position x 0 y 0.
|
||||||
|
Paths are sorted by left bounding box.
|
||||||
|
Intention:
|
||||||
|
to quickly retrieve all path-ids of the glyph-paths when using the Custom Stroke Font extension to edit a existing svg font https://github.com/Shriinivas/inkscapestrokefont
|
||||||
|
this string of ids can then be used to set ids using setIds.py as ids might be lost in different path operations. https://gitlab.com/EllenWasbo/inkscape-extension-setIds
|
||||||
|
"""
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
from inkex import Group, TextElement
|
||||||
|
|
||||||
|
class getGlyphIDs(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--tab", default="getGlyphIDs")
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
|
||||||
|
if self.svg.getElementById('glyph') == None:
|
||||||
|
raise inkex.AbortExtension("Could not find layer Glyphs (id=glyphs)")
|
||||||
|
|
||||||
|
else:
|
||||||
|
txtElem=TextElement()
|
||||||
|
|
||||||
|
if self.svg.getElementById('glyphIds') == None:
|
||||||
|
txtLayer=self.svg.add(Group.new('glyphIds'))#, is_layer=True))
|
||||||
|
txtLayer.set('id','glyphIds')
|
||||||
|
txtLayer.set('inkscape:groupmode','layer')
|
||||||
|
txtLayer.style={'display':'inline'}
|
||||||
|
else:
|
||||||
|
txtLayer=self.svg.getElementById('glyphIds')
|
||||||
|
|
||||||
|
if self.svg.getElementById('txtGlyphIds') == None:
|
||||||
|
txt=txtLayer.add(txtElem)
|
||||||
|
txt.style={'font-size': '20px','letter-spacing': '2px','fill': '#000000','fill-opacity': 1,'stroke': 'none'}
|
||||||
|
txt.set('id','txtGlyphIds')
|
||||||
|
else:
|
||||||
|
txt=self.svg.getElementById('txtGlyphIds')
|
||||||
|
|
||||||
|
idArr=''
|
||||||
|
|
||||||
|
for elem in self.svg.getElementById('glyph'):
|
||||||
|
idArr=idArr+elem.get('id')
|
||||||
|
|
||||||
|
txt.text = idArr
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
getGlyphIDs().run()
|
21
extensions/fablabchemnitz/glyph_ids/meta.json
Normal file
21
extensions/fablabchemnitz/glyph_ids/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Glyph IDs - <various>",
|
||||||
|
"id": "fablabchemnitz.de.glyph_ids.<various>",
|
||||||
|
"path": "Glyph IDs - <various>",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "<various>",
|
||||||
|
"original_id": "EllenWasbo.cutlings.",
|
||||||
|
"license": "GNU GPL v2",
|
||||||
|
"license_url": "https://gitlab.com/EllenWasbo/inkscape-extension-getsetGlyphIDs/-/blob/master/getGlyphIDs.py",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/glyph_ids",
|
||||||
|
"fork_url": "https://gitlab.com/EllenWasbo/inkscape-extension-getsetGlyphIDs",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Glyph+IDs",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"gitlab.com/EllenWasbo",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
29
extensions/fablabchemnitz/glyph_ids/set_glyph_ids.inx
Normal file
29
extensions/fablabchemnitz/glyph_ids/set_glyph_ids.inx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Glyph IDs - Set</name>
|
||||||
|
<id>fablabchemnitz.de.glyph_ids.set_glyph_ids</id>
|
||||||
|
<param type="notebook" name="tab">
|
||||||
|
<page name="setGlyphIDs" gui-text="Glyph IDs - set">
|
||||||
|
<label>Id for each selected path will be set to one single character within the given string below.</label>
|
||||||
|
<label>The path ids are set ordered from left to right (bounding box).</label>
|
||||||
|
<param name="characters" type="string" gui-text="Characters:">abc</param>
|
||||||
|
</page>
|
||||||
|
<page name="help" gui-text="Information">
|
||||||
|
<label appearance="header">For more information</label>
|
||||||
|
<label appearance="url">https://gitlab.com/EllenWasbo/inkscape-extension-getsetGlyphIDs</label>
|
||||||
|
<label>and</label>
|
||||||
|
<label appearance="url">http://cutlings.wasbo.net/inkscape-extension-automate-glyph-ids/</label>
|
||||||
|
</page>
|
||||||
|
</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Text" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">set_glyph_ids.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
65
extensions/fablabchemnitz/glyph_ids/set_glyph_ids.py
Normal file
65
extensions/fablabchemnitz/glyph_ids/set_glyph_ids.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Copyright (C) 2020 Ellen Wasboe, ellen@wasbo.net
|
||||||
|
#
|
||||||
|
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
"""
|
||||||
|
Set ids of selected paths to a character in the specified string.
|
||||||
|
Paths a sorted by left bounding box. Id for the path to the left is set to the first character in the string.
|
||||||
|
Intention: to quickly set the correct id of the glyph-paths when using the Custom Stroke Font extension https://github.com/Shriinivas/inkscapestrokefont
|
||||||
|
"""
|
||||||
|
|
||||||
|
import inkex, re
|
||||||
|
|
||||||
|
class setGlyphIDs(inkex.EffectExtension):
|
||||||
|
"""Set ids of selected paths to a character in the specified string. """
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--tab", default="setGlyphIDs")
|
||||||
|
pars.add_argument("--characters", default="")
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
|
||||||
|
if not self.svg.selected:
|
||||||
|
raise inkex.AbortExtension("Please select the glyph paths.")
|
||||||
|
|
||||||
|
else:
|
||||||
|
if self.options.characters == "":
|
||||||
|
raise inkex.AbortExtension("No characters specified.")
|
||||||
|
else:
|
||||||
|
chars=self.options.characters
|
||||||
|
listChar=list(chars)
|
||||||
|
leftVal=[]
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
for id, elem in self.svg.selection.id_dict().items():
|
||||||
|
leftVal.append(elem.bounding_box().left)
|
||||||
|
elem.set('id','reset'+str(i))#reset all ids to prevent duplicate id problems
|
||||||
|
i+=1
|
||||||
|
|
||||||
|
leftVal.sort(key=float)
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
for id, elem in self.svg.selection.id_dict().items():
|
||||||
|
thisLeft=elem.bounding_box().left
|
||||||
|
charNo=leftVal.index(thisLeft)
|
||||||
|
|
||||||
|
if i < len(listChar):
|
||||||
|
elem.set('id',listChar[charNo])
|
||||||
|
i+=1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
setGlyphIDs().run()
|
60
extensions/fablabchemnitz/inkcut/inkcut.py
Normal file
60
extensions/fablabchemnitz/inkcut/inkcut.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Inkcut, Plot HPGL directly from Inkscape.
|
||||||
|
inkcut.py
|
||||||
|
|
||||||
|
Copyright 2018 The Inkcut Team
|
||||||
|
|
||||||
|
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 3 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., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
MA 02110-1301, USA.
|
||||||
|
|
||||||
|
#edit by Mario Voigt:
|
||||||
|
- latest version tested: Inkcut 2.1.5
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import inkex
|
||||||
|
from lxml import etree
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
import shutil
|
||||||
|
from shutil import copy2
|
||||||
|
|
||||||
|
def contains_text(nodes):
|
||||||
|
for node in nodes:
|
||||||
|
tag = node.tag[node.tag.rfind("}")+1:]
|
||||||
|
if tag == 'text':
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def convert_objects_to_paths(file, document):
|
||||||
|
tempfile = os.path.splitext(file)[0] + "-prepare.svg"
|
||||||
|
# tempfile is needed here only because we want to force the extension to be .svg
|
||||||
|
# so that we can open and close it silently
|
||||||
|
copy2(file, tempfile)
|
||||||
|
|
||||||
|
command = "inkscape " + tempfile + ' --actions="EditSelectAllInAllLayers;EditUnlinkClone;ObjectToPath;FileSave;FileQuit"'
|
||||||
|
|
||||||
|
if shutil.which('xvfb-run'):
|
||||||
|
command = 'xvfb-run -a ' + command
|
||||||
|
|
||||||
|
p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)
|
||||||
|
(out, err) = p.communicate()
|
||||||
|
|
||||||
|
if p.returncode != 0:
|
||||||
|
inkex.errormsg("Failed to convert objects to paths. Continued without converting.")
|
||||||
|
inkex.errormsg(out)
|
||||||
|
inkex.errormsg(err)
|
||||||
|
return document.getroot()
|
||||||
|
else:
|
||||||
|
return etree.parse(tempfile).getroot()
|
16
extensions/fablabchemnitz/inkcut/inkcut_cut.inx
Normal file
16
extensions/fablabchemnitz/inkcut/inkcut_cut.inx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Inkcut - Cut selection</name>
|
||||||
|
<id>fablabchemnitz.de.inkcut.inkcut_cut</id>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Import/Export/Transfer"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">inkcut_cut.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
84
extensions/fablabchemnitz/inkcut/inkcut_cut.py
Normal file
84
extensions/fablabchemnitz/inkcut/inkcut_cut.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Inkcut, Plot HPGL directly from Inkscape.
|
||||||
|
extension.py
|
||||||
|
|
||||||
|
Copyright 2010-2018 The Inkcut Team
|
||||||
|
|
||||||
|
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 3 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., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
MA 02110-1301, USA.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
import shutil
|
||||||
|
from lxml import etree
|
||||||
|
import subprocess
|
||||||
|
from inkcut import contains_text, convert_objects_to_paths
|
||||||
|
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
from subprocess import DEVNULL # py3k
|
||||||
|
except ImportError:
|
||||||
|
import os
|
||||||
|
DEVNULL = open(os.devnull, 'wb')
|
||||||
|
|
||||||
|
class InkscapeInkcutPlugin(inkex.Effect):
|
||||||
|
def effect(self):
|
||||||
|
""" Like cut but requires no selection and does no validation for
|
||||||
|
text nodes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
nodes = self.svg.selected
|
||||||
|
if not len(nodes):
|
||||||
|
inkex.errormsg("There were no paths were selected.")
|
||||||
|
return
|
||||||
|
|
||||||
|
document = self.document
|
||||||
|
if contains_text(self.svg.selected.values()):
|
||||||
|
document = convert_objects_to_paths(self.args[-1], self.document)
|
||||||
|
|
||||||
|
#: If running from source
|
||||||
|
if DEBUG:
|
||||||
|
python = '~/inkcut/venv/bin/python'
|
||||||
|
inkcut = '~/inkcut/main.py'
|
||||||
|
cmd = [python, inkcut]
|
||||||
|
else:
|
||||||
|
cmd = ['inkcut']
|
||||||
|
|
||||||
|
if shutil.which('inkcut') is None:
|
||||||
|
inkex.errormsg("Error: inkcut executable not found!")
|
||||||
|
return
|
||||||
|
|
||||||
|
cmd += ['open', '-',
|
||||||
|
'--nodes']+[str(k) for k in nodes.keys()]
|
||||||
|
p = subprocess.Popen(cmd,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=DEVNULL,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
close_fds=sys.platform != "win32")
|
||||||
|
p.stdin.write(etree.tostring(document))
|
||||||
|
p.stdin.close()
|
||||||
|
|
||||||
|
# Set the returncode to avoid this warning when popen is garbage collected:
|
||||||
|
# "ResourceWarning: subprocess XXX is still running".
|
||||||
|
# See https://bugs.python.org/issue38890 and
|
||||||
|
# https://bugs.python.org/issue26741.
|
||||||
|
p.returncode = 0
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
InkscapeInkcutPlugin().run()
|
16
extensions/fablabchemnitz/inkcut/inkcut_open.inx
Normal file
16
extensions/fablabchemnitz/inkcut/inkcut_open.inx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Inkcut - Open current document</name>
|
||||||
|
<id>fablabchemnitz.de.inkcut.inkcut_open</id>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Import/Export/Transfer"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">inkcut_open.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
75
extensions/fablabchemnitz/inkcut/inkcut_open.py
Normal file
75
extensions/fablabchemnitz/inkcut/inkcut_open.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Inkcut, Plot HPGL directly from Inkscape.
|
||||||
|
extension.py
|
||||||
|
|
||||||
|
Copyright 2010-2018 The Inkcut Team
|
||||||
|
|
||||||
|
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 3 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., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
MA 02110-1301, USA.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import inkex
|
||||||
|
import importlib
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
from lxml import etree
|
||||||
|
from inkcut import convert_objects_to_paths
|
||||||
|
|
||||||
|
DEBUG = False
|
||||||
|
try:
|
||||||
|
from subprocess import DEVNULL # py3k
|
||||||
|
except ImportError:
|
||||||
|
import os
|
||||||
|
DEVNULL = open(os.devnull, 'wb')
|
||||||
|
|
||||||
|
|
||||||
|
class InkscapeInkcutPlugin(inkex.Effect):
|
||||||
|
def effect(self):
|
||||||
|
""" Like cut but requires no selection and does no validation for
|
||||||
|
text nodes.
|
||||||
|
"""
|
||||||
|
#: If running from source
|
||||||
|
if DEBUG:
|
||||||
|
python = '~/inkcut/venv/bin/python'
|
||||||
|
inkcut = '~/inkcut/main.py'
|
||||||
|
cmd = [python, inkcut]
|
||||||
|
else:
|
||||||
|
cmd = ['inkcut']
|
||||||
|
|
||||||
|
if shutil.which('inkcut') is None:
|
||||||
|
inkex.errormsg("Error: inkcut executable not found!")
|
||||||
|
return
|
||||||
|
|
||||||
|
document = convert_objects_to_paths(self.options.input_file, self.document)
|
||||||
|
|
||||||
|
cmd += ['open', '-']
|
||||||
|
p = subprocess.Popen(cmd,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=DEVNULL,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
close_fds=sys.platform != "win32")
|
||||||
|
p.stdin.write(etree.tostring(document))
|
||||||
|
p.stdin.close()
|
||||||
|
# Set the returncode to avoid this warning when popen is garbage collected:
|
||||||
|
# "ResourceWarning: subprocess XXX is still running".
|
||||||
|
# See https://bugs.python.org/issue38890 and
|
||||||
|
# https://bugs.python.org/issue26741.
|
||||||
|
p.returncode = 0
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
InkscapeInkcutPlugin().run()
|
21
extensions/fablabchemnitz/inkcut/meta.json
Normal file
21
extensions/fablabchemnitz/inkcut/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Inkcut - <various>",
|
||||||
|
"id": "fablabchemnitz.de.inkcut.<various>",
|
||||||
|
"path": "inkcut",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "<various>",
|
||||||
|
"original_id": "org.ekips.filter.inkcut.<various>",
|
||||||
|
"license": "GNU GPL v3",
|
||||||
|
"license_url": "https://github.com/inkcut/inkcut/blob/master/LICENSE",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/inkcut",
|
||||||
|
"fork_url": "https://github.com/inkcut/inkcut/tree/master/plugins/inkscape",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Inkcut",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/frmdstryr",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Label Feature With Fill Color</name>
|
||||||
|
<id>fablabchemnitz.de.label_feature_with_fill_color</id>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Text"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">label_feature_with_fill_color.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
@ -0,0 +1,143 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
A inkscape plugin to label features with their fill colour
|
||||||
|
|
||||||
|
|
||||||
|
Copyright (C) 2019 Christoph Fink
|
||||||
|
|
||||||
|
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
from inkex.paths import CubicSuperPath, Path
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
class LabelFeatureWithFillColor(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
if len(self.svg.selected) > 0:
|
||||||
|
for id, node in self.svg.selected.items():
|
||||||
|
self.labelFeature(node)
|
||||||
|
|
||||||
|
def labelFeature(self, node):
|
||||||
|
style = node.get('style')
|
||||||
|
if style:
|
||||||
|
nodeStyle = dict(inkex.Style.parse_str(node.attrib["style"]))
|
||||||
|
nodeColour, labelColour = self.getNodeAndLabelColours(nodeStyle["fill"])
|
||||||
|
nodeX, nodeY, nodeWidth, nodeHeight = self.getNodeDimensions(node)
|
||||||
|
parent = node.getparent()
|
||||||
|
label = etree.SubElement(
|
||||||
|
parent,
|
||||||
|
inkex.addNS("text", "svg"),
|
||||||
|
{
|
||||||
|
"font-size": str(nodeHeight/4),
|
||||||
|
"x": str(nodeX + (nodeWidth/2)),
|
||||||
|
"y": str(nodeY + (nodeHeight/2)),
|
||||||
|
"dy": "0.5em",
|
||||||
|
"style": str(inkex.Style({
|
||||||
|
"fill": labelColour,
|
||||||
|
"stroke": "none",
|
||||||
|
"text-anchor": "middle"
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
labelTextSpan = etree.SubElement(
|
||||||
|
label,
|
||||||
|
inkex.addNS("tspan", "svg"),
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
labelTextSpan.text = nodeColour
|
||||||
|
|
||||||
|
|
||||||
|
def getNodeAndLabelColours(self, nodeStyleFill):
|
||||||
|
if nodeStyleFill[:5] == "url(#":
|
||||||
|
nodeFill = self.svg.getElementById(nodeStyleFill[5:-1])
|
||||||
|
if "Gradient" in nodeFill.tag:
|
||||||
|
nodeColour, labelColour = self.getNodeAndLabelColourForGradient(nodeFill)
|
||||||
|
else:
|
||||||
|
nodeColour = ""
|
||||||
|
labelColour = ""
|
||||||
|
|
||||||
|
else:
|
||||||
|
nodeColour = nodeStyleFill
|
||||||
|
labelColour = self.getLabelColour(nodeColour)
|
||||||
|
|
||||||
|
return (nodeColour, labelColour)
|
||||||
|
|
||||||
|
def getNodeAndLabelColourForGradient(self, gradientNode):
|
||||||
|
stops = self.getGradientStops(gradientNode)
|
||||||
|
|
||||||
|
nodeColours = []
|
||||||
|
|
||||||
|
for stop in stops:
|
||||||
|
offset = float(stop[0])
|
||||||
|
colour = stop[1]
|
||||||
|
nodeColours.append("{colour:s}{offset:s}".format(
|
||||||
|
colour=colour,
|
||||||
|
offset="" if offset in (0, 1) else " ({:0.2f})".format(offset)
|
||||||
|
))
|
||||||
|
nodeColour = u" ↔ ".join(nodeColours)
|
||||||
|
|
||||||
|
avgNodeColour = [sum([inkex.Color(stop[1]).to_rgb()[c] for stop in stops]) / len(stops) for c in range(3)]
|
||||||
|
|
||||||
|
labelColour = str(inkex.Color(avgNodeColour))
|
||||||
|
|
||||||
|
return (nodeColour, labelColour)
|
||||||
|
|
||||||
|
def getGradientStops(self, gradientNode):
|
||||||
|
while "{http://www.w3.org/1999/xlink}href" in gradientNode.attrib:
|
||||||
|
gradientNode = self.svg.getElementById(gradientNode.attrib["{http://www.w3.org/1999/xlink}href"][1:]) # noqa:E129
|
||||||
|
|
||||||
|
stops = []
|
||||||
|
|
||||||
|
for child in gradientNode:
|
||||||
|
if "stop" in child.tag:
|
||||||
|
stopStyle = dict(inkex.Style.parse_str(child.attrib["style"]))
|
||||||
|
stops.append((child.attrib["offset"], stopStyle["stop-color"]))
|
||||||
|
|
||||||
|
# if only opacity differs (colour == same), return one stop only:
|
||||||
|
if len(set([s[1] for s in stops])) == 1:
|
||||||
|
stops = [(0, stops[0][1])]
|
||||||
|
|
||||||
|
return stops
|
||||||
|
|
||||||
|
def getLabelColour(self, nodeColour):
|
||||||
|
labelColour = "#000000"
|
||||||
|
|
||||||
|
try:
|
||||||
|
nodeColour = inkex.Color(nodeColour).to_rgb()
|
||||||
|
if sum(nodeColour) / len(nodeColour) < 128:
|
||||||
|
labelColour = "#ffffff"
|
||||||
|
except (
|
||||||
|
TypeError,
|
||||||
|
ZeroDivisionError # if parseColor returns ""
|
||||||
|
):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return labelColour
|
||||||
|
|
||||||
|
def getNodeDimensions(self, node):
|
||||||
|
bbox = node.bounding_box()
|
||||||
|
nodeX = bbox.left
|
||||||
|
nodeY = bbox.top
|
||||||
|
nodeWidth = bbox.right - bbox.left
|
||||||
|
nodeHeight = bbox.bottom - bbox.top
|
||||||
|
|
||||||
|
return nodeX, nodeY, nodeWidth, nodeHeight
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
LabelFeatureWithFillColor().run()
|
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Label Feature With Fill Color",
|
||||||
|
"id": "fablabchemnitz.de.label_feature_with_fill_color",
|
||||||
|
"path": "label_feature_with_fill_color",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Label feature with fill color",
|
||||||
|
"original_id": "org.inkscape.labelColour",
|
||||||
|
"license": "GNU GPL v3",
|
||||||
|
"license_url": "https://gitlab.com/christoph.fink/inkscape-extension-colour-label/-/blob/master/LICENSE",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/label_feature_with_fill_color",
|
||||||
|
"fork_url": "https://gitlab.com/christoph.fink/inkscape-extension-colour-label",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Label+Feature+With+Fill+Color",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"gitlab.com/christoph.fink",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
21
extensions/fablabchemnitz/path_intersections/meta.json
Normal file
21
extensions/fablabchemnitz/path_intersections/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Path Intersections",
|
||||||
|
"id": "fablabchemnitz.de.path_intersections",
|
||||||
|
"path": "path_intersections",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "<unknown>",
|
||||||
|
"original_id": "<unknown>",
|
||||||
|
"license": "GNU GPL v2",
|
||||||
|
"license_url": "https://python.hotexamples.com/de/site/file?hash=0x3005162b28d022be32458df2259016982d4fcd5657f8339f79e63614a0b5494d&fullName=precut.py&project=starshipfactory/precut",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "",
|
||||||
|
"fork_url": "https://python.hotexamples.com/de/site/file?hash=0x3005162b28d022be32458df2259016982d4fcd5657f8339f79e63614a0b5494d&fullName=precut.py&project=starshipfactory/precut",
|
||||||
|
"documentation_url": "",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/starshipfactory",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Path Intersections</name>
|
||||||
|
<id>fablabchemnitz.de.path_intersections</id>
|
||||||
|
<label>This plugin - initially called "Precut" - was found deeply on web and was nearly lost in translation. Ported to Python 3.0 for InkScape 1.0. This tool finds path intersections within the complete SVG document. Intersections are going to be marked with little squares.</label>
|
||||||
|
<param name="color" type="color" appearance="colorbutton" gui-text="Error highlight color?">4012452351</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Paths - Cut/Intersect/Purge"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">path_intersections.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
@ -0,0 +1,423 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# This file is part of Precut.
|
||||||
|
#
|
||||||
|
# Precut 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.
|
||||||
|
#
|
||||||
|
# Precut 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 Precut. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# please, stick to pep8 formatting for this file
|
||||||
|
|
||||||
|
# seems to be lost in year 2016 https://wiki.inkscape.org/wiki/index.php?title=Inkscape_Extensions&oldid=99881
|
||||||
|
|
||||||
|
"""
|
||||||
|
Migrator: Mario Voigt / FabLab Chemnitz
|
||||||
|
Mail: mario.voigt@stadtfabrikanten.org
|
||||||
|
Date: 13.08.2020
|
||||||
|
|
||||||
|
This plugin - initially called "Precut" - was found deeply on web and was nearly lost in translation. Ported to Python 3.0 for InkScape 1.0. This tool finds path intersections within the complete SVG document. Intersections are going to be marked with little squares.
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
What do we want to check?
|
||||||
|
=========================
|
||||||
|
|
||||||
|
* any text objects that are not converted to a path?
|
||||||
|
* can be implemented as tag blacklist
|
||||||
|
* any outlines? they need to be converted to paths
|
||||||
|
* check for crossing paths
|
||||||
|
* this is the hardest
|
||||||
|
* for lines, this is easy, and can, for example, be done with shapely:
|
||||||
|
|
||||||
|
>>> from shapely.geometry import LineString
|
||||||
|
>>> l1 = LineString([(0, 0), (1, 1)])
|
||||||
|
>>> l2 = LineString([(0, 1), (1, 0)])
|
||||||
|
>>> l1.intersects(l2)
|
||||||
|
True
|
||||||
|
>>> p = l1.intersection(l2)
|
||||||
|
>>> p.x
|
||||||
|
0.5
|
||||||
|
>>> p.y
|
||||||
|
0.5
|
||||||
|
* check for self-intersection, too (=> line.is_simple)
|
||||||
|
* need to split each complex subpath into its segments
|
||||||
|
* then, when doing intersections, remove the `boundary`
|
||||||
|
from the intersection set, because two adjacent
|
||||||
|
segments from a subpath always intersect in their boundary
|
||||||
|
* handle the commands M, Z, L, C, Q, A (parsed via simplepath)
|
||||||
|
* M: moveto
|
||||||
|
* Z: closepath (straight closing line)
|
||||||
|
* L: lineto
|
||||||
|
* C: curveto (cubic bezier)
|
||||||
|
* Q: curveto (quadratic bezier)
|
||||||
|
* A: elliptical arc (circles, ellipsis)
|
||||||
|
* paths need to have a minimum distance to other paths
|
||||||
|
* if two paths are connected ("T-junction"), this junction needs to be
|
||||||
|
exempt from the distance check.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from lxml import etree
|
||||||
|
import inkex
|
||||||
|
from inkex import bezier
|
||||||
|
from inkex.paths import Path
|
||||||
|
from inkex import Color
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from shapely.geometry import LineString, MultiLineString, Point, MultiPoint, GeometryCollection
|
||||||
|
from shapely import speedups
|
||||||
|
|
||||||
|
if speedups.available:
|
||||||
|
speedups.enable()
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def take_N(seq, n):
|
||||||
|
"""
|
||||||
|
split ``seq`` into slices of length ``n``. the total
|
||||||
|
length of ``seq` must be a multiple of ``n``.
|
||||||
|
"""
|
||||||
|
if len(seq) % n != 0:
|
||||||
|
raise ValueError("len=%d, n=%d, (%s)" % (len(seq), n, seq))
|
||||||
|
sub = []
|
||||||
|
for elem in seq:
|
||||||
|
sub.append(elem)
|
||||||
|
if len(sub) == n:
|
||||||
|
yield sub
|
||||||
|
sub = []
|
||||||
|
|
||||||
|
|
||||||
|
def linear_interp(a, b, t):
|
||||||
|
"""
|
||||||
|
linearly interpolate between ``a`` and ``b``. ``t`` must be a
|
||||||
|
a float between 0 and 1.
|
||||||
|
"""
|
||||||
|
return (1 - t) * a + t * b
|
||||||
|
|
||||||
|
|
||||||
|
def sample(start, stop, num):
|
||||||
|
"""
|
||||||
|
interpolate between start and stop, and yield ``num`` samples
|
||||||
|
"""
|
||||||
|
if num == 0:
|
||||||
|
return
|
||||||
|
delta = 1.0 / num
|
||||||
|
t = 0
|
||||||
|
for i in range(num):
|
||||||
|
yield linear_interp(start, stop, t)
|
||||||
|
t += delta
|
||||||
|
|
||||||
|
|
||||||
|
class CheckerResult(object):
|
||||||
|
|
||||||
|
def __init__(self, msg, elem, extra=None, max_len=50):
|
||||||
|
self.msg = msg
|
||||||
|
self.elem = elem
|
||||||
|
self.extra = extra
|
||||||
|
self.max_len = max_len
|
||||||
|
|
||||||
|
def fmt(self, s):
|
||||||
|
s = ", ".join(["%s: %s" % (k, v) for k, v in s.items()])
|
||||||
|
if len(s) > self.max_len:
|
||||||
|
return s[:50] + u"…"
|
||||||
|
return s
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
msg, elem, extra = self.msg, self.elem, self.extra
|
||||||
|
if extra:
|
||||||
|
return "%s: %s (%s)" % (msg, elem.get("id"), self.fmt(extra))
|
||||||
|
else:
|
||||||
|
return "%s: %s" % (msg, elem.get("id"))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<CheckerResult: %s>" % self.msg
|
||||||
|
|
||||||
|
|
||||||
|
class Checker(object):
|
||||||
|
def __call__(self, elem):
|
||||||
|
"""
|
||||||
|
run a check on ``elem`` and yield (elem, message) tuples
|
||||||
|
for each failed check
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("please implement __call__")
|
||||||
|
|
||||||
|
def collect(self):
|
||||||
|
"""
|
||||||
|
run a second stage check on aggregated data
|
||||||
|
"""
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
class StyleChecker(Checker):
|
||||||
|
def __call__(self, elem):
|
||||||
|
style = elem.get("style")
|
||||||
|
if style is None:
|
||||||
|
return
|
||||||
|
parsed = dict(inkex.Style.parse_str(style))
|
||||||
|
if "stroke" in parsed and parsed["stroke"] != "none":
|
||||||
|
yield CheckerResult("element with stroke found", elem)
|
||||||
|
|
||||||
|
|
||||||
|
class ElemBlacklistChecker(Checker):
|
||||||
|
blacklist = ["text"]
|
||||||
|
|
||||||
|
def __call__(self, elem):
|
||||||
|
_, tag = elem.tag.rsplit("}", 1)
|
||||||
|
if tag in self.blacklist:
|
||||||
|
yield CheckerResult("'%s' element found in document" % tag, elem)
|
||||||
|
|
||||||
|
|
||||||
|
class Subpath(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.points = []
|
||||||
|
self.cursor = None
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.points)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_point(self):
|
||||||
|
if self.points:
|
||||||
|
return self.points[-1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def first_point(self):
|
||||||
|
if self.points:
|
||||||
|
return self.points[0]
|
||||||
|
|
||||||
|
def moveto(self, point):
|
||||||
|
assert len(self) == 0, "moveto may only be called at the beginning of a subpath"
|
||||||
|
self.points.append(point)
|
||||||
|
self.cursor = point
|
||||||
|
|
||||||
|
def lineto(self, point):
|
||||||
|
self.points.append(point)
|
||||||
|
self.cursor = point
|
||||||
|
|
||||||
|
def curveto(self, points):
|
||||||
|
for p in self.approx_curve([self.cursor] + points):
|
||||||
|
self.lineto(p)
|
||||||
|
|
||||||
|
def closepath(self):
|
||||||
|
self.lineto(self.first_point)
|
||||||
|
|
||||||
|
def add_points(self, points):
|
||||||
|
self.points.extend(points)
|
||||||
|
self.cursor = points[-1]
|
||||||
|
|
||||||
|
def as_linestring(self):
|
||||||
|
return LineString(self.points)
|
||||||
|
|
||||||
|
def approx_curve(self, points):
|
||||||
|
for four in take_N(points, 4):
|
||||||
|
# TODO: automatically set number of samples depending on length
|
||||||
|
for t in sample(0, 1, 50):
|
||||||
|
yield bezier.bezierpointatt(four, t)
|
||||||
|
|
||||||
|
|
||||||
|
class IntersectionChecker(Checker):
|
||||||
|
def __init__(self):
|
||||||
|
self.paths = []
|
||||||
|
|
||||||
|
def __call__(self, elem):
|
||||||
|
# logger.debug(elem.attrib)
|
||||||
|
path = elem.get("d")
|
||||||
|
if path is None:
|
||||||
|
return []
|
||||||
|
parsed = Path(path).to_arrays()
|
||||||
|
self.paths.append((parsed, elem))
|
||||||
|
# logger.debug(parsed)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def fixVHbehaviour(self, elem):
|
||||||
|
raw = Path(elem.get("d")).to_arrays()
|
||||||
|
subpaths, prev = [], 0
|
||||||
|
for i in range(len(raw)): # Breaks compound paths into simple paths
|
||||||
|
if raw[i][0] == 'M' and i != 0:
|
||||||
|
subpaths.append(raw[prev:i])
|
||||||
|
prev = i
|
||||||
|
subpaths.append(raw[prev:])
|
||||||
|
seg = []
|
||||||
|
for simpath in subpaths:
|
||||||
|
if simpath[-1][0] == 'Z':
|
||||||
|
simpath[-1][0] = 'L'
|
||||||
|
if simpath[-2][0] == 'L': simpath[-1][1] = simpath[0][1]
|
||||||
|
else: simpath.pop()
|
||||||
|
for i in range(len(simpath)):
|
||||||
|
if simpath[i][0] == 'V': # vertical and horizontal lines only have one point in args, but 2 are required
|
||||||
|
#inkex.utils.debug(simpath[i][0])
|
||||||
|
simpath[i][0]='L' #overwrite V with regular L command
|
||||||
|
add=simpath[i-1][1][0] #read the X value from previous segment
|
||||||
|
simpath[i][1].append(simpath[i][1][0]) #add the second (missing) argument by taking argument from previous segment
|
||||||
|
simpath[i][1][0]=add #replace with recent X after Y was appended
|
||||||
|
if simpath[i][0] == 'H': # vertical and horizontal lines only have one point in args, but 2 are required
|
||||||
|
#inkex.utils.debug(simpath[i][0])
|
||||||
|
simpath[i][0]='L' #overwrite H with regular L command
|
||||||
|
simpath[i][1].append(simpath[i-1][1][1]) #add the second (missing) argument by taking argument from previous segment
|
||||||
|
#inkex.utils.debug(simpath[i])
|
||||||
|
seg.append(simpath[i])
|
||||||
|
elem.set("d", Path(seg))
|
||||||
|
return seg
|
||||||
|
|
||||||
|
def get_line_strings(self):
|
||||||
|
# logger.debug("paths: %s", self.paths)
|
||||||
|
for path, elem in self.paths:
|
||||||
|
path = self.fixVHbehaviour(elem)
|
||||||
|
logger.debug("new path, %s", elem.get("id"))
|
||||||
|
current_subpath = Subpath()
|
||||||
|
for cmd, coords in path:
|
||||||
|
logger.debug(" new command: %s", cmd)
|
||||||
|
if cmd != "A":
|
||||||
|
points = list(take_N(coords, n=2))
|
||||||
|
else:
|
||||||
|
points = list(take_N(coords, n=7))
|
||||||
|
logger.debug(" points: %s", points)
|
||||||
|
if cmd == "M":
|
||||||
|
# M starts a new subpath
|
||||||
|
if len(current_subpath) > 1:
|
||||||
|
yield current_subpath, elem
|
||||||
|
current_subpath = Subpath()
|
||||||
|
current_subpath.moveto(points[0])
|
||||||
|
# more than one point means the rest of the points are to
|
||||||
|
# be treated as if cmd was L:
|
||||||
|
# http://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands
|
||||||
|
if len(points) > 1:
|
||||||
|
points = points[1:]
|
||||||
|
cmd = "L"
|
||||||
|
if cmd == "L":
|
||||||
|
current_subpath.add_points(points)
|
||||||
|
if cmd == "Z":
|
||||||
|
current_subpath.closepath()
|
||||||
|
if cmd == "C":
|
||||||
|
current_subpath.curveto(points)
|
||||||
|
if cmd == "Q":
|
||||||
|
logger.warning("quadratic beziers are not supported yet")
|
||||||
|
# current_subpath.moveto(points[-1])
|
||||||
|
if cmd == "A":
|
||||||
|
logger.warning("elliptic arcs are not supported yet")
|
||||||
|
if len(current_subpath) > 1:
|
||||||
|
yield current_subpath, elem
|
||||||
|
current_subpath = Subpath()
|
||||||
|
|
||||||
|
def collect(self):
|
||||||
|
return self.check_intersections()
|
||||||
|
|
||||||
|
def check_intersections(self):
|
||||||
|
checks_done = MultiLineString()
|
||||||
|
for subpath, elem in self.get_line_strings():
|
||||||
|
line = subpath.as_linestring()
|
||||||
|
if not line.is_simple:
|
||||||
|
# TODO: find location of self-intersection and introduce some
|
||||||
|
# tolerance
|
||||||
|
# checks_done = checks_done.union(line)
|
||||||
|
yield CheckerResult("self-intersection found", elem)
|
||||||
|
# continue
|
||||||
|
if checks_done.intersects(line):
|
||||||
|
intersection = checks_done.intersection(line)
|
||||||
|
yield CheckerResult("intersection found", elem, extra={"intersection": intersection})
|
||||||
|
checks_done = checks_done.union(line)
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorVisualization(object):
|
||||||
|
def __init__(self, svg, effect, color, group_id="precut_errors"):
|
||||||
|
self.svg = svg
|
||||||
|
self.color = color
|
||||||
|
|
||||||
|
g = svg.find(".//%s[@id='%s']" % (inkex.addNS("g", "svg"), group_id))
|
||||||
|
if g is not None:
|
||||||
|
self.g = g
|
||||||
|
else:
|
||||||
|
parent = svg
|
||||||
|
attrs = {"id": group_id, "style": "opacity:.5", inkex.addNS("label", "inkscape"): "Precut Errors"}
|
||||||
|
self.g = etree.SubElement(parent, inkex.addNS("g", "svg"), attrs)
|
||||||
|
|
||||||
|
def fmt_point(self, point):
|
||||||
|
return "%s %s" % point
|
||||||
|
|
||||||
|
def convert(self, geom):
|
||||||
|
"""
|
||||||
|
convert a shapely geometry to SVG
|
||||||
|
"""
|
||||||
|
|
||||||
|
def vis_line_string(geom):
|
||||||
|
path = []
|
||||||
|
point_iter = iter(geom.coords)
|
||||||
|
head = next(point_iter)
|
||||||
|
tail = list(point_iter)
|
||||||
|
path.append("M%s" % self.fmt_point(head))
|
||||||
|
for point in tail:
|
||||||
|
path.append("L%s" % self.fmt_point(point))
|
||||||
|
attrs = {"d": " ".join(path), "style": "stroke:%s;stroke-width:5px;" % self.color}
|
||||||
|
etree.SubElement(self.g, inkex.addNS("path", "svg"), attrs)
|
||||||
|
|
||||||
|
def vis_point(geom):
|
||||||
|
x, y = geom.x, geom.y
|
||||||
|
x1, y1 = x - 5, y - 5
|
||||||
|
x2, y2 = x - 5, y + 5
|
||||||
|
x3, y3 = x + 5, y + 5
|
||||||
|
x4, y4 = x + 5, y - 5
|
||||||
|
vis_line_string(LineString([(x1, y1), (x2, y2), (x3, y3), (x4, y4), (x1, y1)]))
|
||||||
|
|
||||||
|
def vis_geom_collection(geom):
|
||||||
|
for g in geom.geoms:
|
||||||
|
self.convert(g)
|
||||||
|
|
||||||
|
converters = {
|
||||||
|
LineString: vis_line_string,
|
||||||
|
Point: vis_point,
|
||||||
|
MultiLineString: vis_geom_collection,
|
||||||
|
MultiPoint: vis_geom_collection,
|
||||||
|
GeometryCollection: vis_geom_collection,
|
||||||
|
}
|
||||||
|
converters[geom.__class__](geom)
|
||||||
|
|
||||||
|
def add_error(self, geom):
|
||||||
|
self.convert(geom)
|
||||||
|
|
||||||
|
|
||||||
|
class PathIntersections(inkex.Effect):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.check_result = []
|
||||||
|
self.checkers = [ElemBlacklistChecker(), StyleChecker(), IntersectionChecker()]
|
||||||
|
inkex.Effect.__init__(self, *args, **kwargs)
|
||||||
|
self.arg_parser.add_argument("--color", type=Color, default='4012452351', help="Error highlight color")
|
||||||
|
|
||||||
|
def walk(self, elem):
|
||||||
|
if elem.get("id") == "precut_errors":
|
||||||
|
return
|
||||||
|
for child in elem.iterchildren():
|
||||||
|
self.visit(child)
|
||||||
|
self.walk(child)
|
||||||
|
|
||||||
|
def visit(self, elem):
|
||||||
|
logger.debug("visiting %s", elem)
|
||||||
|
for checker in self.checkers:
|
||||||
|
self.check_result.extend(checker(elem))
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
svg = self.document.getroot()
|
||||||
|
self.walk(svg)
|
||||||
|
vis = ErrorVisualization(svg, self, color=self.options.color)
|
||||||
|
# additional "collect" pass for "global" analysis
|
||||||
|
for checker in self.checkers:
|
||||||
|
self.check_result.extend(checker.collect())
|
||||||
|
for res in self.check_result:
|
||||||
|
#print >>sys.stderr, unicode(res).encode("utf8")
|
||||||
|
#print(sys.stderr, str(res.encode("utf8")))
|
||||||
|
if res.extra and "intersection" in res.extra:
|
||||||
|
# TODO: add visualization for other kinds of errors
|
||||||
|
vis.add_error(res.extra["intersection"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
logging.basicConfig(stream=sys.stderr, level=logging.WARNING, format="%(levelname)s %(message)s")
|
||||||
|
PathIntersections().run()
|
21
extensions/fablabchemnitz/plycutter/meta.json
Normal file
21
extensions/fablabchemnitz/plycutter/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "PlyCutter",
|
||||||
|
"id": "fablabchemnitz.de.plycutter",
|
||||||
|
"path": "plycutter",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "PlyCutter",
|
||||||
|
"original_id": "fablabchemnitz.de.plycutter",
|
||||||
|
"license": "GNU AGPL v3",
|
||||||
|
"license_url": "https://github.com/tjltjl/plycutter/blob/master/LICENSE-agpl-3.0.txt",
|
||||||
|
"comment": "Written by Mario Voigt",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/plycutter",
|
||||||
|
"fork_url": null,
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/PlyCutter",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/tjltjl",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
78
extensions/fablabchemnitz/plycutter/plycutter.inx
Normal file
78
extensions/fablabchemnitz/plycutter/plycutter.inx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>PlyCutter</name>
|
||||||
|
<id>fablabchemnitz.de.plycutter</id>
|
||||||
|
<param name="tab" type="notebook">
|
||||||
|
<page name="tab_settings" gui-text="PlyCutter">
|
||||||
|
<label appearance="header">Import Settings</label>
|
||||||
|
<param name="debug" type="bool" gui-text="Turn on debugging">false</param>
|
||||||
|
<param name="thickness" type="float" min="0.001" max="99999.000" precision="3" gui-text="Thickness of sheets to find">6.000</param>
|
||||||
|
<param name="min_finger_width" type="float" min="0.001" max="99999.000" precision="3" gui-text="Minimum fingers width">3.000</param>
|
||||||
|
<param name="max_finger_width" type="float" min="0.001" max="99999.000" precision="3" gui-text="Maximum fingers width">5.000</param>
|
||||||
|
<param name="support_radius" type="float" min="0.001" max="99999.000" precision="3" gui-text="Support radius" gui-description="Set maximum range for generating material on a sheet where neither surface is visible">12.000</param>
|
||||||
|
<param name="final_dilation" type="float" min="0.001" max="99999.000" precision="3" gui-text="Final dilation" gui-description="Laser cutter kerf compensation">0.05</param>
|
||||||
|
<param name="random_seed" type="int" min="0" max="999999999" gui-text="Random seed" gui-description="For pseudo-random heuristics">42</param>
|
||||||
|
<separator/>
|
||||||
|
<label appearance="header">General</label>
|
||||||
|
<param name="resizetoimport" type="bool" gui-text="Resize the canvas to the imported drawing's bounding box">true</param>
|
||||||
|
<param name="extraborder" type="float" precision="3" gui-text="Add extra border around fitted canvas">0.0</param>
|
||||||
|
<param name="extraborder_units" type="optiongroup" appearance="combo" gui-text="Border offset units">
|
||||||
|
<option value="mm">mm</option>
|
||||||
|
<option value="cm">cm</option>
|
||||||
|
<option value="in">in</option>
|
||||||
|
<option value="pt">pt</option>
|
||||||
|
<option value="px">px</option>
|
||||||
|
</param>
|
||||||
|
<separator/>
|
||||||
|
<label appearance="header">*.stl Input File</label>
|
||||||
|
<param name="infile" type="path" gui-text=" " gui-description="The model file" filetypes="stl" mode="file">/your/stl/file</param>
|
||||||
|
<param name="import_units" type="optiongroup" appearance="combo" gui-text="Import file units">
|
||||||
|
<option value="mm">mm</option>
|
||||||
|
<option value="cm">cm</option>
|
||||||
|
<option value="in">in</option>
|
||||||
|
<option value="pt">pt</option>
|
||||||
|
<option value="px">px</option>
|
||||||
|
</param>
|
||||||
|
</page>
|
||||||
|
<page name="tab_about" gui-text="About">
|
||||||
|
<label appearance="header">Plycutter</label>
|
||||||
|
<label>A wrapper for Plycutter, utilizing kabeja to convert the DXF output to SVG. To make it work you need to install at least java and the plycutter python module from github.</label>
|
||||||
|
<label>2021 - 2022 / written by Mario Voigt (Stadtfabrikanten e.V. / FabLab Chemnitz)</label>
|
||||||
|
<spacer/>
|
||||||
|
<label appearance="header">Online Documentation</label>
|
||||||
|
<label appearance="url">https://y.stadtfabrikanten.org/plycutter</label>
|
||||||
|
<spacer/>
|
||||||
|
<label appearance="header">Contributing</label>
|
||||||
|
<label appearance="url">https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X</label>
|
||||||
|
<label appearance="url">mailto:mario.voigt@stadtfabrikanten.org</label>
|
||||||
|
<spacer/>
|
||||||
|
<label appearance="header">Third Party Modules</label>
|
||||||
|
<label appearance="url">https://github.com/tjltjl/plycutter</label>
|
||||||
|
<spacer/>
|
||||||
|
<label appearance="header">MightyScape Extension Collection</label>
|
||||||
|
<label>This piece of software is part of the MightyScape for Inkscape Extension Collection and is licensed under GNU GPL v3</label>
|
||||||
|
<label appearance="url">https://y.stadtfabrikanten.org/mightyscape-overview</label>
|
||||||
|
</page>
|
||||||
|
<page name="tab_donate" gui-text="Donate">
|
||||||
|
<label appearance="header">Coffee + Pizza</label>
|
||||||
|
<label>We are the Stadtfabrikanten, running the FabLab Chemnitz since 2016. A FabLab is an open workshop that gives people access to machines and digital tools like 3D printers, laser cutters and CNC milling machines.</label>
|
||||||
|
<spacer/>
|
||||||
|
<label>You like our work and want to support us? You can donate to our non-profit organization by different ways:</label>
|
||||||
|
<label appearance="url">https://y.stadtfabrikanten.org/donate</label>
|
||||||
|
<spacer/>
|
||||||
|
<label>Thanks for using our extension and helping us!</label>
|
||||||
|
<image>../000_about_fablabchemnitz.svg</image>
|
||||||
|
</page>
|
||||||
|
</param>
|
||||||
|
<effect needs-live-preview="true">
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz Boxes/Papercraft">
|
||||||
|
<submenu name="Finger-jointed/Tabbed Boxes" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">plycutter.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
127
extensions/fablabchemnitz/plycutter/plycutter.py
Normal file
127
extensions/fablabchemnitz/plycutter/plycutter.py
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import inkex
|
||||||
|
import tempfile
|
||||||
|
import subprocess
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
from lxml import etree
|
||||||
|
from inkex import Transform
|
||||||
|
|
||||||
|
class PlyCutter(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--tab")
|
||||||
|
pars.add_argument("--infile")
|
||||||
|
pars.add_argument("--import_units", default="mm")
|
||||||
|
pars.add_argument("--resizetoimport", type=inkex.Boolean, default=True, help="Resize the canvas to the imported drawing's bounding box")
|
||||||
|
pars.add_argument("--extraborder", type=float, default=0.0)
|
||||||
|
pars.add_argument("--extraborder_units", default="mm")
|
||||||
|
|
||||||
|
pars.add_argument("--thickness", type=float, default=6.000, help="Set the thickness of sheets to find.")
|
||||||
|
pars.add_argument("--debug", type=inkex.Boolean, default=False, help="Turn on debugging")
|
||||||
|
pars.add_argument("--min_finger_width", type=float, default=3.000, help="Set minimum width for generated fingers.")
|
||||||
|
pars.add_argument("--max_finger_width", type=float, default=5.000, help="Set maximum width for generated fingers.")
|
||||||
|
pars.add_argument("--support_radius", type=float, default=12.000, help="Set maximum range for generating material on a sheet where neither surface is visible")
|
||||||
|
pars.add_argument("--final_dilation", default=0.05, type=float, help="Final dilation (laser cutter kerf compensation)")
|
||||||
|
pars.add_argument("--random_seed", type=int, default=42, help="Random seed for pseudo-random heuristics")
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
stl_input = self.options.infile
|
||||||
|
if not os.path.exists(stl_input):
|
||||||
|
inkex.utils.debug("The input file does not exist. Please select a proper file and try again.")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# Prepare output
|
||||||
|
basename = os.path.splitext(os.path.basename(stl_input))[0]
|
||||||
|
svg_output = os.path.join(tempfile.gettempdir(), basename + ".svg")
|
||||||
|
|
||||||
|
# Clean up possibly previously generated output file from plycutter
|
||||||
|
if os.path.exists(svg_output):
|
||||||
|
try:
|
||||||
|
os.remove(svg_output)
|
||||||
|
except OSError as e:
|
||||||
|
inkex.utils.debug("Error while deleting previously generated output file " + stl_input)
|
||||||
|
|
||||||
|
# Run PlyCutter
|
||||||
|
plycutter_cmd = "plycutter "
|
||||||
|
plycutter_cmd += "--thickness " + str(self.options.thickness) + " "
|
||||||
|
if self.options.debug == True: plycutter_cmd += "--debug "
|
||||||
|
plycutter_cmd += "--min_finger_width " + str(self.options.min_finger_width) + " "
|
||||||
|
plycutter_cmd += "--max_finger_width " + str(self.options.max_finger_width) + " "
|
||||||
|
plycutter_cmd += "--support_radius " + str(self.options.support_radius) + " "
|
||||||
|
plycutter_cmd += "--final_dilation " + str(self.options.final_dilation) + " "
|
||||||
|
plycutter_cmd += "--random_seed " + str(self.options.random_seed) + " "
|
||||||
|
plycutter_cmd += "--format svg " #static
|
||||||
|
plycutter_cmd += "-o \"" + svg_output + "\" "
|
||||||
|
plycutter_cmd += "\"" + stl_input + "\""
|
||||||
|
|
||||||
|
#print command
|
||||||
|
#inkex.utils.debug(plycutter_cmd)
|
||||||
|
|
||||||
|
#create a new env for subprocess which does not contain extensions dir because there's a collision with "rtree.py"
|
||||||
|
pypath = ''
|
||||||
|
for d in sys.path:
|
||||||
|
if d != '/usr/share/inkscape/extensions':
|
||||||
|
pypath = pypath + d + ';'
|
||||||
|
neutral_env = os.environ.copy()
|
||||||
|
neutral_env['PYTHONPATH'] = pypath
|
||||||
|
|
||||||
|
p = Popen(plycutter_cmd, shell=True, stdout=PIPE, stderr=PIPE, env=neutral_env)
|
||||||
|
stdout, stderr = p.communicate()
|
||||||
|
|
||||||
|
p.wait()
|
||||||
|
if p.returncode != 0:
|
||||||
|
inkex.utils.debug("PlyCutter failed: %d %s %s" % (p.returncode,
|
||||||
|
str(stdout.decode('UTF-8')).replace('\\n', '\n').replace('\\t', '\t'),
|
||||||
|
str(stderr.decode('UTF-8')).replace('\\n', '\n').replace('\\t', '\t'))
|
||||||
|
)
|
||||||
|
exit(1)
|
||||||
|
elif self.options.debug is True:
|
||||||
|
inkex.utils.debug("PlyCutter debug output: %d %s %s" % (p.returncode,
|
||||||
|
str(stdout.decode('UTF-8')).replace('\\n', '\n').replace('\\t', '\t'),
|
||||||
|
str(stderr.decode('UTF-8')).replace('\\n', '\n').replace('\\t', '\t'))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Write the generated SVG into InkScape's canvas
|
||||||
|
try:
|
||||||
|
stream = open(svg_output, 'r')
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
inkex.utils.debug("There was no SVG output generated by PlyCutter. Please check your model file.")
|
||||||
|
exit(1)
|
||||||
|
p = etree.XMLParser(huge_tree=True)
|
||||||
|
doc = etree.parse(stream, parser=etree.XMLParser(huge_tree=True)).getroot()
|
||||||
|
stream.close()
|
||||||
|
|
||||||
|
scale = self.svg.uutounit("1" + self.options.import_units)
|
||||||
|
|
||||||
|
g = inkex.Group(id=self.svg.get_unique_id("plycutter-"))
|
||||||
|
g.insert(0, inkex.Desc("Imported file: {}".format(self.options.infile)))
|
||||||
|
self.svg.get_current_layer().add(g)
|
||||||
|
for element in doc.iter("{http://www.w3.org/2000/svg}path"):
|
||||||
|
g.append(element)
|
||||||
|
if element.tag == inkex.addNS('path', 'svg'):
|
||||||
|
element.set('style', 'fill:none;stroke:#000000;stroke-width:{}px;stroke-opacity:1'.format(1/scale))
|
||||||
|
|
||||||
|
g.transform = 'scale({},{})'.format(scale, scale)
|
||||||
|
|
||||||
|
#Adjust viewport and width/height to have the import at the center of the canvas
|
||||||
|
if self.options.resizetoimport:
|
||||||
|
#push some calculation of all bounding boxes. seems to refresh something in the background which makes the bbox calculation working at the bottom
|
||||||
|
for element in self.document.getroot().iter("*"):
|
||||||
|
try:
|
||||||
|
element.bounding_box()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
bbox = g.bounding_box() #only works because we process bounding boxes previously. see top
|
||||||
|
if bbox is not None:
|
||||||
|
root = self.svg.getElement('//svg:svg');
|
||||||
|
offset = self.svg.unittouu(str(self.options.extraborder) + self.options.extraborder_units)
|
||||||
|
root.set('viewBox', '%f %f %f %f' % (bbox.left - offset, bbox.top - offset, bbox.width + 2 * offset, bbox.height + 2 * offset))
|
||||||
|
root.set('width', bbox.width + 2 * offset)
|
||||||
|
root.set('height', bbox.height + 2 * offset)
|
||||||
|
else:
|
||||||
|
self.msg("Error resizing to bounding box.")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
PlyCutter().run()
|
21
extensions/fablabchemnitz/rotations/meta.json
Normal file
21
extensions/fablabchemnitz/rotations/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Rotations - <various>",
|
||||||
|
"id": "fablabchemnitz.de.rotations_<various>",
|
||||||
|
"path": "rotations",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Rotate for <various>",
|
||||||
|
"original_id": "org.bg.filter.rotate<various>",
|
||||||
|
"license": "GNU GPL v2",
|
||||||
|
"license_url": "https://github.com/hobzcalvin/LaserPrep/*.py",
|
||||||
|
"comment": "ported to Inkscape v1 by Mario Voigt",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/rotations",
|
||||||
|
"fork_url": "https://github.com/hobzcalvin/LaserPrep",
|
||||||
|
"documentation_url": "",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/hobzcalvin",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
57
extensions/fablabchemnitz/rotations/rotate_helper.py
Normal file
57
extensions/fablabchemnitz/rotations/rotate_helper.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#! /usr/bin/env python3
|
||||||
|
'''
|
||||||
|
Copyright (C) 2019 Grant Patterson <grant@revoltlabs.co>
|
||||||
|
|
||||||
|
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
|
||||||
|
'''
|
||||||
|
|
||||||
|
from math import copysign, cos, pi, sin
|
||||||
|
from inkex import Transform
|
||||||
|
|
||||||
|
def rotate_matrix(node, a):
|
||||||
|
bbox = node.bounding_box()
|
||||||
|
cx = bbox.center_x
|
||||||
|
cy = bbox.center_y
|
||||||
|
return Transform([[cos(a), -sin(a), cx], [sin(a), cos(a), cy]]) @ Transform([[1, 0, -cx], [0, 1, -cy]])
|
||||||
|
|
||||||
|
def optimal_rotations(node, precision):
|
||||||
|
step = pi / float(precision)
|
||||||
|
bbox = node.bounding_box()
|
||||||
|
min_width = bbox.right - bbox.left
|
||||||
|
min_width_angle = None
|
||||||
|
min_bbox_area = min_width * (bbox.bottom - bbox.top)
|
||||||
|
min_bbox_area_angle = None
|
||||||
|
|
||||||
|
for i in range(precision):
|
||||||
|
angle = -pi/2.0 + i*step
|
||||||
|
rotated = node.bounding_box(rotate_matrix(node, angle))
|
||||||
|
|
||||||
|
width = rotated.width
|
||||||
|
height = rotated.height
|
||||||
|
bbox_area = width * height
|
||||||
|
|
||||||
|
if width < min_width:
|
||||||
|
min_width = width
|
||||||
|
min_width_angle = angle
|
||||||
|
if bbox_area < min_bbox_area:
|
||||||
|
if width > height:
|
||||||
|
# To keep results similar to min_width_angle, rotate by an
|
||||||
|
# additional 90 degrees which doesn't affect bbox area.
|
||||||
|
angle -= copysign(pi/2.0, angle)
|
||||||
|
|
||||||
|
min_bbox_area = bbox_area
|
||||||
|
min_bbox_area_angle = angle
|
||||||
|
|
||||||
|
return min_width_angle, min_bbox_area_angle
|
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Rotations - Find All Optimal</name>
|
||||||
|
<id>fablabchemnitz.de.rotations_find_all_optimal</id>
|
||||||
|
<param name="precision" type="int" min="1" max="72000" gui-text="Precision (steps):" gui-description="Default is 360">360</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Transformations" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">rotations_find_all_optimal.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
@ -0,0 +1,71 @@
|
|||||||
|
#! /usr/bin/env python3
|
||||||
|
'''
|
||||||
|
Copyright (C) 2019 Grant Patterson <grant@revoltlabs.co>
|
||||||
|
|
||||||
|
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
|
||||||
|
'''
|
||||||
|
|
||||||
|
import gettext
|
||||||
|
import sys
|
||||||
|
import inkex
|
||||||
|
import rotate_helper
|
||||||
|
from inkex import Transform
|
||||||
|
import copy
|
||||||
|
|
||||||
|
debug = False
|
||||||
|
|
||||||
|
error = lambda msg: inkex.errormsg(gettext.gettext(msg))
|
||||||
|
if debug:
|
||||||
|
stderr = lambda msg: sys.stderr.write(msg + '\n')
|
||||||
|
else:
|
||||||
|
stderr = lambda msg: None
|
||||||
|
|
||||||
|
class RotationsFindAllOptimal(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--precision", type=int, default=3, help="Precision")
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
|
||||||
|
def duplicateNodes(aList):
|
||||||
|
clones={}
|
||||||
|
for id, node in aList.items():
|
||||||
|
clone = copy.deepcopy(node)
|
||||||
|
myid = node.tag.split('}')[-1]
|
||||||
|
clone.set("id", self.svg.get_unique_id(myid))
|
||||||
|
node.getparent().append(clone)
|
||||||
|
node.delete()
|
||||||
|
clones[clone.get("id")]=clone
|
||||||
|
return(clones)
|
||||||
|
|
||||||
|
for nid, node in self.svg.selected.items():
|
||||||
|
# set() removes duplicates
|
||||||
|
angles = set(
|
||||||
|
# and remove Nones
|
||||||
|
[x for x in rotate_helper.optimal_rotations(node, self.options.precision)
|
||||||
|
if x is not None])
|
||||||
|
# Go backwards so we know if we need to duplicate the node for
|
||||||
|
# multiple rotations. (We don't want to rotate the main node
|
||||||
|
# before duplicating it.)
|
||||||
|
for i, angle in reversed(list(enumerate(angles))):
|
||||||
|
if i > 0:
|
||||||
|
# Rotate a duplicate of the node
|
||||||
|
rotate_node = list(duplicateNodes({nid: node}).items())[0][1]
|
||||||
|
else:
|
||||||
|
rotate_node = node
|
||||||
|
rotate_node.transform = rotate_helper.rotate_matrix(rotate_node, angle) @ rotate_node.transform
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
RotationsFindAllOptimal().run()
|
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Rotations - Minimum Bounding Box Area</name>
|
||||||
|
<id>fablabchemnitz.de.rotations_minimum_bounding_box_area</id>
|
||||||
|
<param name="precision" type="int" min="1" max="72000" gui-text="Precision (steps):" gui-description="Default is 360">360</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Transformations" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">rotations_minimum_bounding_box_area.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
@ -0,0 +1,46 @@
|
|||||||
|
#! /usr/bin/env python3
|
||||||
|
'''
|
||||||
|
Copyright (C) 2019 Grant Patterson <grant@revoltlabs.co>
|
||||||
|
|
||||||
|
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
|
||||||
|
'''
|
||||||
|
|
||||||
|
import gettext
|
||||||
|
import sys
|
||||||
|
import inkex
|
||||||
|
import rotate_helper
|
||||||
|
from inkex import Transform
|
||||||
|
|
||||||
|
debug = False
|
||||||
|
|
||||||
|
error = lambda msg: inkex.errormsg(gettext.gettext(msg))
|
||||||
|
if debug:
|
||||||
|
stderr = lambda msg: sys.stderr.write(msg + '\n')
|
||||||
|
else:
|
||||||
|
stderr = lambda msg: None
|
||||||
|
|
||||||
|
class RotationsMinimumBoundingBoxArea(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--precision", type=int, default=3, help="Precision")
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
for node in self.svg.selected.values():
|
||||||
|
min_bbox_angle = rotate_helper.optimal_rotations(node, self.options.precision)[1]
|
||||||
|
if min_bbox_angle is not None:
|
||||||
|
node.transform = Transform(rotate_helper.rotate_matrix(node, min_bbox_angle)) @ node.transform
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
RotationsMinimumBoundingBoxArea().run()
|
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Rotations - Minimum Width</name>
|
||||||
|
<id>fablabchemnitz.de.rotations_minimum_width</id>
|
||||||
|
<param name="precision" type="int" min="1" max="72000" gui-text="Precision (steps):" gui-description="Default is 360">360</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Transformations" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">rotations_minimum_width.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
@ -0,0 +1,46 @@
|
|||||||
|
#! /usr/bin/env python3
|
||||||
|
'''
|
||||||
|
Copyright (C) 2019 Grant Patterson <grant@revoltlabs.co>
|
||||||
|
|
||||||
|
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
|
||||||
|
'''
|
||||||
|
|
||||||
|
import gettext
|
||||||
|
import sys
|
||||||
|
import inkex
|
||||||
|
import rotate_helper
|
||||||
|
from inkex import Transform
|
||||||
|
|
||||||
|
debug = False
|
||||||
|
|
||||||
|
error = lambda msg: inkex.errormsg(gettext.gettext(msg))
|
||||||
|
if debug:
|
||||||
|
stderr = lambda msg: sys.stderr.write(msg + '\n')
|
||||||
|
else:
|
||||||
|
stderr = lambda msg: None
|
||||||
|
|
||||||
|
class RotationsMinimumWidth(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--precision", type=int, default=3, help="Precision")
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
for node in self.svg.selected.values():
|
||||||
|
min_width_angle = rotate_helper.optimal_rotations(node, self.options.precision)[0]
|
||||||
|
if min_width_angle is not None:
|
||||||
|
node.transform = rotate_helper.rotate_matrix(node, min_width_angle) @ node.transform
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
RotationsMinimumWidth().run()
|
Loading…
Reference in New Issue
Block a user