Added Low Poly 2 by miffysora
This commit is contained in:
parent
ba9ac619c3
commit
67de370574
16
extensions/fablabchemnitz_low_poly_2.inx
Normal file
16
extensions/fablabchemnitz_low_poly_2.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>Low Poly 2</name>
|
||||
<id>fablabchemnitz.de.low_poly_2</id>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Shape/Pattern from existing Object(s)"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">fablabchemnitz_low_poly_2.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
300
extensions/fablabchemnitz_low_poly_2.py
Normal file
300
extensions/fablabchemnitz_low_poly_2.py
Normal file
@ -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.y<other.x*other.y)
|
||||
|
||||
def __le__(self,other):
|
||||
return (self.x*self.y<=other.y*other.y)
|
||||
|
||||
def __gt__(self,other):
|
||||
return (self.x*self.y>other.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()
|
Reference in New Issue
Block a user