add back more extensions

This commit is contained in:
Mario Voigt 2022-10-08 22:40:43 +02:00
parent 196337a7bc
commit bc2301079d
33 changed files with 1902 additions and 0 deletions

View 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

View File

@ -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>

View 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()

View 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

View 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"
]
}
]

View 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>

View 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()

View 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"
]
}
]

View 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>

View 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()

View 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()

View 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>

View 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()

View 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>

View 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()

View 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"
]
}
]

View File

@ -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>

View File

@ -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()

View File

@ -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"
]
}
]

View 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"
]
}
]

View File

@ -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>

View File

@ -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()

View 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"
]
}
]

View 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>

View 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()

View 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"
]
}
]

View 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

View File

@ -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>

View File

@ -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()

View File

@ -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>

View File

@ -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()

View File

@ -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>

View File

@ -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()