312 lines
13 KiB
Python
Executable File
312 lines
13 KiB
Python
Executable File
#! /usr/bin/python
|
|
'''
|
|
Copyright (C) 2017 Artem Synytsyn a.synytsyn@gmail.com
|
|
|
|
#TODO: Code cleaning and refactoring
|
|
|
|
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
'''
|
|
|
|
import inkex
|
|
import sys
|
|
from lxml import etree
|
|
from math import *
|
|
|
|
|
|
import re
|
|
from typing import Any
|
|
import wikipedia
|
|
import openpyxl
|
|
|
|
|
|
import math, decimal, datetime
|
|
dec = decimal.Decimal
|
|
|
|
|
|
|
|
|
|
# ['NAMESPACE', 'PARSER', 'TAG', 'WRAPPED_ATTRS', '__abstractmethods__', '__bool__',
|
|
# '__class__', '__contains__', '__copy__', '__deepcopy__', '__delattr__', '__delitem__',
|
|
# '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__',
|
|
# '__getattribute__', '__getitem__', '__getstate__', '__gt__', '__hash__', '__init__',
|
|
# '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__ne__',
|
|
# '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__',
|
|
# '__setitem__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '__weakref__',
|
|
# '_abc_impl', '_ancestors', '_init', '_is_visible', 'add', 'add_unit', 'addnext',
|
|
# 'addprevious', 'ancestors', 'append', 'attrib', 'backlinks', 'bake_transforms_recursively',
|
|
# 'base', 'bounding_box', 'cascaded_style', 'clear', 'clip', 'composed_style',
|
|
# 'composed_transform', 'copy', 'cssselect', 'delete', 'desc', 'descendants',
|
|
# 'description', 'duplicate', 'effective_style', 'eid', 'extend', 'find', 'findall',
|
|
# 'findone', 'findtext', 'get', 'get_id', 'get_line_height_uu', 'get_or_create',
|
|
# 'get_path', 'getchildren', 'getiterator', 'getnext', 'getparent', 'getprevious',
|
|
# 'getroottree', 'groupmode', 'href', 'index', 'insert', 'is_class_element',
|
|
# 'is_sensitive', 'is_visible', 'items', 'iter', 'iterancestors', 'iterchildren',
|
|
# 'iterdescendants', 'iterfind', 'itersiblings', 'itertext', 'keys', 'label',
|
|
# 'makeelement', 'new', 'nsmap', 'path', 'pop', 'prefix', 'presentation_style',
|
|
# 'remove', 'remove_all', 'replace', 'replace_with', 'root', 'set', 'set_id',
|
|
# 'set_path', 'set_random_id', 'set_random_ids', 'set_sensitive', 'shape_box',
|
|
# 'sourceline', 'specified_style', 'tag', 'tag_name', 'tail', 'text', 'title',
|
|
# 'to_dimensional', 'to_dimensionless', 'to_path_element', 'tostring', 'typename',
|
|
# 'unit', 'unit_to_viewport', 'unittouu', 'update', 'uutounit', 'values',
|
|
# 'viewport_to_unit', 'wrapped_attrs', 'wrapped_props', 'xml_path', 'xpath']
|
|
|
|
|
|
# ___ _ _ _ _ _
|
|
# |_ _|_ __ | | _____ ___ __ _ _ __ ___| | | |_ __ _ __(_)_ __ | |_
|
|
# | || '_ \| |/ / __|/ __/ _` | '_ \ / _ \ | | | '_ \| '__| | '_ \| __|
|
|
# | || | | | <\__ \ (_| (_| | |_) | __/ |_| | |_) | | | | | | | |_
|
|
# |___|_| |_|_|\_\___/\___\__,_| .__/ \___|\___/| .__/|_| |_|_| |_|\__|
|
|
# |_| |_|
|
|
|
|
|
|
class _Spacing():
|
|
size = 0.
|
|
padding = 0.
|
|
margin_top = 0.
|
|
margin_bottom = 0.
|
|
margin_left = 0.
|
|
margin_right = 0.
|
|
|
|
class InkscapeUprint(inkex.Effect):
|
|
def __init__(self):
|
|
inkex.Effect.__init__(self)
|
|
|
|
# General settings
|
|
self.arg_parser.add_argument("--x",
|
|
type=float,
|
|
dest="x", default=0.0,
|
|
help="Center X")
|
|
self.arg_parser.add_argument("--y",
|
|
type=float,
|
|
dest="y", default=0.0,
|
|
help="Center Y")
|
|
self.arg_parser.add_argument("-u", "--units",
|
|
type=str,
|
|
dest="units", default="mm",
|
|
help="units to measure size of knob")
|
|
|
|
# Dummy
|
|
self.arg_parser.add_argument("--tab")
|
|
|
|
# # Label settings
|
|
self.arg_parser.add_argument("--subst_file",
|
|
type=str,
|
|
dest="subst_file", default='',
|
|
help="xlsx file to load substitutions")
|
|
# self.arg_parser.add_argument("--font_family",
|
|
# type=str,
|
|
# dest="font_family", default="Apple Garamond",
|
|
# help="Font family to use for days and labels")
|
|
# self.arg_parser.add_argument("--year",
|
|
# type=int,
|
|
# dest="year", default=1970,
|
|
# help="Calendar Year")
|
|
# self.arg_parser.add_argument("-m", "--month",
|
|
# type=str,
|
|
# dest="month", default="Gennaio",
|
|
# help="month to render")
|
|
# self.arg_parser.add_argument("--dayfrom",
|
|
# type=int,
|
|
# dest="dayfrom", default=1,
|
|
# help="Range to render day from")
|
|
# self.arg_parser.add_argument("--dayto",
|
|
# type=int,
|
|
# dest="dayto", default=31,
|
|
# help="Range to render day to")
|
|
# self.arg_parser.add_argument("--day_text_size",
|
|
# type=float,
|
|
# dest="day_text_size", default=12,
|
|
# help="Text size of day")
|
|
# self.arg_parser.add_argument("--saint_text_size",
|
|
# type=float,
|
|
# dest="saint_text_size", default=8,
|
|
# help="Text size of saint")
|
|
|
|
# spacing
|
|
# self.__elements = ['day', 'wday', 'saint', 'moon']
|
|
|
|
self.__element_spacing = {
|
|
'size' : 0,
|
|
'padding' : 0,
|
|
'margin-top' : 5,
|
|
'margin-bottom' : 5,
|
|
'margin-left' : 5,
|
|
'margin-right' : 5
|
|
}
|
|
|
|
self.__page_spacing = {
|
|
'size' : 0,
|
|
'padding' : 0,
|
|
'margin-top' : 5,
|
|
'margin-bottom' : 5,
|
|
'margin-left' : 5,
|
|
'margin-right' : 5
|
|
}
|
|
|
|
# for el in self.__elements:
|
|
# for sk in self.__spacing.keys():
|
|
# sk = sk.replace('-','_')
|
|
# self.arg_parser.add_argument("--" + el + "_" + sk,
|
|
# type=float,
|
|
# dest=el + "_" + sk,
|
|
# help="Spacing " + el + " " + sk)
|
|
self.pages = []
|
|
|
|
def svg_traverse_node(self, node, tag=None):
|
|
def _tag(tag):
|
|
return re.sub(r'\{.*?\}', r'', tag)
|
|
l = []
|
|
if isinstance(node, etree._Element):
|
|
if tag is None or _tag(node.tag) == tag:
|
|
l.append(node)
|
|
for child in node.getchildren():
|
|
l.extend(self.svg_traverse_node(child))
|
|
elif isinstance(node, etree._Comment):
|
|
pass
|
|
elif isinstance(node, list):
|
|
for child in node:
|
|
l.extend(self.svg_traverse_node(child))
|
|
return l
|
|
|
|
def replace_text(self, selected, substs):
|
|
import re
|
|
for node in selected:
|
|
tspans = [ n for n in self.svg_traverse_node(node, tag='tspan')]
|
|
for k,v in substs.items():
|
|
patt = re.compile(r'\{\{' + re.escape(k) + r'\}\}')
|
|
for t in tspans:
|
|
if t.text is None: continue
|
|
t.text = patt.sub(v, t.text)
|
|
multilines = t.text.split('\n')
|
|
if len(multilines) > 1:
|
|
t.text = multilines[0]
|
|
for i in range(1, len(multilines)):
|
|
new_t = etree.Element('tspan', nsmap=t.nsmap)
|
|
new_t.text = multilines[i]
|
|
new_t.tail = t.tail
|
|
new_t.attrib.update(t.attrib) # copy style
|
|
p = t.getparent()
|
|
p.insert(p.index(t) + 1, new_t)
|
|
|
|
def duplicate_object(self, parent, selected):
|
|
duplicated = []
|
|
for node in selected:
|
|
duplicated.append(etree.Element(node.tag, node.attrib, nsmap=node.nsmap))
|
|
self.duplicate_object(duplicated[-1], node.getchildren())
|
|
duplicated[-1].text = node.text
|
|
if 'transform' in node.attrib:
|
|
duplicated[-1].set('transform', node.attrib['transform'])
|
|
parent.append(duplicated[-1])
|
|
|
|
return duplicated
|
|
|
|
def translate_object(self, obj, x, y):
|
|
trans = 'translate(' + str(x) + ',' + str(y) + ')'
|
|
for d in obj:
|
|
if 'transform' in d.attrib: d.set('transform', d.get('transform') + ' ' + trans)
|
|
else: d.set('transform', trans)
|
|
|
|
def place_in_pages(self, elements = None):
|
|
if elements is None:
|
|
return
|
|
first_page = self.svg.getElement("//inkscape:page")
|
|
parent = first_page.getparent()
|
|
pages = [ node for node in self.svg_traverse_node(parent, tag='inkscape:page')]
|
|
rightmost_page = max(pages, key=lambda p: p.get('x'))
|
|
|
|
# the reference page is always the first one
|
|
pgbbox = {"width": first_page.get('width'), "height": first_page.get('height')}
|
|
elbbox = self.svg.selected.bounding_box()
|
|
# number of elements per page
|
|
nx = int (( float(pgbbox['width']) - self.__page_spacing['margin-left'] - self.__page_spacing['margin-right'] ) / \
|
|
( elbbox.width + self.__element_spacing['margin-left'] + self.__element_spacing['margin-right'] ))
|
|
ny = int (( float(pgbbox['height']) - self.__page_spacing['margin-top'] - self.__page_spacing['margin-bottom'] ) / \
|
|
( elbbox.height + self.__element_spacing['margin-top'] + self.__element_spacing['margin-bottom'] ))
|
|
|
|
# remove model position
|
|
correct_x = self.svg.selected.bounding_box().left
|
|
correct_y = self.svg.selected.bounding_box().top
|
|
|
|
# compute page position zero
|
|
px = float(rightmost_page.get('x')) + float(rightmost_page.get('width'))
|
|
py = float(rightmost_page.get('y'))
|
|
|
|
# place elements
|
|
for i, el in enumerate(elements):
|
|
# create new page if needed
|
|
if i % int(nx * ny) == 0:
|
|
new_page = etree.Element('{http://www.inkscape.org/namespaces/inkscape}page', nsmap=first_page.nsmap)
|
|
new_page.set('x', str(float(rightmost_page.get('x')) + float(rightmost_page.get('width')) + 20 ))
|
|
new_page.set('y', str(float(rightmost_page.get('y')) + 0 ))
|
|
new_page.set('width', str(float(first_page.get('width'))))
|
|
new_page.set('height', str(float(first_page.get('height'))))
|
|
parent.append(new_page)
|
|
rightmost_page = new_page
|
|
px = float(rightmost_page.get('x'))
|
|
py = float(rightmost_page.get('y'))
|
|
|
|
x = px + (i % nx) * ( elbbox.width + self.__element_spacing['margin-left'] + self.__element_spacing['margin-right'] ) + self.__page_spacing['margin-left']
|
|
y = py + (i // nx) * ( elbbox.height + self.__element_spacing['margin-top'] + self.__element_spacing['margin-bottom'] ) + self.__page_spacing['margin-top']
|
|
self.translate_object(el, x-correct_x, y-correct_y)
|
|
|
|
|
|
# inkex.utils.errormsg("pages:" + repr(pages))
|
|
# for p in pages:
|
|
# inkex.utils.errormsg("p:" + str(p))
|
|
pass
|
|
|
|
def effect(self):
|
|
|
|
# for el in self.options.element_spacing:
|
|
# self.__element_spacing[el] = self.svg.unittouu(str(self.options.element_spacing[el]) + self.options.units)
|
|
# for el in self.options.page_spacing:
|
|
# self.__page_spacing[el] = self.svg.unittouu(str(self.options.page_spacing[el]) + self.options.units)
|
|
|
|
parent = self.svg.get_current_layer()
|
|
selected = self.svg.selected
|
|
|
|
if selected:
|
|
self.bbox = selected.bounding_box()
|
|
self.x_offset = self.svg.unittouu(str(self.options.x) + self.options.units)
|
|
self.y_offset = self.svg.unittouu(str(self.options.y) + self.options.units)
|
|
else:
|
|
inkex.utils.errormsg("Error - No object selected")
|
|
# self.place_in_pages()
|
|
return
|
|
|
|
wb = openpyxl.load_workbook(self.options.subst_file)
|
|
ws = wb.active
|
|
headings = [cell.value for cell in ws[1]]
|
|
substs = []
|
|
for row in ws.iter_rows(min_row=2, values_only=True):
|
|
substs.append(dict(zip(headings, row)))
|
|
|
|
new_objects = []
|
|
|
|
for i, subst in enumerate(substs):
|
|
dup = self.duplicate_object(parent, selected)
|
|
# self.translate_object(dup, (i+1) * (self.x_offset + self.bbox.width), self.y_offset)
|
|
self.replace_text(dup, subst)
|
|
new_objects.append(dup)
|
|
|
|
# populating spacing with options
|
|
self.place_in_pages(new_objects)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
e = InkscapeUprint()
|
|
e.run()
|
|
|