diff --git a/extensions/fablabchemnitz_low_poly_2.inx b/extensions/fablabchemnitz_low_poly_2.inx new file mode 100644 index 00000000..d20f8306 --- /dev/null +++ b/extensions/fablabchemnitz_low_poly_2.inx @@ -0,0 +1,16 @@ + + + Low Poly 2 + fablabchemnitz.de.low_poly_2 + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz_low_poly_2.py b/extensions/fablabchemnitz_low_poly_2.py new file mode 100644 index 00000000..a74ba293 --- /dev/null +++ b/extensions/fablabchemnitz_low_poly_2.py @@ -0,0 +1,300 @@ +#!/usr/bin/env python3 + +import os +import inkex +import voronoi +from inkex.transforms import Transform +from inkex.paths import CubicSuperPath, Path +from PIL import Image +from lxml import etree +import base64 +from io import BytesIO +import urllib.request as urllib + +# A tool for making polygonal art. Can be created with one click with a pass. + +class Point: + def __init__(self, x, y): + self.x = x + self.y = y + def __lt__(self,other): + return (self.x*self.yother.x*other.y) + + def __ge__(self,other): + return (self.x*self.y>=other.x*other.y) + + def __eq__(self,other): + return (self.x==other.x) and (self.y==other.y) + + def __ne__(self,other): + return (self.x!=other.x) or (self.y!=other.y) + + def __str__(self): + return "("+str(self.x)+","+str(self.y)+")" + + +class Voronoi2svg(inkex.Effect): + def __init__(self): + inkex.Effect.__init__(self) + + # Clipping a line by a bounding box + def dot(self, x, y): + return x[0] * y[0] + x[1] * y[1] + + def intersectLineSegment(self, line, v1, v2): + s1 = self.dot(line, v1) - line[2] + s2 = self.dot(line, v2) - line[2] + if s1 * s2 > 0: + return (0, 0, False) + else: + tmp = self.dot(line, v1) - self.dot(line, v2) + if tmp == 0: + return (0, 0, False) + u = (line[2] - self.dot(line, v2)) / tmp + v = 1 - u + return (u * v1[0] + v * v2[0], u * v1[1] + v * v2[1], True) + + def clipEdge(self, vertices, lines, edge, bbox): + # bounding box corners + bbc = [] + bbc.append((bbox[0], bbox[2])) + bbc.append((bbox[1], bbox[2])) + bbc.append((bbox[1], bbox[3])) + bbc.append((bbox[0], bbox[3])) + + # record intersections of the line with bounding box edges + line = (lines[edge[0]]) + interpoints = [] + for i in range(4): + p = self.intersectLineSegment(line, bbc[i], bbc[(i + 1) % 4]) + if (p[2]): + interpoints.append(p) + + # if the edge has no intersection, return empty intersection + if (len(interpoints) < 2): + return [] + + if (len(interpoints) > 2): #h appens when the edge crosses the corner of the box + interpoints = list(set(interpoints)) # remove doubles + + # points of the edge + v1 = vertices[edge[1]] + interpoints.append((v1[0], v1[1], False)) + v2 = vertices[edge[2]] + interpoints.append((v2[0], v2[1], False)) + + # sorting the points in the widest range to get them in order on the line + minx = interpoints[0][0] + maxx = interpoints[0][0] + miny = interpoints[0][1] + maxy = interpoints[0][1] + for point in interpoints: + minx = min(point[0], minx) + maxx = max(point[0], maxx) + miny = min(point[1], miny) + maxy = max(point[1], maxy) + + if (maxx - minx) > (maxy - miny): + interpoints.sort() + else: + interpoints.sort(key=lambda pt: pt[1]) + + start = [] + inside = False #true when the part of the line studied is in the clip box + startWrite = False #true when the part of the line is in the edge segment + for point in interpoints: + if point[2]: #The point is a bounding box intersection + if inside: + if startWrite: + return [[start[0], start[1]], [point[0], point[1]]] + else: + return [] + else: + if startWrite: + start = point + inside = not inside + else: # The point is a segment endpoint + if startWrite: + if inside: + # a vertex ends the line inside the bounding box + return [[start[0], start[1]], [point[0], point[1]]] + else: + return [] + else: + if inside: + start = point + startWrite = not startWrite + + # Transformation helpers + def invertTransform(self, mat): + det = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0] + if det != 0: #det is 0 only in case of 0 scaling + # invert the rotation/scaling part + a11 = mat[1][1] / det + a12 = -mat[0][1] / det + a21 = -mat[1][0] / det + a22 = mat[0][0] / det + + # invert the translational part + a13 = -(a11 * mat[0][2] + a12 * mat[1][2]) + a23 = -(a21 * mat[0][2] + a22 * mat[1][2]) + + return [[a11, a12, a13], [a21, a22, a23]] + else: + return [[0, 0, -mat[0][2]], [0, 0, -mat[1][2]]] + + def getGlobalTransform(self, node): + parent = node.getparent() + myTrans = Transform(node.get('transform')).matrix + if myTrans: + if parent is not None: + parentTrans = self.getGlobalTransform(parent) + if parentTrans: + return Transform(parentTrans) * Transform(myTrans) + else: + return myTrans + else: + if parent is not None: + return self.getGlobalTransform(parent) + else: + return None + + def checkImagePath(self, node): + """Embed the data of the selected Image Tag element""" + xlink = node.get('xlink:href') + if xlink and xlink[:5] == 'data:': + # No need, data alread embedded + return + + url = urllib.urlparse(xlink) + href = urllib.url2pathname(url.path) + + # Primary location always the filename itself. + path = self.absolute_href(href or '') + + # Backup directory where we can find the image + if not os.path.isfile(path): + path = node.get('sodipodi:absref', path) + + if not os.path.isfile(path): + inkex.errormsg('File not found "{}". Unable to embed image.').format(path) + return + + if (os.path.isfile(path)): + return path + + def effect(self): + # Check that elements have been selected + if len(self.options.ids) == 0: + inkex.errormsg("Please select objects!") + return + + # Drawing styles + linestyle = { + 'stroke': '#000000', + 'stroke-width': str(self.svg.unittouu('1px')), + 'fill': 'none' + } + + facestyle = { + 'stroke': '#000000', + 'stroke-width':'0px',# str(self.svg.unittouu('1px')), + 'fill': 'none' + } + + # Handle the transformation of the current group + parentGroup = (self.svg.selected[self.options.ids[0]]).getparent() + + svg = self.document.getroot() + image_element = svg.find('.//{http://www.w3.org/2000/svg}image') + if image_element is None: + inkex.utils.debug("No image found") + exit(1) + self.path = self.checkImagePath(image_element) # This also ensures the file exists + if self.path is None: # check if image is embedded or linked + image_string = image_element.get('{http://www.w3.org/1999/xlink}href') + # find comma position + i = 0 + while i < 40: + if image_string[i] == ',': + break + i = i + 1 + img = Image.open(BytesIO(base64.b64decode(image_string[i + 1:len(image_string)]))) + else: + img = Image.open(self.path) + + extrinsic_image_width=float(image_element.get('width')) + extrinsic_image_height=float(image_element.get('height')) + (width, height) = img.size + trans = self.getGlobalTransform(parentGroup) + invtrans = None + if trans: + invtrans = self.invertTransform(trans) + + # Recovery of the selected objects + pts = [] + nodes = [] + seeds = [] + + for id in self.options.ids: + node = self.svg.selected[id] + nodes.append(node) + if(node.tag=="{http://www.w3.org/2000/svg}path"):#If it is path + # Get vertex coordinates of path + points = CubicSuperPath(node.get('d')) + for p in points[0]: + pt=[p[1][0],p[1][1]] + if trans: + Transform(trans).apply_to_point(pt) + pts.append(Point(pt[0], pt[1])) + seeds.append(Point(p[1][0], p[1][1])) + else: # For other shapes + bbox = node.bounding_box() + if bbox: + cx = 0.5 * (bbox.left + bbox.top) + cy = 0.5 * (bbox.top + bbox.bottom) + pt = [cx, cy] + if trans: + Transform(trans).apply_to_point(pt) + pts.append(Point(pt[0], pt[1])) + seeds.append(Point(cx, cy)) + pts.sort() + seeds.sort() + + # In Creation of groups to store the result + # Delaunay + groupDelaunay = etree.SubElement(parentGroup, inkex.addNS('g', 'svg')) + groupDelaunay.set(inkex.addNS('label', 'inkscape'), 'Delaunay') + + scale_x=float(extrinsic_image_width)/float(width) + scale_y=float(extrinsic_image_height)/float(height) + # Voronoi diagram generation + + triangles = voronoi.computeDelaunayTriangulation(seeds) + for triangle in triangles: + p1 = seeds[triangle[0]] + p2 = seeds[triangle[1]] + p3 = seeds[triangle[2]] + cmds = [['M', [p1.x, p1.y]], + ['L', [p2.x, p2.y]], + ['L', [p3.x, p3.y]], + ['Z', []]] + path = etree.Element(inkex.addNS('path', 'svg')) + path.set('d', str(Path(cmds))) + middleX=(p1.x+p2.x+p3.x)/3.0 + middleY=(p1.y+p2.y+p3.y)/3.0 + if width>middleX and height>middleY and middleX>=0 and middleY>=0: + r,g,b = img.getpixel((middleX,middleY)) + facestyle["fill"]=str(inkex.Color((r, g, b))) + else: + facestyle["fill"]="black" + path.set('style', str(inkex.Style(facestyle))) + groupDelaunay.append(path) + +Voronoi2svg().run() \ No newline at end of file