Merge branch 'master' of https://780f082f9e7a3158c671efd803efd1ea70270517@gitea.fablabchemnitz.de/MarioVoigt/mightyscape-1.X.git
This commit is contained in:
commit
62d4e98b3a
@ -8,7 +8,7 @@ CutOptim OS Wrapper script to make CutOptim work on Windows and Linux systems wi
|
||||
Author: Mario Voigt / FabLab Chemnitz
|
||||
Mail: mario.voigt@stadtfabrikanten.org
|
||||
Date: 31.08.2020
|
||||
Last patch: 31.08.2020
|
||||
Last patch: 14.01.2021
|
||||
License: GNU GPL v3
|
||||
|
||||
"""
|
||||
@ -50,10 +50,13 @@ class CutOptimWrapper(inkex.Effect):
|
||||
else:
|
||||
cmd += " --" + arg + " " + str(getattr(self.options, arg))
|
||||
|
||||
output_file = None
|
||||
if os.name == "nt":
|
||||
cmd += " --output cutoptim.svg"
|
||||
output_file = "cutoptim.svg"
|
||||
else:
|
||||
cmd += " --output /tmp/cutoptim.svg"
|
||||
output_file = "/tmp/cutoptim.svg"
|
||||
|
||||
cmd += " --output " + output_file
|
||||
#inkex.utils.debug(str(cmd))
|
||||
|
||||
# run CutOptim with the parameters provided
|
||||
@ -62,7 +65,7 @@ class CutOptimWrapper(inkex.Effect):
|
||||
|
||||
# check output existence
|
||||
try:
|
||||
stream = open("/tmp/cutoptim.svg", 'r')
|
||||
stream = open(output_file, 'r')
|
||||
except FileNotFoundError as e:
|
||||
inkex.utils.debug("There was no SVG output generated. Cannot continue")
|
||||
exit(1)
|
||||
|
64
extensions/fablabchemnitz/delaunay.inx
Normal file
64
extensions/fablabchemnitz/delaunay.inx
Normal file
@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension
|
||||
xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Delaunay Triangulation</name>
|
||||
<id>fablabchemnitz.de.delaunay_triangulation</id>
|
||||
<param name="tab" type="notebook">
|
||||
<page name="Options" gui-text="Options">
|
||||
<param name="joggling" type="bool" gui-text="Support concavity and holes">false</param>
|
||||
<param name="furthest" type="bool" gui-text="Use furthest-site triangulation">false</param>
|
||||
<param name="elt_type" type="enum" gui-text="Object type to generate">
|
||||
<item value="poly">Triangles</item>
|
||||
<item value="line">Individual lines</item>
|
||||
</param>
|
||||
<spacer />
|
||||
<separator />
|
||||
<spacer />
|
||||
<hbox>
|
||||
<param name="fill_type" type="enum" gui-text="Fill color source">
|
||||
<item value="first_sel">Same as first object selected</item>
|
||||
<item value="last_sel">Same as last object selected</item>
|
||||
<item value="random">Random</item>
|
||||
<item value="specified">Explicitly specified</item>
|
||||
</param>
|
||||
<param name="fill_color" type="color" appearance="colorbutton" gui-text=" " gui-description="Specific fill color">-1</param>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<param name="stroke_type" type="enum" gui-text="Stroke color source">
|
||||
<item value="first_sel">Same as first object selected</item>
|
||||
<item value="last_sel">Same as last object selected</item>
|
||||
<item value="random">Random</item>
|
||||
<item value="specified">Explicitly specified</item>
|
||||
</param>
|
||||
<param name="stroke_color" type="color" appearance="colorbutton" gui-text=" " gui-description="Specific stroke color">255</param>
|
||||
</hbox>
|
||||
</page>
|
||||
<page name="Advanced" gui-text="Advanced">
|
||||
<param name="qhull" type="string" gui-text="qhull options">Qbb Qc Qz Q12</param>
|
||||
<param name="name" type="description">
|
||||
If "Support concavity" is enabled on the Options tab, "QJ" will be
|
||||
prepended to the qhull options listed above. The default options
|
||||
are "Qbb Qc Qz Q12". The following website describes the available
|
||||
options.
|
||||
</param>
|
||||
<label appearance="url">http://www.qhull.org/html/qhull.htm#options</label>
|
||||
</page>
|
||||
<page name="Help" gui-text="Help">
|
||||
<label>
|
||||
This effect uses the Delaunay triangulation algorithm to create
|
||||
triangles from all of the points found in the selected objects.
|
||||
</label>
|
||||
</page>
|
||||
</param>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Shape/Pattern from existing Path(s)" />
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">delaunay.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
198
extensions/fablabchemnitz/delaunay.py
Normal file
198
extensions/fablabchemnitz/delaunay.py
Normal file
@ -0,0 +1,198 @@
|
||||
#! /usr/bin/python
|
||||
|
||||
'''
|
||||
Copyright (C) 2020 Scott Pakin, scott-ink@pakin.org
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 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 inkex
|
||||
import numpy as np
|
||||
import random
|
||||
import sys
|
||||
from inkex import Group, Line, Polygon, TextElement
|
||||
from inkex.styles import Style
|
||||
from inkex.transforms import Vector2d
|
||||
from scipy.spatial import Delaunay
|
||||
|
||||
class DelaunayTriangulation(inkex.EffectExtension):
|
||||
'Overlay selected objects with triangles.'
|
||||
|
||||
def add_arguments(self, pars):
|
||||
'Parse the arguments passed to us from an Inkscape dialog box.'
|
||||
pars.add_argument('--tab', help='The selected UI tab when OK was pressed')
|
||||
pars.add_argument('--joggling', type=inkex.Boolean, default=False, help='Use joggled input instead of merged facets')
|
||||
pars.add_argument('--furthest', type=inkex.Boolean, default=False, help='Furthest-site Delaunay triangulation')
|
||||
pars.add_argument('--elt_type', default='poly', help='Element type to generate ("poly" or "line")')
|
||||
pars.add_argument('--qhull', help='Triangulation options to pass to qhull')
|
||||
pars.add_argument('--fill_type', help='How to fill generated polygons')
|
||||
pars.add_argument('--fill_color', type=inkex.Color, help='Fill color to use with a fill type of "specified"')
|
||||
pars.add_argument('--stroke_type', help='How to stroke generated polygons')
|
||||
pars.add_argument('--stroke_color', type=inkex.Color, help='Stroke color to use with a stroke type of "specified"')
|
||||
|
||||
def _path_points(self, elt):
|
||||
'Return a list of all points on a path (endpoints, not control points).'
|
||||
pts = set()
|
||||
first = None
|
||||
prev = Vector2d()
|
||||
for cmd in elt.path.to_absolute():
|
||||
if first is None:
|
||||
first = cmd.end_point(first, prev)
|
||||
ep = cmd.end_point(first, prev)
|
||||
pts.add((ep.x, ep.y))
|
||||
prev = ep
|
||||
return pts
|
||||
|
||||
def _create_styles(self, n):
|
||||
'Return a style to use for the generated objects.'
|
||||
# Use either the first or the last element's stroke for line caps,
|
||||
# stroke widths, etc.
|
||||
fstyle = self.svg.selection.first().style
|
||||
lstyle = self.svg.selection[-1].style
|
||||
if self.options.stroke_type == 'last_sel':
|
||||
style = Style(lstyle)
|
||||
else:
|
||||
style = Style(fstyle)
|
||||
|
||||
# Apply the specified fill color.
|
||||
if self.options.fill_type == 'first_sel':
|
||||
fcolor = fstyle.get_color('fill')
|
||||
style.set_color(fcolor, 'fill')
|
||||
elif self.options.fill_type == 'last_sel':
|
||||
fcolor = lstyle.get_color('fill')
|
||||
style.set_color(fcolor, 'fill')
|
||||
elif self.options.fill_type == 'specified':
|
||||
style.set_color(self.options.fill_color, 'fill')
|
||||
elif self.options.fill_type == 'random':
|
||||
pass # Handled below
|
||||
else:
|
||||
sys.exit(inkex.utils.errormsg(_('Internal error: Unrecognized fill type "%s".')) % self.options.fill_type)
|
||||
|
||||
# Apply the specified stroke color.
|
||||
if self.options.stroke_type == 'first_sel':
|
||||
scolor = fstyle.get_color('stroke')
|
||||
style.set_color(scolor, 'stroke')
|
||||
elif self.options.stroke_type == 'last_sel':
|
||||
scolor = lstyle.get_color('stroke')
|
||||
style.set_color(scolor, 'stroke')
|
||||
elif self.options.stroke_type == 'specified':
|
||||
style.set_color(self.options.stroke_color, 'stroke')
|
||||
elif self.options.stroke_type == 'random':
|
||||
pass # Handled below
|
||||
else:
|
||||
sys.exit(inkex.utils.errormsg(_('Internal error: Unrecognized stroke type "%s".')) % self.options.stroke_type)
|
||||
|
||||
# Produce n copies of the style.
|
||||
styles = [Style(style) for i in range(n)]
|
||||
if self.options.fill_type == 'random':
|
||||
for s in styles:
|
||||
r = random.randint(0, 255)
|
||||
g = random.randint(0, 255)
|
||||
b = random.randint(0, 255)
|
||||
s.set_color('#%02x%02x%02x' % (r, g, b), 'fill')
|
||||
s['fill-opacity'] = 255
|
||||
if self.options.stroke_type == 'random':
|
||||
for s in styles:
|
||||
r = random.randint(0, 255)
|
||||
g = random.randint(0, 255)
|
||||
b = random.randint(0, 255)
|
||||
s.set_color('#%02x%02x%02x' % (r, g, b), 'stroke')
|
||||
s['stroke-opacity'] = 255
|
||||
|
||||
# Return the list of styles.
|
||||
return [str(s) for s in styles]
|
||||
|
||||
def _create_polygons(self, triangles):
|
||||
'Render triangles as SVG polygons.'
|
||||
styles = self._create_styles(len(triangles))
|
||||
group = self.svg.get_current_layer().add(Group())
|
||||
for tri, style in zip(triangles, styles):
|
||||
tri_str = ' '.join(['%.10g %.10g' % (pt[0], pt[1]) for pt in tri])
|
||||
poly = Polygon()
|
||||
poly.set('points', tri_str)
|
||||
poly.style = style
|
||||
group.add(poly)
|
||||
|
||||
def _create_lines(self, triangles):
|
||||
'Render triangles as individual SVG lines.'
|
||||
# First, find all unique lines.
|
||||
lines = set()
|
||||
for tri in triangles:
|
||||
if len(tri) != 3:
|
||||
sys.exit(inkex.utils.errormsg(_('Internal error: Encountered a non-triangle.')))
|
||||
for i, j in [(0, 1), (0, 2), (1, 2)]:
|
||||
xy1 = tuple(tri[i])
|
||||
xy2 = tuple(tri[j])
|
||||
if xy1 < xy2:
|
||||
lines.update([(xy1, xy2)])
|
||||
else:
|
||||
lines.update([(xy2, xy1)])
|
||||
|
||||
# Then, create SVG line elements.
|
||||
styles = self._create_styles(len(lines))
|
||||
group = self.svg.get_current_layer().add(Group())
|
||||
for ([(x1, y1), (x2, y2)], style) in zip(lines, styles):
|
||||
line = Line()
|
||||
line.set('x1', x1)
|
||||
line.set('y1', y1)
|
||||
line.set('x2', x2)
|
||||
line.set('y2', y2)
|
||||
line.style = style
|
||||
group.add(line)
|
||||
|
||||
def effect(self):
|
||||
'Triangulate a set of objects.'
|
||||
|
||||
# Complain if the selection is empty.
|
||||
if len(self.svg.selection) == 0:
|
||||
return inkex.utils.errormsg(_('Please select at least one object.'))
|
||||
|
||||
# Acquire a set of all points from all selected objects.
|
||||
all_points = set()
|
||||
warned_text = False
|
||||
for obj in self.svg.selection.values():
|
||||
if isinstance(obj, TextElement) and not warned_text:
|
||||
sys.stderr.write('Warning: Text elements are not currently supported. Ignoring all text in the selection.\n')
|
||||
warned_text = True
|
||||
all_points.update(self._path_points(obj.to_path_element()))
|
||||
|
||||
# Use SciPy to perform the Delaunay triangulation.
|
||||
pts = np.array(list(all_points))
|
||||
if len(pts) == 0:
|
||||
return inkex.utils.errormsg(_('No points were found.'))
|
||||
qopts = self.options.qhull
|
||||
if self.options.joggling:
|
||||
qopts = 'QJ ' + qopts
|
||||
simplices = Delaunay(pts, furthest_site=self.options.furthest, qhull_options=qopts).simplices
|
||||
|
||||
# Create either triangles or lines, as request. Either option uses the
|
||||
# style of the first object in the selection.
|
||||
triangles = []
|
||||
for s in simplices:
|
||||
try:
|
||||
triangles.append(pts[s])
|
||||
except IndexError:
|
||||
pass
|
||||
if self.options.elt_type == 'poly':
|
||||
self._create_polygons(triangles)
|
||||
elif self.options.elt_type == 'line':
|
||||
self._create_lines(triangles)
|
||||
else:
|
||||
return inkex.utils.errormsg(_('Internal error: unexpected element type "%s".') % self.options.elt_type)
|
||||
|
||||
if __name__ == '__main__':
|
||||
DelaunayTriangulation().run()
|
Reference in New Issue
Block a user