176 lines
7.2 KiB
Python
176 lines
7.2 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# Copyright (c) 2017, Veronika Irvine
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
#
|
|
# 1. Redistributions of source code must retain the above copyright notice, this
|
|
# list of conditions and the following disclaimer.
|
|
# 2. Redistributions in binary form must reproduce the above copyright notice,# this list of conditions and the following disclaimer in the documentation
|
|
# and/or other materials provided with the distribution.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
import os
|
|
from math import sin, cos, radians, ceil
|
|
from lxml import etree
|
|
import inkex
|
|
|
|
__author__ = 'Veronika Irvine'
|
|
__credits__ = ['Ben Connors', 'Veronika Irvine', 'Mark Shafer']
|
|
__license__ = 'Simplified BSD'
|
|
|
|
class GroundFromTemplate(inkex.EffectExtension):
|
|
|
|
def loadFile(self):
|
|
# Ensure that file exists and has the proper extension
|
|
if not self.options.file:
|
|
inkex.errormsg('You must specify a template file.')
|
|
exit()
|
|
self.options.file = self.options.file.strip()
|
|
if self.options.file == '':
|
|
inkex.errormsg('You must specify a template file.')
|
|
exit()
|
|
if not os.path.isfile(self.options.file):
|
|
inkex.errormsg('You have not specified a valid path for the template file.\n\nYour entry: '+self.options.file)
|
|
exit()
|
|
extension = os.path.splitext(self.options.file)[1]
|
|
if extension != '.txt':
|
|
inkex.errormsg('The file name must end with .txt.\n\nYour entry: '+self.options.file)
|
|
exit()
|
|
|
|
data = []
|
|
rowCount = 0
|
|
colCount = 0
|
|
with open(self.options.file,'r') as f:
|
|
first = True
|
|
for line in f:
|
|
if first:
|
|
# first line of file gives row count and column count
|
|
first = False
|
|
line = line.strip()
|
|
temp = line.split('\t')
|
|
type = temp[0]
|
|
rowCount = int(temp[1])
|
|
colCount = int(temp[-1])
|
|
|
|
else:
|
|
line = line.strip()
|
|
line = line.lstrip('[')
|
|
line = line.rstrip(']')
|
|
rowData = line.split(']\t[')
|
|
data.append([])
|
|
for cell in rowData:
|
|
cell = cell.strip()
|
|
data[-1].append([float(num) for num in cell.split(',')])
|
|
|
|
return {'type':type, 'rowCount':rowCount, 'colCount':colCount, 'data':data}
|
|
|
|
def line(self, x1, y1, x2, y2):
|
|
"""
|
|
Draw a line from point at (x1, y1) to point at (x2, y2).
|
|
Style of line is hard coded and specified by 's'.
|
|
"""
|
|
# define the motions
|
|
path = 'M %s,%s L %s,%s' %(x1,y1,x2,y2)
|
|
|
|
# define the stroke style
|
|
s = {'stroke-linejoin': 'miter',
|
|
'stroke-width': self.options.linewidth,
|
|
'stroke-opacity': '1.0',
|
|
'fill-opacity': '1.0',
|
|
'stroke': self.options.linecolor,
|
|
'stroke-linecap': 'butt',
|
|
'stroke-linejoin': 'butt',
|
|
'fill': 'none'
|
|
}
|
|
|
|
# create attributes from style and path
|
|
attribs = {'style':str(inkex.Style(s)), 'd':path}
|
|
|
|
# insert path object into current layer
|
|
etree.SubElement(self.svg.get_current_layer(), inkex.addNS('path', 'svg'), attribs)
|
|
|
|
def draw(self, data, rowCount, colCount):
|
|
a = self.options.distance
|
|
theta = self.options.angle
|
|
deltaX = a*sin(theta)
|
|
deltaY = a*cos(theta)
|
|
maxRows = ceil(self.options.height / deltaY)
|
|
maxCols = ceil(self.options.width / deltaX)
|
|
|
|
x = 0.0
|
|
y = 0.0
|
|
repeatY = 0
|
|
repeatX = 0
|
|
|
|
while repeatY * rowCount < maxRows:
|
|
x = 0.0
|
|
repeatX = 0
|
|
|
|
while repeatX * colCount < maxCols:
|
|
|
|
for row in data:
|
|
for coords in row:
|
|
x1 = x + coords[0]*deltaX
|
|
y1 = y + coords[1]*deltaY
|
|
x2 = x + coords[2]*deltaX
|
|
y2 = y + coords[3]*deltaY
|
|
x3 = x + coords[4]*deltaX
|
|
y3 = y + coords[5]*deltaY
|
|
|
|
self.line(x1,y1,x2,y2)
|
|
self.line(x1,y1,x3,y3)
|
|
|
|
repeatX += 1
|
|
x += deltaX * colCount
|
|
|
|
repeatY += 1
|
|
y += deltaY * rowCount
|
|
|
|
def add_arguments(self, pars):
|
|
pars.add_argument('-f', '--file', help='File containing lace ground description')
|
|
pars.add_argument('--angle', type=float)
|
|
pars.add_argument('--distance', type=float)
|
|
pars.add_argument('--pinunits')
|
|
pars.add_argument('--width', type=float)
|
|
pars.add_argument('--patchunits')
|
|
pars.add_argument('--height', type=float)
|
|
pars.add_argument('--linewidth', type=float)
|
|
pars.add_argument('--lineunits')
|
|
pars.add_argument('--linecolor', type=inkex.Color)
|
|
|
|
def effect(self):
|
|
result = self.loadFile()
|
|
|
|
# Convert input to universal units
|
|
self.options.width = self.svg.unittouu(str(self.options.width)+self.options.patchunits)
|
|
self.options.height = self.svg.unittouu(str(self.options.height)+self.options.patchunits)
|
|
self.options.linewidth = self.svg.unittouu(str(self.options.linewidth)+self.options.lineunits)
|
|
self.options.distance = self.svg.unittouu(str(self.options.distance)+self.options.pinunits)
|
|
|
|
# Users expect distance to be the vertical distance between footside pins
|
|
# (vertical distance between every other row) but in the script we use it
|
|
# as the diagonal distance between grid points
|
|
# therefore convert distance based on the angle chosen
|
|
self.options.angle = radians(self.options.angle)
|
|
self.options.distance = self.options.distance/(2.0*cos(self.options.angle))
|
|
|
|
# Draw a ground based on file description and user inputs
|
|
self.options.linecolor = self.options.linecolor.to_rgb()
|
|
# For now, assume style is Checker but could change in future
|
|
self.draw(result['data'],result['rowCount'],result['colCount'])
|
|
|
|
if __name__ == '__main__':
|
|
GroundFromTemplate().run() |