mightyscape-1.2/extensions/fablabchemnitz/bobbinlace/ground_from_template.py

176 lines
7.2 KiB
Python
Raw Normal View History

2022-10-03 02:49:07 +02:00
#!/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': 'miter',
'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()