mightyscape-1.2/extensions/fablabchemnitz/another_perspective/another_perspective.py
2024-07-02 21:02:08 +02:00

312 lines
9.3 KiB
Python

#!/usr/bin/env python3
"""
Copyright (C) 2017 Corentin Brulé
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, see <http://www.gnu.org/licenses/>.
Special thanks and orignal copyrigths : Aaron Spike (2005) and Timo Kähkönen (2012)
"""
import inkex
import re
from lxml import etree
from inkex.transforms import Transform
from inkex.paths import Path, CubicSuperPath
__version__ = '0.1'
debug=False
def distort_path(path_str,source,destination):
path_arr = path_string_to_array(path_str)
subpath_type=""
is_num =""
xy_counter =""
xy=""
path_arr2=[]
subpath_type_upper=""
point=""
i=0
for i in range(len(path_arr)):
patt1 = r"[mzlhvcsqta]"
curr = path_arr[i]
if re.match(patt1,curr,flags=re.I):
xy_counter = -1
subpath_type = curr
subpath_type_upper = subpath_type.upper()
is_num = False
path_arr2.append(curr)
else :
is_num = True
curr = float(curr)
if xy_counter%2 == 0:
xy="x"
else:
xy="y"
if is_num :
if xy=="y" :
point = transferPoint(float(path_arr[i-1]),curr,source,destination)
path_arr2.append(point["x"])
path_arr2.append(point["y"])
xy_counter+=1
path_str = path_array_to_string(path_arr2)
return path_str
def path_array_to_string(path_arr):
path_str=str(path_arr)
path_str=path_str.replace(r"([0-9]),([-0-9])", "$1 $2")
path_str=path_str.replace(r"([0-9]),([-0-9])", "$1 $2")
path_str=path_str.replace(",", "")
path_str=path_str.replace("[", "").replace("]","")
path_str=path_str.replace("'", "")
return path_str
def path_string_to_array(path_str):
patt1=r"[mzlhvcsqta]|-?[0-9.]+" #gi
#path_arr=path_str.match(patt1) #array de résultats
path_arr = re.findall(patt1,path_str,flags=re.I)
patt1=r"[mzlhvcsqta]" #i
i = 0
for i in range(len(path_arr)):
if re.match(path_arr[i],patt1,flags=re.I) == -1:
path_arr[i] = float(path_arr[i])
return path_arr
'''
def isPermissible(p):
p0 = {x:c0.attr("cx"),y:c0.attr("cy")}
p1 = {x:c1.attr("cx"),y:c1.attr("cy")}
p2 = {x:c2.attr("cx"),y:c2.attr("cy")}
p3 = {x:c3.attr("cx"),y:c3.attr("cy")}
a0 = angle(p3, p0, p1)
a1 = angle(p0, p1, p2)
a2 = angle(p1, p2, p3)
a3 = angle(p2, p3, p0)
if not (a0 > 0 and a0 < 180) or not (a1 > 0 and a1 < 180) or not(a2 > 0 and a2 < 180) or not(a3 > 0 and a3 < 180) :
return False
else :
return True
}
def angle(c, b, a):
ab = {x: b.x - a.x, y: b.y - a.y }
cb = {x: b.x - c.x, y: b.y - c.y }
dot = (ab.x * cb.x + ab.y * cb.y)
cross = (ab.x * cb.y - ab.y * cb.x)
alpha = Math.atan2(cross, dot)
return alpha * 180 / PI
}
'''
def transferPoint (xI, yI, source, destination):
ADDING = 0.001 # to avoid dividing by zero
xA = source[0]["x"]
yA = source[0]["y"]
xC = source[2]["x"]
yC = source[2]["y"]
xAu = destination[0]["x"]
yAu = destination[0]["y"]
xBu = destination[1]["x"]
yBu = destination[1]["y"]
xCu = destination[2]["x"]
yCu = destination[2]["y"]
xDu = destination[3]["x"]
yDu = destination[3]["y"]
# Calcultations
if xBu==xCu :
xCu+=ADDING
if xAu==xDu :
xDu+=ADDING
if xAu==xBu :
xBu+=ADDING
if xDu==xCu :
xCu+=ADDING
kBC = float(yBu-yCu)/float(xBu-xCu)
kAD = float(yAu-yDu)/float(xAu-xDu)
kAB = float(yAu-yBu)/float(xAu-xBu)
kDC = float(yDu-yCu)/float(xDu-xCu)
if kBC==kAD :
kAD += ADDING
xE = float(kBC*xBu - kAD*xAu + yAu - yBu) / float(kBC-kAD)
yE = kBC*(xE - xBu) + yBu
if kAB==kDC :
kDC += ADDING
xF = float(kAB*xBu - kDC*xCu + yCu - yBu) / float(kAB-kDC)
yF = kAB*(xF - xBu) + yBu
if xE==xF :
xF += ADDING
kEF = float(yE-yF) / float(xE-xF)
if kEF==kAB:
kAB += ADDING
xG = float(kEF*xDu - kAB*xAu + yAu - yDu) / float(kEF-kAB)
yG = kEF*(xG - xDu) + yDu
if kEF==kBC :
kBC+=ADDING
xH = float(kEF*xDu - kBC*xBu + yBu - yDu) / float(kEF-kBC)
yH = kEF*(xH - xDu) + yDu
rG = float(yC-yI)/float(yC-yA)
rH = float(xI-xA)/float(xC-xA)
xJ = (xG-xDu)*rG + xDu
yJ = (yG-yDu)*rG + yDu
xK = (xH-xDu)*rH + xDu
yK = (yH-yDu)*rH + yDu
if xF==xJ:
xJ+=ADDING
if xE==xK:
xK+=ADDING
kJF = float(yF-yJ) / float(xF-xJ)
kKE = float(yE-yK) / float(xE-xK)
xKE = ""
if kJF==kKE:
kKE += ADDING
xIu = float(kJF*xF - kKE*xE + yE - yF) / float(kJF-kKE)
yIu = kJF * (xIu - xJ) + yJ
b = {"x":xIu,"y":yIu}
b["x"] = round(b["x"])
b["y"] = round(b["y"])
return b
def projection(path_object,coords):
pp_object = Path(path_object).to_arrays()
bounds = Path(path_object).bounding_box()
# Make array of coordinates, every array member represent corner of text path
source = [
{"x":bounds.left,"y":bounds.top},
{"x":bounds.right,"y":bounds.top},
{"x":bounds.right,"y":bounds.bottom},
{"x":bounds.left,"y":bounds.bottom}
]
destination=[
{"x":coords[0][0],"y":coords[0][1]},
{"x":coords[1][0],"y":coords[1][1]},
{"x":coords[2][0],"y":coords[2][1]},
{"x":coords[3][0],"y":coords[3][1]}
]
path_destination = distort_path(path_object,source,destination)
return path_destination
'''
def complex2tulpe(complexNb):
return (complexNb.real,complexNb.imag)
'''
class AnotherPerspective(inkex.EffectExtension):
def envelope2coords(self, path_envelope):
pp_envelope = CubicSuperPath(path_envelope)
if len(pp_envelope[0]) < 4:
inkex.errormsg("The selected envelope (your second path) does not contain enough nodes. Check to have at least 4 nodes.")
exit()
c0 = pp_envelope[0][0][0]
c1 = pp_envelope[0][1][0]
c2 = pp_envelope[0][2][0]
c3 = pp_envelope[0][3][0]
# inkex.debug(str(c0)+" "+str(c1)+" "+str(c2)+" "+str(c3))
return [c0, c1, c2, c3]
def effect(self):
if len(self.options.ids) < 2:
inkex.errormsg("This extension requires two selected paths.")
exit()
obj = self.svg.selected[self.options.ids[0]]
envelope = self.svg.selected[self.options.ids[1]]
if obj.get(inkex.addNS('type','sodipodi')):
inkex.errormsg("The first selected object is of type '%s'.\nTry using the procedure Path->Object to Path." % obj.get(inkex.addNS('type','sodipodi')))
exit()
if obj.tag == inkex.addNS('path','svg') or obj.tag == inkex.addNS('g','svg'):
if envelope.tag == inkex.addNS('path','svg'):
mat = envelope.transform @ Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])
path = CubicSuperPath(envelope.get('d'))
Path(path).transform(mat)
absolute_envelope_path = envelope.get('d')
# inkex.debug(absolute_envelope_path)
coords_to_project = self.envelope2coords(absolute_envelope_path)
if obj.tag == inkex.addNS('path','svg'):
mat = obj.transform @ Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])
absolute_d = str(Path(obj.get('d')))
path = CubicSuperPath(absolute_d)
Path(path).transform(mat)
absolute_object_path = str(path)
# inkex.debug(absolute_object_path)
elif obj.tag == inkex.addNS('g','svg'):
absolute_object_path=""
for p in obj.iterfind(".//{http://www.w3.org/2000/svg}path"):
absolute_d = str(Path(p.get('d')))
mat = p.transform @ Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])
path = CubicSuperPath(absolute_d)
Path(path).transform(mat)
absolute_object_path += str(Path(path))
# inkex.debug(absolute_object_path)
new_path = projection(absolute_object_path,coords_to_project)
attributes = {'d':new_path, 'style':str(obj.style)}
new_element = etree.SubElement(self.svg.get_current_layer(),inkex.addNS('path','svg'),attributes)
else:
if envelope.tag == inkex.addNS('g','svg'):
inkex.errormsg("The second selected object is a group, not a path.\nTry using the procedure Object->Ungroup.")
else:
inkex.errormsg("The second selected object is not a path.\nTry using the procedure Path->Object to Path.")
exit()
else:
inkex.errormsg("The first selected object is not a path.\nTry using the procedure Path->Object to Path.")
exit()
if __name__ == '__main__':
AnotherPerspective().run()