From 2b180a26f68c0a4d58eb6f7328363baaf7571f01 Mon Sep 17 00:00:00 2001 From: Mario Voigt Date: Sat, 22 Aug 2020 12:13:37 +0200 Subject: [PATCH] Added convex hull plugin --- extensions/fablabchemnitz_convexhull.inx | 14 +++ extensions/fablabchemnitz_convexhull.py | 118 +++++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 extensions/fablabchemnitz_convexhull.inx create mode 100644 extensions/fablabchemnitz_convexhull.py diff --git a/extensions/fablabchemnitz_convexhull.inx b/extensions/fablabchemnitz_convexhull.inx new file mode 100644 index 00000000..0c6543b2 --- /dev/null +++ b/extensions/fablabchemnitz_convexhull.inx @@ -0,0 +1,14 @@ + + + Convex Hull + fablabchemnitz.de.convexhull + + all + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz_convexhull.py b/extensions/fablabchemnitz_convexhull.py new file mode 100644 index 00000000..d7dbebc1 --- /dev/null +++ b/extensions/fablabchemnitz_convexhull.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 + +import inkex +import sys +import numpy as n +from inkex.paths import CubicSuperPath +from inkex.transforms import Transform +from lxml import etree + +link = lambda a,b: n.concatenate((a,b[1:])) +edge = lambda a,b: n.concatenate(([a],[b])) + +''' + +Convex Hull routine by Markus Kohler, found at https://gist.github.com/kohlerm/0337f7c64df42f21f96405b3f8e895f2, which itself is an implementation of: +https://github.com/flengyel/REST/blob/master/quickhull.py +Compute the convex hull of a set of points in the plane +Adapted from en.literateprograms.org/Quickhull_(Python,arrays) +Content available under MIT/X11 License at +http://en.literateprograms.org/LiteratePrograms:Copyrights +Copyright (c) 2008 the authors listed in the page history +(see http://en.literateprograms.org/index.php?title=Quickhull_(Python,_arrays)&action=history) +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +''' + +def qhull(sample): + def dome(sample,base): + h, t = base + dists = n.dot(sample-h, n.dot(((0,-1),(1,0)),(t-h))) + outer = n.repeat(sample, dists>0, 0) + + if len(outer): + pivot = sample[n.argmax(dists)] + return link(dome(outer, edge(h, pivot)), + dome(outer, edge(pivot, t))) + else: + return base + if len(sample) > 2: + axis = sample[:,0] + base = n.take(sample, [n.argmin(axis), n.argmax(axis)], 0) + return link(dome(sample, base), + dome(sample, base[::-1])) + else: + return sample + +class ConvexHull(inkex.Effect): + def __init__(self): + inkex.Effect.__init__(self) + self.paths = {} + self.paths_clone_transform = {} + + def joinWithNode (self, node, path, makeGroup=False, cloneTransform=None): + if (not path) or (len(path) == 0 ): + return + g = self.document.getroot() + # Now make a element which contains the twist & is a child + # of the new element + style = { 'stroke': '#000000', 'fill': 'none', 'stroke-width': '1' } + line_attribs = { 'style':str(inkex.Style(style)), 'd': path } + if (cloneTransform != None ) and (cloneTransform != '' ): + line_attribs['transform'] = cloneTransform + etree.SubElement(g, inkex.addNS('path', 'svg' ), line_attribs) + + def effect(self): + global output_nodes, points + #Loop through all the selected items in Inkscape + for node in self.svg.selected.values(): + #create numpy array of nodes + n_array = [] + + #Iterate through all the selected objects in Inkscape + for node in self.svg.selected.values(): + #Check if the node is a path ("svg:path" node in XML ) + #id = node.id + if node.tag == inkex.addNS('path','svg'): + # bake (or fuse) transform + node.apply_transform() + #turn into cubicsuperpath + d = node.get('d') + p = CubicSuperPath(d) + for subpath in p: # there may be several paths joined together (e.g. holes) + for csp in subpath: # groups of three to handle control points. + # just the points no control points (handles) + n_array.append(csp[1][0]) + n_array.append(csp[1][1]) + + k = n.asarray(n_array) + length = int(len(k)/2) + c = k.reshape(length,2) + hull_pts = qhull(c) + + pdata = '' + for vertex in hull_pts: + if pdata == '': + pdata = 'M%f,%f' % (vertex[0], vertex[1] ) + else: + pdata += 'L %f,%f' % (vertex[0], vertex[1] ) + pdata += ' Z' + path = 'polygon' + makeGroup = False + paths_clone_transform = None + self.joinWithNode(path, pdata, makeGroup, paths_clone_transform ) + +ConvexHull().run() \ No newline at end of file