mightyscape-1.2/extensions/fablabchemnitz/perspective_grid/perspective_grid.py

279 lines
14 KiB
Python
Raw Normal View History

2022-09-02 18:19:57 +02:00
#!/usr/bin/env python3
'''
Copyright (C) 2013 Carl Sorensen carl.d.sorensen@gmail.com
Derived from grid_cartesian.py copyright (C) 2007 John Beard john.j.beard@gmail.com
##This extension allows you to draw a two-point perspective grid in Inkscape.
##There is a wide range of options including subdivision, subsubdivions
##and angles of the triangular axes.
##Custom line widths are also possible.
##All elements are grouped with similar elements (eg all x-subdivs)
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 2 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
import inkex
from math import *
from lxml import etree
def draw_SVG_line(x1, y1, x2, y2, width, stroke, name, parent):
style = { 'stroke': stroke, 'stroke-width':"{:0.6f}".format(width), 'fill': 'none' }
line_attribs = {'style':str(inkex.Style(style)),
inkex.addNS('label','inkscape'):name,
'd':'M '+str(x1)+','+str(y1)+' L '+str(x2)+','+str(y2)}
etree.SubElement(parent, inkex.addNS('path','svg'), line_attribs )
def draw_SVG_rect(x,y,w,h, width, stroke, fill, name, parent):
style = { 'stroke': stroke, 'stroke-width':str(width), 'fill':fill}
rect_attribs = {'style':str(inkex.Style(style)),
inkex.addNS('label','inkscape'):name,
'x':str(x), 'y':str(y), 'width':str(w), 'height':str(h)}
etree.SubElement(parent, inkex.addNS('rect','svg'), rect_attribs )
def colorString(pickerColor):
longcolor = int(pickerColor)
if longcolor < 0:
longcolor = longcolor & 0xFFFFFFFF
return '#' + format(longcolor >> 8, '06X')
class PerspectiveGrid(inkex.EffectExtension):
def add_arguments(self, pars):
pars.add_argument("--size_unit", default="", help="Units for geometry")
pars.add_argument("--width", type=int, default=500, help="Width of grid window")
pars.add_argument("--height", type=int, default=300, help="Height of grid window")
pars.add_argument("--p_divs", type=int, default=10, help="Number of divisions in perspective angle")
pars.add_argument("--horizon", type=float, default=150, help="Y coordinate of horizon")
pars.add_argument("--left_x", type=float, default=-250, help="X coordinate of left perspective point")
pars.add_argument("--right_x", type=float, default=750, help="X coordinate of right perspective point")
pars.add_argument("--div_th", type=float, default=2, help="Grid division line thickness (px)")
pars.add_argument("--border_th", type=float, default=3, help="Border Line thickness (px)")
pars.add_argument("--div_color", type=int, help="Grid color")
def EdgePoints(self,x0, y0, theta):
# find the intersection points of the line with the extended
# grid bounding box.
# Note that y is positive DOWN, not up
# Grid bounding box goes from (0,0) to (self.xmax, self.ymax)
theta_r = radians(theta)
if theta_r == 0:
return [[0,y0],[self.xmax,y0],
[-100, self.ymax], [self.xmax+100,0]]
r_bot = (self.ymax-y0)/sin(theta_r)
r_top = -y0/sin(theta_r)
r_left = -x0/cos(theta_r)
r_right = (self.xmax-x0)/cos(theta_r)
return [[0,y0+r_left*sin(theta_r)], # left
[self.xmax, y0+r_right*sin(theta_r)], # right
[x0+r_bot*cos(theta_r), self.ymax], #bottom
[x0+r_top*cos(theta_r), 0]] #top
def trimmed_coords(self, x1, y1, theta):
#find the start and end coordinates for a grid line
#starting at (x1, y1) with an angle of theta
border_points = self.EdgePoints(x1, y1, theta)
left = 0
right = 1
top = 3
bottom = 2
x=0
y=1
if theta > 0:
if border_points[left][y] < 0:
start_x = border_points[top][x]
start_y = border_points[top][y]
else:
start_x = border_points[left][x]
start_y = border_points[left][y]
if border_points[right][y] > self.ymax:
end_x = border_points[bottom][x]
end_y = border_points[bottom][y]
else:
end_x = border_points[right][x]
end_y = border_points[right][y]
else:
if border_points[left][y] > self.ymax:
start_x = border_points[bottom][x]
start_y = border_points[bottom][y]
else:
start_x = border_points[left][x]
start_y = border_points[left][y]
if border_points[right][y] < 0:
end_x = border_points[top][x]
end_y = border_points[top][y]
else:
end_x = border_points[right][x]
end_y = border_points[right][y]
return [[start_x,start_y],[end_x, end_y]]
def drawAngledGridLine (self, x1, y1, theta, thickness, color,
label, groupName):
end_points = self.trimmed_coords(x1, y1, theta)
x_start = end_points[0][0]
y_start = end_points[0][1]
x_end = end_points[1][0]
y_end = end_points[1][1]
if (x_start >= 0 and x_start <= self.xmax and
y_start >= 0 and y_start <= self.ymax and
x_end >= 0 and x_end <= self.xmax and
y_end >= 0 and y_end <= self.ymax):
draw_SVG_line(x_start, y_start,
x_end, y_end,
thickness, colorString(color), label, groupName)
def perspective_intersection(self, left_theta, right_theta):
if right_theta == 0 or left_theta == 0 or left_theta == right_theta:
return -100 # outside of bounding box
try:
r=(self.right_x - self.left_x)/(sin(right_theta)/tan(left_theta)-cos(right_theta))
y_int = self.horizon + r*sin(right_theta)
if y_int < 0 or y_int > self.ymax :
return -100 #above or below bounding box
return self.right_x + r*cos(right_theta)
except ZeroDivisionError:
inkex.errormsg("Perspective angle divisions resulted in division by zero. Please adjust the values for perspective points and/or angle divisions.")
exit()
def effect(self):
#find the pixel dimensions of the overall grid
self.ymax = self.svg.unittouu(str(self.options.height)+self.options.size_unit)
self.xmax = self.svg.unittouu(str(self.options.width)+self.options.size_unit)
self.horizon = self.svg.unittouu(str(self.options.horizon)+self.options.size_unit)
self.left_x = self.svg.unittouu(str(self.options.left_x)+self.options.size_unit)
self.right_x = self.svg.unittouu(str(self.options.right_x)+self.options.size_unit)
# Overwrite thickness values to use px unit properly
self.options.div_th = self.svg.unittouu(str(self.options.div_th) + "px")
self.options.border_th = self.svg.unittouu(str(self.options.border_th) + "px")
# Embed grid in group
#Put in in the centre of the current view
t = 'translate(' + str( self.svg.namedview.center[0]- self.xmax/2.0) + ',' + \
str( self.svg.namedview.center[1]- self.ymax/2.0) + ')'
g_attribs = {inkex.addNS('label','inkscape'):'Grid_Perspective:Size' + \
str( self.xmax)+'x'+str(self.ymax) +
':Horizon'+str(self.horizon) +
':LeftX'+str(self.left_x) +
':RightX'+str(self.right_x),
'transform':t }
grid = etree.SubElement(self.svg.get_current_layer(), 'g', g_attribs)
#Group for vertical gridlines
g_attribs = {inkex.addNS('label','inkscape'):'VerticalGridlines'}
gv = etree.SubElement(grid, 'g', g_attribs)
#Group for left point gridlines
g_attribs = {inkex.addNS('label','inkscape'):'LeftPointGridlines'}
glp = etree.SubElement(grid, 'g', g_attribs)
#Group for right point gridlines
g_attribs = {inkex.addNS('label','inkscape'):'RightPointGridlines'}
grp = etree.SubElement(grid, 'g', g_attribs)
draw_SVG_rect(0, 0, self.xmax, self.ymax, self.options.border_th,
colorString(self.options.div_color), 'none',
'Border', grid) #border rectangle
# Calculate the extreme angles for the left and right points
try:
if self.horizon < 0 :
left_theta_min = atan((self.horizon-0)/(0-self.right_x))
left_theta_max = atan((self.ymax - self.horizon)/
(0-self.left_x))
right_theta_min = atan((0-self.horizon)/
(self.left_x-self.xmax))
right_theta_max = atan((self.horizon - self.ymax)/
(self.right_x - self.xmax ))
elif self.horizon < self.ymax :
left_theta_min = atan((self.horizon-0)/(self.left_x-0))
left_theta_max = atan((self.ymax - self.horizon)/
(0-self.left_x))
right_theta_min = atan((self.horizon-0)/
(self.right_x-self.xmax))
right_theta_max = atan((self.horizon - self.ymax)/
(self.right_x - self.xmax ))
else:
left_theta_min = atan((self.horizon-0)/(self.left_x-0))
left_theta_max = atan((self.ymax - self.horizon)/
(0-self.right_x))
right_theta_min = atan((self.horizon-0)/
(self.right_x-self.xmax))
right_theta_max = atan((self.horizon - self.ymax)/
(self.left_x - self.xmax ))
except ZeroDivisionError:
inkex.errormsg("Division by zero error. Please adjust the values accordingly.")
exit()
left_dtheta = (left_theta_max - left_theta_min)/float(self.options.p_divs)
right_dtheta = (right_theta_max - right_theta_min)/float(self.options.p_divs)
mid_index = self.options.p_divs/2
left_mid_theta = left_theta_min + mid_index * left_dtheta
right_mid_theta = right_theta_min + mid_index * right_dtheta
#DO THE PERSPECTIVE DIVISONS========================================
for i in range(0,self.options.p_divs+1):
left_theta = left_theta_min + i * left_dtheta
right_theta = right_theta_min + i * right_dtheta
self.drawAngledGridLine(self.left_x, self.horizon,
degrees(left_theta),
self.options.div_th,
self.options.div_color,
'LeftDivPersp'+str(i),
glp)
self.drawAngledGridLine(self.right_x, self.horizon,
degrees(right_theta),
self.options.div_th,
self.options.div_color,
'RightDivPersp'+str(i),
grp)
intersection = self.perspective_intersection(left_theta,
right_theta_max - i * right_dtheta)
if intersection > 0 and intersection < self.xmax:
draw_SVG_line(intersection, 0,
intersection, self.ymax,
self.options.div_th,
colorString(self.options.div_color),
'VerticalDiv'+str(i), gv)
comment = """
intersection = self.perspective_intersection(left_theta, right_mid_theta)
if intersection > 0 and intersection < self.xmax:
draw_SVG_line(intersection, 0,
intersection, self.ymax,
self.options.div_th,
colorString(self.options.div_color),
'VerticalDiv'+str(i), gv)
intersection = self.perspective_intersection(left_theta, right_theta)
if intersection > 0 and intersection < self.xmax:
draw_SVG_line(intersection, 0,
intersection, self.ymax,
self.options.div_th,
colorString(self.options.div_color),
'VerticalDiv'+str(i), gv)
"""
intersection = self.perspective_intersection(left_mid_theta, right_mid_theta)
if intersection > 0 and intersection < self.xmax:
draw_SVG_line(intersection, 0,
intersection, self.ymax,
self.options.div_th,
colorString(self.options.div_color),
'VerticalDiv'+str(i), gv)
if __name__ == '__main__':
PerspectiveGrid().run()