summaryrefslogtreecommitdiff
path: root/renderer
diff options
context:
space:
mode:
Diffstat (limited to 'renderer')
-rw-r--r--renderer/Makefile35
-rw-r--r--renderer/src/_lib.scad83
-rw-r--r--renderer/src/_settings.scad12
-rw-r--r--renderer/src/jig.scad4
-rw-r--r--renderer/src/pcb_shape.scad8
-rwxr-xr-xrenderer/support/generate_kicad.py322
-rwxr-xr-xrenderer/support/inkscape_exporter.py65
-rwxr-xr-xrenderer/support/inkscape_svg_filter_layers.py51
-rw-r--r--renderer/support/pogojig/__init__.py0
-rw-r--r--renderer/support/pogojig/inkscape/__init__.py0
-rw-r--r--renderer/support/pogojig/inkscape/bezmisc.py287
-rw-r--r--renderer/support/pogojig/inkscape/cspsubdiv.py38
-rw-r--r--renderer/support/pogojig/inkscape/cubicsuperpath.py174
-rw-r--r--renderer/support/pogojig/inkscape/dxf_footer.txt62
-rw-r--r--renderer/support/pogojig/inkscape/dxf_header.txt580
-rw-r--r--renderer/support/pogojig/inkscape/effect.py279
-rw-r--r--renderer/support/pogojig/inkscape/ffgeom.py137
-rw-r--r--renderer/support/pogojig/inkscape/inkex.py363
-rw-r--r--renderer/support/pogojig/inkscape/inkscape.py124
-rw-r--r--renderer/support/pogojig/inkscape/simplepath.py213
-rw-r--r--renderer/support/pogojig/inkscape/simpletransform.py242
-rw-r--r--renderer/support/pogojig/kicad/__init__.py0
-rw-r--r--renderer/support/pogojig/kicad/kicad-cache.lib21
-rw-r--r--renderer/support/pogojig/kicad/kicad.pro34
24 files changed, 3134 insertions, 0 deletions
diff --git a/renderer/Makefile b/renderer/Makefile
new file mode 100644
index 0000000..0c6f340
--- /dev/null
+++ b/renderer/Makefile
@@ -0,0 +1,35 @@
+# Environment variables:
+# INKSCAPE_DXF_FLATNESS controls inkscape SVG->DXF export curve flatness (default: 0.1)
+# INKSCAPE, OPENSCAD: Commands to use for inkscape and openscad
+
+OPENSCAD ?= openscad
+
+all: out.zip
+
+out.zip: out/jig.stl out/pcb_shape.dxf out/kicad
+ zip -r out.zip out
+
+src/input.preprocessed.dxf: src/input.preprocessed.svg
+ support/inkscape_exporter.py $< $@
+
+out/pcb_shape.dxf: src/pcb_shape.scad src/input.preprocessed.dxf out
+ $(OPENSCAD) -o $@ $<
+
+out/jig.stl: src/jig.scad src/input.preprocessed.dxf
+ $(OPENSCAD) -o $@ $<
+
+out/kicad: input.svg out/pcb_shape.dxf
+ support/generate_kicad.py $^ $@
+
+src/input.preprocessed.svg: input.svg
+ support/inkscape_svg_filter_layers.py $< $@ --only --name "Test Points" "Mounting Holes" "Grip Slots" "Outline"
+
+out:
+ mkdir -p out
+
+.PHONY: clean
+clean:
+ rm -f src/input.preprocessed.dxf
+ rm -f src/input.preprocessed.svg
+ rm -rf out
+
diff --git a/renderer/src/_lib.scad b/renderer/src/_lib.scad
new file mode 100644
index 0000000..2654ed7
--- /dev/null
+++ b/renderer/src/_lib.scad
@@ -0,0 +1,83 @@
+
+module hole(l, step, w){
+ translate([0, 0, -eps]) {
+ union(){
+ linear_extrude(l+eps*2)
+ children();
+ minkowski(){
+ linear_extrude(eps) children();
+ cylinder(step, w, 0);
+ }
+ }
+ marker_r = 4;
+ marker_w = 1;
+ linear_extrude(1)
+ difference() {
+ offset(marker_r+0.5*marker_w) children();
+ offset(marker_r-0.5*marker_w) children();
+ }
+ }
+}
+
+module top_chamfer(height, chamfer){
+ difference(){
+ linear_extrude(height) children();
+ translate([0,0,height+eps])
+ union() {
+ for(w=[0:.2:chamfer]){
+ mirror([0,0,1])
+ linear_extrude(chamfer-w)
+ difference(){
+ offset(1) children();
+ offset(-w) children();
+ }
+ }
+ }
+ }
+}
+
+module base_shape(wall){
+ offset(grip_rounding) offset(-grip_rounding)difference(){
+ hull() offset(wall)
+ children();
+ import(input_file, layer="Grip Slots");
+ }
+}
+
+module holder(height, depth, wall, tolerance, chamfer){
+ difference() {
+ top_chamfer(height, chamfer/2)
+ //linear_extrude(height)
+ base_shape(wall) children();
+ translate([0,0,height-depth])
+ linear_extrude(depth+eps) offset(tolerance)
+ children();
+ translate([0,0,height-chamfer+eps]) minkowski(){
+ linear_extrude(eps) children();
+ cylinder(chamfer, 0, chamfer);
+ }
+ }
+}
+
+module mounting_hole(height, inset_depth, inset_extra){
+ union(){
+ translate([0,0,-eps])
+ linear_extrude(height+2*eps)
+ children();
+ translate([0,0,height-inset_depth])
+ linear_extrude(inset_depth+eps)
+ offset(inset_extra)
+ children();
+ }
+}
+
+module jig(height, depth, wall, tolerance, chamfer) {
+ difference(){
+ holder(height, depth, wall, tolerance, chamfer)
+ import(input_file, layer="Outline");
+ hole(height-depth, 2, 1)
+ import(input_file, layer="Test Points");
+ mounting_hole(height, 3, 2)
+ import(input_file, layer="Mounting Holes");
+ }
+}
diff --git a/renderer/src/_settings.scad b/renderer/src/_settings.scad
new file mode 100644
index 0000000..f51736a
--- /dev/null
+++ b/renderer/src/_settings.scad
@@ -0,0 +1,12 @@
+
+height=15;
+depth=6;
+wall=10;
+pcb_extra=7;
+tolerance=0.2;
+chamfer=3;
+grip_rounding = 1.5;
+
+input_file = "input.preprocessed.dxf";
+$fs=0.01;
+eps=0.1;
diff --git a/renderer/src/jig.scad b/renderer/src/jig.scad
new file mode 100644
index 0000000..d953f29
--- /dev/null
+++ b/renderer/src/jig.scad
@@ -0,0 +1,4 @@
+include <_settings.scad>
+include <_lib.scad>
+
+jig(height, depth, wall, tolerance, chamfer); \ No newline at end of file
diff --git a/renderer/src/pcb_shape.scad b/renderer/src/pcb_shape.scad
new file mode 100644
index 0000000..a3e1db9
--- /dev/null
+++ b/renderer/src/pcb_shape.scad
@@ -0,0 +1,8 @@
+include <_settings.scad>
+include <_lib.scad>
+
+difference(){
+ base_shape(wall+pcb_extra)
+ import(input_file, layer="Outline");
+ import(input_file, layer="Mounting Holes");
+} \ No newline at end of file
diff --git a/renderer/support/generate_kicad.py b/renderer/support/generate_kicad.py
new file mode 100755
index 0000000..3e1b614
--- /dev/null
+++ b/renderer/support/generate_kicad.py
@@ -0,0 +1,322 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+import time
+from os import path
+from textwrap import dedent
+import pkgutil
+import subprocess
+import xml.etree.ElementTree as xe
+
+import ezdxf
+
+
+__version__ = '0.1'
+
+PIN_TS_BASE = 0x23420000
+TEDIT_BASE = 0x23430000
+PATH_BASE = 0x23440000
+
+
+def sch_template(name, num_pins, yspace=200):
+ templ = f'''
+ EESchema Schematic File Version 5
+ EELAYER 30 0
+ EELAYER END
+ $Descr A3 16535 11693
+ encoding utf-8
+ Sheet 1 1
+ Title "{name}"
+ Date "{time.strftime("%d %b %Y")}"
+ Rev ""
+ Comp ""
+ Comment1 ""
+ Comment2 ""
+ Comment3 ""
+ Comment4 ""
+ Comment5 ""
+ Comment6 ""
+ Comment7 ""
+ Comment8 ""
+ Comment9 ""
+ $EndDescr
+ {{components}}
+ $EndSCHEMATC
+ '''
+
+ components = []
+ for i in range(num_pins):
+ identifier = f'TP{i}'
+ value = 'pogopin'
+ x, y = 1000, 1000 + i*yspace
+ components.append(dedent(f'''
+ $Comp
+ L Connector:Conn_01x01_Female {identifier}
+ U 1 1 {PIN_TS_BASE + i:08X}
+ P {x} {y}
+ F 0 "{identifier}" H {x-50} {y+50} 50 0000 R CNN
+ F 1 "{value}" H {x+50} {y} 50 0000 L CNN
+ F 2 "Pogopin:AutogeneratedPogopinFootprint" H {x} {y} 50 0001 C CNN
+ F 3 "~" H {x} {y} 50 0001 C CNN
+ 1 {x} {y}
+ -1 0 0 1
+ $EndComp
+ ''').strip())
+
+ return dedent(templ).lstrip().format(components='\n'.join(components))
+
+def pcb_template(outline, pins, annular=0.5):
+ pcb_templ = f'''
+ (kicad_pcb (version 20190605) (host pogojig "({__version__})")
+
+ (general
+ (thickness 1.6)
+ (drawings {len(pins)})
+ (tracks 0)
+ (modules {len(pins)})
+ (nets {len(pins)+1})
+ )
+
+ (page "A4")
+ (layers
+ (0 "F.Cu" signal)
+ (31 "B.Cu" signal)
+ (32 "B.Adhes" user)
+ (33 "F.Adhes" user)
+ (34 "B.Paste" user)
+ (35 "F.Paste" user)
+ (36 "B.SilkS" user)
+ (37 "F.SilkS" user)
+ (38 "B.Mask" user)
+ (39 "F.Mask" user)
+ (40 "Dwgs.User" user)
+ (41 "Cmts.User" user)
+ (42 "Eco1.User" user)
+ (43 "Eco2.User" user)
+ (44 "Edge.Cuts" user)
+ (45 "Margin" user)
+ (46 "B.CrtYd" user)
+ (47 "F.CrtYd" user)
+ (48 "B.Fab" user)
+ (49 "F.Fab" user)
+ )
+
+ (setup
+ (last_trace_width 0.25)
+ (trace_clearance 0.2)
+ (zone_clearance 0.508)
+ (zone_45_only no)
+ (trace_min 0.2)
+ (via_size 0.8)
+ (via_drill 0.4)
+ (via_min_size 0.4)
+ (via_min_drill 0.3)
+ (uvia_size 0.3)
+ (uvia_drill 0.1)
+ (uvias_allowed no)
+ (uvia_min_size 0.2)
+ (uvia_min_drill 0.1)
+ (max_error 0.005)
+ (defaults
+ (edge_clearance 0.01)
+ (edge_cuts_line_width 0.05)
+ (courtyard_line_width 0.05)
+ (copper_line_width 0.2)
+ (copper_text_dims (size 1.5 1.5) (thickness 0.3) keep_upright)
+ (silk_line_width 0.12)
+ (silk_text_dims (size 1 1) (thickness 0.15) keep_upright)
+ (other_layers_line_width 0.1)
+ (other_layers_text_dims (size 1 1) (thickness 0.15) keep_upright)
+ )
+ (pad_size 3.14159 3.14159)
+ (pad_drill 1.41421)
+ (pad_to_mask_clearance 0.051)
+ (solder_mask_min_width 0.25)
+ (aux_axis_origin 0 0)
+ (visible_elements FFFFFF7F)
+ (pcbplotparams
+ (layerselection 0x010fc_ffffffff)
+ (usegerberextensions false)
+ (usegerberattributes false)
+ (usegerberadvancedattributes false)
+ (creategerberjobfile false)
+ (excludeedgelayer true)
+ (linewidth 0.100000)
+ (plotframeref false)
+ (viasonmask false)
+ (mode 1)
+ (useauxorigin false)
+ (hpglpennumber 1)
+ (hpglpenspeed 20)
+ (hpglpendiameter 15.000000)
+ (psnegative false)
+ (psa4output false)
+ (plotreference true)
+ (plotvalue true)
+ (plotinvisibletext false)
+ (padsonsilk false)
+ (subtractmaskfromsilk false)
+ (outputformat 1)
+ (mirror false)
+ (drillshape 1)
+ (scaleselection 1)
+ (outputdirectory ""))
+ )
+
+ (net 0 "")
+ {{net_defs}}
+
+ (net_class "Default" "This is the default net class."
+ (clearance 0.2)
+ (trace_width 0.25)
+ (via_dia 0.8)
+ (via_drill 0.4)
+ (uvia_dia 0.3)
+ (uvia_drill 0.1)
+ {{net_class_defs}}
+ )
+
+ {{module_defs}}
+
+ {{edge_cuts}}
+ )'''
+
+ module_defs = []
+ for i, pin in enumerate(pins):
+ (x, y), hole_dia = pin # all dimensions in mm here
+ pad_dia = hole_dia + 2*annular
+ mod = f'''
+ (module "Pogopin:AutogeneratedPogopinFootprint" (layer "F.Cu") (tedit {TEDIT_BASE + i:08X}) (tstamp {PIN_TS_BASE + i:08X})
+ (at {x} {y})
+ (descr "Pogo pin {i}")
+ (tags "test point plated hole")
+ (path "/{PATH_BASE + i:08X}")
+ (attr virtual)
+ (fp_text reference "TP{i}" (at 0 -{pad_dia/2 + 1}) (layer "F.SilkS")
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text value "pogo pin {i}" (at 0 {pad_dia/2 + 1}) (layer "F.Fab")
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_text user "%R" (at 0 -{pad_dia/2 + 1}) (layer "F.Fab")
+ (effects (font (size 1 1) (thickness 0.15)))
+ )
+ (fp_circle (center 0 0) (end {pad_dia} 0) (layer "F.CrtYd") (width 0.05))
+ (fp_circle (center 0 0) (end 0 -{pad_dia}) (layer "F.SilkS") (width 0.12))
+ (pad "1" thru_hole circle (at 0 0) (size {pad_dia} {pad_dia}) (drill {hole_dia}) (layers *.Cu *.Mask)
+ (net {i+1} "pogo{i}"))
+ )'''
+ module_defs.append(mod)
+
+ edge_cuts = [ f'(gr_line (start {x1} {y1}) (end {x2} {y2}) (layer "Edge.Cuts") (width 0.05))'
+ for (x1, y1), (x2, y2) in outline ]
+
+ net_defs = [ f'(net {i+1} "pogo{i}")' for i, _pin in enumerate(pins) ]
+ net_class_defs = [ f'(add_net "pogo{i}")' for i, _pin in enumerate(pins) ]
+ return pcb_templ.format(
+ net_defs='\n'.join(net_defs),
+ net_class_defs='\n'.join(net_class_defs),
+ module_defs='\n'.join(module_defs),
+ edge_cuts='\n'.join(edge_cuts))
+
+def inkscape_query_all(filename):
+ proc = subprocess.run([ os.environ.get('INKSCAPE', 'inkscape'), filename, '--query-all'], capture_output=True)
+ proc.check_returncode()
+ data = [ line.split(',') for line in proc.stdout.decode().splitlines() ]
+ return { id: (float(x), float(y), float(w), float(h)) for id, x, y, w, h in data }
+
+SVG_NS = {
+ 'svg': 'http://www.w3.org/2000/svg',
+ 'inkscape': 'http://www.inkscape.org/namespaces/inkscape',
+ 'sodipodi': 'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd'
+}
+
+def svg_find_elements(doc, tag, layer=None):
+ for i, g in enumerate(doc.findall('svg:g', SVG_NS)):
+ if g.attrib.get(f'{{{SVG_NS["inkscape"]}}}groupmode') != 'layer':
+ continue
+
+ label = g.attrib.get(f'{{{SVG_NS["inkscape"]}}}label', '')
+ if not layer or label == layer:
+ yield from g.iter(tag)
+
+# def svg_get_scale(doc):
+# w = doc.attrib['width']
+# h = doc.attrib['height']
+#
+# if not w.endswith('mm') and h.endswith('mm'):
+# raise ValueError('Document dimensions in SVG must be set to millimeters')
+#
+# w, h = float(w[:-2]), float(h[:-2])
+# _x, _y, vb_w, vb_h = map(float, doc.attrib['viewBox'].split())
+# scale_x, scale_y = vb_w / w, vb_h / h
+# assert abs(1 - scale_x/scale_y) < 0.001
+# return scale_x
+
+def svg_get_viewbox_mm(doc):
+ w = doc.attrib['width']
+ h = doc.attrib['height']
+
+ if not w.endswith('mm') and h.endswith('mm'):
+ raise ValueError('Document dimensions in SVG must be set to millimeters')
+
+ w, h = float(w[:-2]), float(h[:-2])
+ x, y, vb_w, vb_h = map(float, doc.attrib['viewBox'].split())
+ scale_x, scale_y = vb_w / w, vb_h / h
+ return x/scale_x, y/scale_y, w, h
+
+if __name__ == '__main__':
+ import argparse
+ parser = argparse.ArgumentParser()
+ parser.add_argument('svg', metavar='pogo_map.svg', help='Input inkscape SVG pogo pin map (use provided template!)')
+ parser.add_argument('outline', metavar='outline.dxf', help='Board outline DXF generated by OpenSCAD')
+ parser.add_argument('output', default='kicad', help='Output directory/project name and path')
+ parser.add_argument('-y', '--yspace', type=int, default=200, help='Schematic pin Y spacing in mil (default: 200)')
+ parser.add_argument('-a', '--annular', type=float, default=0.5, help='Pogo pin annular ring width in mm (default: 0.5)')
+ parser.add_argument('-l', '--svg-layer', type=str, default='Test Points', help='Name of SVG layer containing pogo pins')
+ parser.add_argument('-n', '--name', default='jig', help='Output KiCAD project name')
+ args = parser.parse_args()
+
+ if not path.exists(args.output):
+ os.mkdir(args.output)
+
+ if not path.isdir(args.output):
+ raise SystemError(f'Output path "{args.output}" is not a directory')
+
+ with open(args.svg, 'r') as f:
+ doc = xe.fromstring(f.read())
+ pogo_circle_ids = [ circle.attrib['id'] for circle in svg_find_elements(doc, f'{{{SVG_NS["svg"]}}}circle', args.svg_layer) ]
+ # scale = svg_get_scale(doc)
+ page_x, page_y, page_w, page_h = svg_get_viewbox_mm(doc)
+ MM_PER_IN = 25.4
+ SVG_DEF_DPI = 96
+ px_to_mm = lambda px: px/SVG_DEF_DPI * MM_PER_IN
+ query = inkscape_query_all(args.svg)
+ dims = [ query[id] for id in pogo_circle_ids ]
+ assert all( abs(1 - w/h) < 0.001 for _x, _y, w, h in dims )
+ print('origin:', page_x, page_y)
+ print('dims:', page_w, page_h)
+ pins = [ (
+ (page_x + px_to_mm(x) + px_to_mm(w)/2,
+ page_y - page_h + px_to_mm(y) + px_to_mm(w)/2),
+ px_to_mm(w)) for x, y, w, h in dims ]
+
+ doc = ezdxf.readfile(args.outline)
+ outline = []
+ for line in doc.modelspace().query('LINE'):
+ (x1, y1, _z1), (x2, y2, _z2) = line.dxf.start, line.dxf.end
+ outline.append(((x1, -y1), (x2, -y2)))
+
+ with open(path.join(args.output, f'{args.name}.sch'), 'w', encoding='utf8') as sch:
+ sch.write(sch_template(f'{args.name} generated schematic (PogoJig v{__version__})', len(pins), yspace=args.yspace))
+
+ with open(path.join(args.output, f'{args.name}.kicad_pcb'), 'w', encoding='utf8') as pcb:
+ pcb.write(pcb_template(outline, pins, annular=args.annular))
+
+ with open(path.join(args.output, f'{args.name}.pro'), 'w', encoding='utf8') as f:
+ f.write(pkgutil.get_data('pogojig.kicad', 'kicad.pro').decode('utf8'))
+
+ with open(path.join(args.output, f'{args.name}-cache.lib'), 'w', encoding='utf8') as f:
+ f.write(pkgutil.get_data('pogojig.kicad', 'kicad-cache.lib').decode('utf8'))
+
diff --git a/renderer/support/inkscape_exporter.py b/renderer/support/inkscape_exporter.py
new file mode 100755
index 0000000..25fcabe
--- /dev/null
+++ b/renderer/support/inkscape_exporter.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+
+import os
+import shutil
+import tempfile
+
+from pogojig.inkscape import effect, inkscape
+
+from xvfbwrapper import Xvfb
+
+
+def _unfuck_svg_document(temp_svg_path):
+ """
+ Unfucks an SVG document so is can be processed by the better_dxf_export
+ plugin (or what's left of it).
+ """
+ command_line = inkscape.InkscapeCommandLine(temp_svg_path)
+ layers = command_line.layers
+
+ command_line.apply_to_document('LayerUnlockAll', 'LayerShowAll')
+
+ layer_copies = []
+
+ for i in layers:
+ layer_copy = command_line.duplicate_layer(i)
+ layer_copies.append(layer_copy)
+
+ command_line.apply_to_layer_content(layer_copy, 'ObjectToPath')
+ command_line.apply_to_layer_content(layer_copy, 'SelectionUnGroup')
+
+ if not i.use_paths:
+ command_line.apply_to_layer_content(layer_copy, 'StrokeToPath')
+ command_line.apply_to_layer_content(layer_copy, 'SelectionUnion')
+
+ for original, copy in zip(layers, layer_copies):
+ command_line.clear_layer(original)
+ command_line.move_content(copy, original)
+ command_line.delete_layer(copy)
+
+ command_line.apply_to_document('FileSave', 'FileClose', 'FileQuit')
+ command_line.run()
+
+
+if __name__ == '__main__':
+ import argparse
+ parser = argparse.ArgumentParser()
+ parser.add_argument('infile', metavar='input.svg', help='Inkscape SVG input file')
+ parser.add_argument('outfile', metavar='output.dxf', help='DXF output file')
+ args = parser.parse_args()
+
+ with Xvfb():
+ effect.ExportEffect.check_document_units(args.infile)
+
+ with tempfile.TemporaryDirectory() as tmpdir:
+ temp_svg_path = os.path.join(tmpdir, os.path.basename(args.infile))
+ shutil.copyfile(args.infile, temp_svg_path)
+
+ _unfuck_svg_document(temp_svg_path)
+
+ export_effect = effect.ExportEffect()
+ export_effect.affect(args=[temp_svg_path], output=False)
+
+ with open(args.outfile, 'w') as f:
+ export_effect.write_dxf(f)
+
diff --git a/renderer/support/inkscape_svg_filter_layers.py b/renderer/support/inkscape_svg_filter_layers.py
new file mode 100755
index 0000000..9301e1e
--- /dev/null
+++ b/renderer/support/inkscape_svg_filter_layers.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+
+import xml.etree.ElementTree as xe
+import argparse
+import re
+
+
+if __name__ != '__main__':
+ raise SystemError('Not running as shell script')
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument('infile', metavar='input.svg', type=argparse.FileType('r'))
+parser.add_argument('outfile', metavar='output.svg', type=argparse.FileType('wb'))
+parser.add_argument('-n', '--name', nargs='+', default=[], help='Remove layers with this exact name (case-insensitive)')
+parser.add_argument('-r', '--regex', nargs='+', default=[], help='Remove layers with names matching this regex')
+parser.add_argument('-i', '--invisible', action='store_true', help='Remove hidden (invisible) layers')
+parser.add_argument('-o', '--only', action='store_true', help='Invert logic, i.e. keep matched layers and discard others')
+parser.add_argument('-d', '--strip-defs', action='store_true', help='Also strip any <defs> tags (off by default)')
+args = parser.parse_args()
+
+doc = xe.fromstring(args.infile.read())
+ns = {
+ 'svg': 'http://www.w3.org/2000/svg',
+ 'inkscape': 'http://www.inkscape.org/namespaces/inkscape',
+ 'sodipodi': 'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd'
+}
+
+if args.strip_defs:
+ for elem in doc.findall('svg:defs', ns):
+ doc.remove(elem)
+
+for i, g in enumerate(doc.findall('svg:g', ns)):
+ if g.attrib.get(f'{{{ns["inkscape"]}}}groupmode') != 'layer':
+ continue
+
+ label = g.attrib.get(f'{{{ns["inkscape"]}}}label', '')
+ match = (
+ any(label == name for name in args.name) or
+ any(re.match(regex, label) for regex in args.regex) or
+ ('display:none' in g.attrib.get('style', '') and args.hidden)
+ )
+ print(f'Layer {i} "{label}": {"match" if match else "not found"}', end='')
+
+ if match != args.only:
+ print(', removing.')
+ doc.remove(g)
+ else:
+ print()
+
+args.outfile.write(xe.tostring(doc))
diff --git a/renderer/support/pogojig/__init__.py b/renderer/support/pogojig/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/renderer/support/pogojig/__init__.py
diff --git a/renderer/support/pogojig/inkscape/__init__.py b/renderer/support/pogojig/inkscape/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/renderer/support/pogojig/inkscape/__init__.py
diff --git a/renderer/support/pogojig/inkscape/bezmisc.py b/renderer/support/pogojig/inkscape/bezmisc.py
new file mode 100644
index 0000000..68a338d
--- /dev/null
+++ b/renderer/support/pogojig/inkscape/bezmisc.py
@@ -0,0 +1,287 @@
+#!/usr/bin/env python
+'''
+Copyright (C) 2010 Nick Drobchenko, nick@cnc-club.ru
+Copyright (C) 2005 Aaron Spike, aaron@ekips.org
+
+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 math, cmath
+
+def rootWrapper(a,b,c,d):
+ if a:
+ # Monics formula see http://en.wikipedia.org/wiki/Cubic_function#Monic_formula_of_roots
+ a,b,c = (b/a, c/a, d/a)
+ m = 2.0*a**3 - 9.0*a*b + 27.0*c
+ k = a**2 - 3.0*b
+ n = m**2 - 4.0*k**3
+ w1 = -.5 + .5*cmath.sqrt(-3.0)
+ w2 = -.5 - .5*cmath.sqrt(-3.0)
+ if n < 0:
+ m1 = pow(complex((m+cmath.sqrt(n))/2),1./3)
+ n1 = pow(complex((m-cmath.sqrt(n))/2),1./3)
+ else:
+ if m+math.sqrt(n) < 0:
+ m1 = -pow(-(m+math.sqrt(n))/2,1./3)
+ else:
+ m1 = pow((m+math.sqrt(n))/2,1./3)
+ if m-math.sqrt(n) < 0:
+ n1 = -pow(-(m-math.sqrt(n))/2,1./3)
+ else:
+ n1 = pow((m-math.sqrt(n))/2,1./3)
+ x1 = -1./3 * (a + m1 + n1)
+ x2 = -1./3 * (a + w1*m1 + w2*n1)
+ x3 = -1./3 * (a + w2*m1 + w1*n1)
+ return (x1,x2,x3)
+ elif b:
+ det=c**2.0-4.0*b*d
+ if det:
+ return (-c+cmath.sqrt(det))/(2.0*b),(-c-cmath.sqrt(det))/(2.0*b)
+ else:
+ return -c/(2.0*b),
+ elif c:
+ return 1.0*(-d/c),
+ return ()
+
+def bezierparameterize(points):
+ #parametric bezier
+ ((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = points
+ x0=bx0
+ y0=by0
+ cx=3*(bx1-x0)
+ bx=3*(bx2-bx1)-cx
+ ax=bx3-x0-cx-bx
+ cy=3*(by1-y0)
+ by=3*(by2-by1)-cy
+ ay=by3-y0-cy-by
+
+ return ax,ay,bx,by,cx,cy,x0,y0
+ #ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
+
+def linebezierintersect(line, bezier):
+ #parametric line
+ ((lx1,ly1),(lx2,ly2)) = line
+ ((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = bezier
+ dd=lx1
+ cc=lx2-lx1
+ bb=ly1
+ aa=ly2-ly1
+
+ if aa:
+ coef1=cc/aa
+ coef2=1
+ else:
+ coef1=1
+ coef2=aa/cc
+
+ ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
+ #cubic intersection coefficients
+ a=coef1*ay-coef2*ax
+ b=coef1*by-coef2*bx
+ c=coef1*cy-coef2*cx
+ d=coef1*(y0-bb)-coef2*(x0-dd)
+
+ roots = rootWrapper(a,b,c,d)
+ retval = []
+ for i in roots:
+ if type(i) is complex and i.imag==0:
+ i = i.real
+ if type(i) is not complex and 0<=i<=1:
+ retval.append(bezierpointatt(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),i))
+ return retval
+
+def bezierpointatt(xxx_todo_changeme3,t):
+ ((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme3
+ ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
+ x=ax*(t**3)+bx*(t**2)+cx*t+x0
+ y=ay*(t**3)+by*(t**2)+cy*t+y0
+ return x,y
+
+def bezierslopeatt(xxx_todo_changeme4,t):
+ ((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme4
+ ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
+ dx=3*ax*(t**2)+2*bx*t+cx
+ dy=3*ay*(t**2)+2*by*t+cy
+ return dx,dy
+
+def beziertatslope(xxx_todo_changeme5, xxx_todo_changeme6):
+ ((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme5
+ (dy,dx) = xxx_todo_changeme6
+ ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
+ #quadratic coefficents of slope formula
+ if dx:
+ slope = 1.0*(dy/dx)
+ a=3*ay-3*ax*slope
+ b=2*by-2*bx*slope
+ c=cy-cx*slope
+ elif dy:
+ slope = 1.0*(dx/dy)
+ a=3*ax-3*ay*slope
+ b=2*bx-2*by*slope
+ c=cx-cy*slope
+ else:
+ return []
+
+ roots = rootWrapper(0,a,b,c)
+ retval = []
+ for i in roots:
+ if type(i) is complex and i.imag==0:
+ i = i.real
+ if type(i) is not complex and 0<=i<=1:
+ retval.append(i)
+ return retval
+
+def tpoint(xxx_todo_changeme7, xxx_todo_changeme8,t):
+ (x1,y1) = xxx_todo_changeme7
+ (x2,y2) = xxx_todo_changeme8
+ return x1+t*(x2-x1),y1+t*(y2-y1)
+def beziersplitatt(xxx_todo_changeme9,t):
+ ((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme9
+ m1=tpoint((bx0,by0),(bx1,by1),t)
+ m2=tpoint((bx1,by1),(bx2,by2),t)
+ m3=tpoint((bx2,by2),(bx3,by3),t)
+ m4=tpoint(m1,m2,t)
+ m5=tpoint(m2,m3,t)
+ m=tpoint(m4,m5,t)
+
+ return ((bx0,by0),m1,m4,m),(m,m5,m3,(bx3,by3))
+
+'''
+Approximating the arc length of a bezier curve
+according to <http://www.cit.gu.edu.au/~anthony/info/graphics/bezier.curves>
+
+if:
+ L1 = |P0 P1| +|P1 P2| +|P2 P3|
+ L0 = |P0 P3|
+then:
+ L = 1/2*L0 + 1/2*L1
+ ERR = L1-L0
+ERR approaches 0 as the number of subdivisions (m) increases
+ 2^-4m
+
+Reference:
+Jens Gravesen <gravesen@mat.dth.dk>
+"Adaptive subdivision and the length of Bezier curves"
+mat-report no. 1992-10, Mathematical Institute, The Technical
+University of Denmark.
+'''
+def pointdistance(xxx_todo_changeme10, xxx_todo_changeme11):
+ (x1,y1) = xxx_todo_changeme10
+ (x2,y2) = xxx_todo_changeme11
+ return math.sqrt(((x2 - x1) ** 2) + ((y2 - y1) ** 2))
+def Gravesen_addifclose(b, len, error = 0.001):
+ box = 0
+ for i in range(1,4):
+ box += pointdistance(b[i-1], b[i])
+ chord = pointdistance(b[0], b[3])
+ if (box - chord) > error:
+ first, second = beziersplitatt(b, 0.5)
+ Gravesen_addifclose(first, len, error)
+ Gravesen_addifclose(second, len, error)
+ else:
+ len[0] += (box / 2.0) + (chord / 2.0)
+def bezierlengthGravesen(b, error = 0.001):
+ len = [0]
+ Gravesen_addifclose(b, len, error)
+ return len[0]
+
+# balf = Bezier Arc Length Function
+balfax,balfbx,balfcx,balfay,balfby,balfcy = 0,0,0,0,0,0
+def balf(t):
+ retval = (balfax*(t**2) + balfbx*t + balfcx)**2 + (balfay*(t**2) + balfby*t + balfcy)**2
+ return math.sqrt(retval)
+
+def Simpson(f, a, b, n_limit, tolerance):
+ n = 2
+ multiplier = (b - a)/6.0
+ endsum = f(a) + f(b)
+ interval = (b - a)/2.0
+ asum = 0.0
+ bsum = f(a + interval)
+ est1 = multiplier * (endsum + (2.0 * asum) + (4.0 * bsum))
+ est0 = 2.0 * est1
+ #print multiplier, endsum, interval, asum, bsum, est1, est0
+ while n < n_limit and abs(est1 - est0) > tolerance:
+ n *= 2
+ multiplier /= 2.0
+ interval /= 2.0
+ asum += bsum
+ bsum = 0.0
+ est0 = est1
+ for i in range(1, n, 2):
+ bsum += f(a + (i * interval))
+ est1 = multiplier * (endsum + (2.0 * asum) + (4.0 * bsum))
+ #print multiplier, endsum, interval, asum, bsum, est1, est0
+ return est1
+
+def bezierlengthSimpson(xxx_todo_changeme12, tolerance = 0.001):
+ ((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme12
+ global balfax,balfbx,balfcx,balfay,balfby,balfcy
+ ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
+ balfax,balfbx,balfcx,balfay,balfby,balfcy = 3*ax,2*bx,cx,3*ay,2*by,cy
+ return Simpson(balf, 0.0, 1.0, 4096, tolerance)
+
+def beziertatlength(xxx_todo_changeme13, l = 0.5, tolerance = 0.001):
+ ((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme13
+ global balfax,balfbx,balfcx,balfay,balfby,balfcy
+ ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
+ balfax,balfbx,balfcx,balfay,balfby,balfcy = 3*ax,2*bx,cx,3*ay,2*by,cy
+ t = 1.0
+ tdiv = t
+ curlen = Simpson(balf, 0.0, t, 4096, tolerance)
+ targetlen = l * curlen
+ diff = curlen - targetlen
+ while abs(diff) > tolerance:
+ tdiv /= 2.0
+ if diff < 0:
+ t += tdiv
+ else:
+ t -= tdiv
+ curlen = Simpson(balf, 0.0, t, 4096, tolerance)
+ diff = curlen - targetlen
+ return t
+
+#default bezier length method
+bezierlength = bezierlengthSimpson
+
+if __name__ == '__main__':
+ #print linebezierintersect(((,),(,)),((,),(,),(,),(,)))
+ #print linebezierintersect(((0,1),(0,-1)),((-1,0),(-.5,0),(.5,0),(1,0)))
+ tol = 0.00000001
+ curves = [((0,0),(1,5),(4,5),(5,5)),
+ ((0,0),(0,0),(5,0),(10,0)),
+ ((0,0),(0,0),(5,1),(10,0)),
+ ((-10,0),(0,0),(10,0),(10,10)),
+ ((15,10),(0,0),(10,0),(-5,10))]
+ '''
+ for curve in curves:
+ timing.start()
+ g = bezierlengthGravesen(curve,tol)
+ timing.finish()
+ gt = timing.micro()
+
+ timing.start()
+ s = bezierlengthSimpson(curve,tol)
+ timing.finish()
+ st = timing.micro()
+
+ print g, gt
+ print s, st
+ '''
+ for curve in curves:
+ print(beziertatlength(curve,0.5))
+
+
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99
diff --git a/renderer/support/pogojig/inkscape/cspsubdiv.py b/renderer/support/pogojig/inkscape/cspsubdiv.py
new file mode 100644
index 0000000..72da473
--- /dev/null
+++ b/renderer/support/pogojig/inkscape/cspsubdiv.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+from .bezmisc import *
+from .ffgeom import *
+
+def maxdist(points):
+ ((p0x,p0y),(p1x,p1y),(p2x,p2y),(p3x,p3y)) = points
+ p0 = Point(p0x,p0y)
+ p1 = Point(p1x,p1y)
+ p2 = Point(p2x,p2y)
+ p3 = Point(p3x,p3y)
+
+ s1 = Segment(p0,p3)
+ return max(s1.distanceToPoint(p1),s1.distanceToPoint(p2))
+
+
+def cspsubdiv(csp,flat):
+ for sp in csp:
+ subdiv(sp,flat)
+
+def subdiv(sp,flat,i=1):
+ while i < len(sp):
+ p0 = sp[i-1][1]
+ p1 = sp[i-1][2]
+ p2 = sp[i][0]
+ p3 = sp[i][1]
+
+ b = (p0,p1,p2,p3)
+ m = maxdist(b)
+ if m <= flat:
+ i += 1
+ else:
+ one, two = beziersplitatt(b,0.5)
+ sp[i-1][2] = one[1]
+ sp[i][0] = two[2]
+ p = [one[2],one[3],two[1]]
+ sp[i:1] = [p]
+
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99
diff --git a/renderer/support/pogojig/inkscape/cubicsuperpath.py b/renderer/support/pogojig/inkscape/cubicsuperpath.py
new file mode 100644
index 0000000..a594660
--- /dev/null
+++ b/renderer/support/pogojig/inkscape/cubicsuperpath.py
@@ -0,0 +1,174 @@
+#!/usr/bin/env python
+"""
+cubicsuperpath.py
+
+Copyright (C) 2005 Aaron Spike, aaron@ekips.org
+
+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
+
+"""
+from . import simplepath
+from math import *
+
+def matprod(mlist):
+ prod=mlist[0]
+ for m in mlist[1:]:
+ a00=prod[0][0]*m[0][0]+prod[0][1]*m[1][0]
+ a01=prod[0][0]*m[0][1]+prod[0][1]*m[1][1]
+ a10=prod[1][0]*m[0][0]+prod[1][1]*m[1][0]
+ a11=prod[1][0]*m[0][1]+prod[1][1]*m[1][1]
+ prod=[[a00,a01],[a10,a11]]
+ return prod
+def rotmat(teta):
+ return [[cos(teta),-sin(teta)],[sin(teta),cos(teta)]]
+def applymat(mat, pt):
+ x=mat[0][0]*pt[0]+mat[0][1]*pt[1]
+ y=mat[1][0]*pt[0]+mat[1][1]*pt[1]
+ pt[0]=x
+ pt[1]=y
+def norm(pt):
+ return sqrt(pt[0]*pt[0]+pt[1]*pt[1])
+
+def ArcToPath(p1,params):
+ A=p1[:]
+ rx,ry,teta,longflag,sweepflag,x2,y2=params[:]
+ teta = teta*pi/180.0
+ B=[x2,y2]
+ if rx==0 or ry==0 or A==B:
+ return([[A[:],A[:],A[:]],[B[:],B[:],B[:]]])
+ mat=matprod((rotmat(teta),[[1/rx,0],[0,1/ry]],rotmat(-teta)))
+ applymat(mat, A)
+ applymat(mat, B)
+ k=[-(B[1]-A[1]),B[0]-A[0]]
+ d=k[0]*k[0]+k[1]*k[1]
+ k[0]/=sqrt(d)
+ k[1]/=sqrt(d)
+ d=sqrt(max(0,1-d/4))
+ if longflag==sweepflag:
+ d*=-1
+ O=[(B[0]+A[0])/2+d*k[0],(B[1]+A[1])/2+d*k[1]]
+ OA=[A[0]-O[0],A[1]-O[1]]
+ OB=[B[0]-O[0],B[1]-O[1]]
+ start=acos(OA[0]/norm(OA))
+ if OA[1]<0:
+ start*=-1
+ end=acos(OB[0]/norm(OB))
+ if OB[1]<0:
+ end*=-1
+
+ if sweepflag and start>end:
+ end +=2*pi
+ if (not sweepflag) and start<end:
+ end -=2*pi
+
+ NbSectors=int(abs(start-end)*2/pi)+1
+ dTeta=(end-start)/NbSectors
+ #v=dTeta*2/pi*0.552
+ #v=dTeta*2/pi*4*(sqrt(2)-1)/3
+ v = 4*tan(dTeta/4)/3
+ #if not sweepflag:
+ # v*=-1
+ p=[]
+ for i in range(0,NbSectors+1,1):
+ angle=start+i*dTeta
+ v1=[O[0]+cos(angle)-(-v)*sin(angle),O[1]+sin(angle)+(-v)*cos(angle)]
+ pt=[O[0]+cos(angle) ,O[1]+sin(angle) ]
+ v2=[O[0]+cos(angle)- v *sin(angle),O[1]+sin(angle)+ v *cos(angle)]
+ p.append([v1,pt,v2])
+
+ mat=matprod((rotmat(teta),[[rx,0],[0,ry]],rotmat(-teta)))
+ for pts in p:
+ applymat(mat, pts[0])
+ applymat(mat, pts[1])
+ applymat(mat, pts[2])
+
+ # Use exact coordinates for the endpoints. This prevents small drifts when relative coordinates are used and guarantees that a path ending with a z command closes perfectly.
+ p[0][0] = p1[:]
+ p[0][1] = p1[:]
+ p[-1][1] = [x2, y2]
+ p[-1][2] = [x2, y2]
+
+ return p
+
+def CubicSuperPath(simplepath):
+ csp = []
+ subpath = -1
+ subpathstart = []
+ last = []
+ lastctrl = []
+ for s in simplepath:
+ cmd, params = s
+ if cmd == 'M':
+ if last:
+ csp[subpath].append([lastctrl[:],last[:],last[:]])
+ subpath += 1
+ csp.append([])
+ subpathstart = params[:]
+ last = params[:]
+ lastctrl = params[:]
+ elif cmd == 'L':
+ csp[subpath].append([lastctrl[:],last[:],last[:]])
+ last = params[:]
+ lastctrl = params[:]
+ elif cmd == 'C':
+ csp[subpath].append([lastctrl[:],last[:],params[:2]])
+ last = params[-2:]
+ lastctrl = params[2:4]
+ elif cmd == 'Q':
+ q0=last[:]
+ q1=params[0:2]
+ q2=params[2:4]
+ x0= q0[0]
+ x1=1./3*q0[0]+2./3*q1[0]
+ x2= 2./3*q1[0]+1./3*q2[0]
+ x3= q2[0]
+ y0= q0[1]
+ y1=1./3*q0[1]+2./3*q1[1]
+ y2= 2./3*q1[1]+1./3*q2[1]
+ y3= q2[1]
+ csp[subpath].append([lastctrl[:],[x0,y0],[x1,y1]])
+ last = [x3,y3]
+ lastctrl = [x2,y2]
+ elif cmd == 'A':
+ arcp=ArcToPath(last[:],params[:])
+ arcp[ 0][0]=lastctrl[:]
+ last=arcp[-1][1]
+ lastctrl = arcp[-1][0]
+ csp[subpath]+=arcp[:-1]
+ elif cmd == 'Z':
+ csp[subpath].append([lastctrl[:],last[:],last[:]])
+ last = subpathstart[:]
+ lastctrl = subpathstart[:]
+ #append final superpoint
+ csp[subpath].append([lastctrl[:],last[:],last[:]])
+ return csp
+
+def unCubicSuperPath(csp):
+ a = []
+ for subpath in csp:
+ if subpath:
+ a.append(['M',subpath[0][1][:]])
+ for i in range(1,len(subpath)):
+ a.append(['C',subpath[i-1][2][:] + subpath[i][0][:] + subpath[i][1][:]])
+ return a
+
+def parsePath(d):
+ return CubicSuperPath(simplepath.parsePath(d))
+
+def formatPath(p):
+ return simplepath.formatPath(unCubicSuperPath(p))
+
+
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99
diff --git a/renderer/support/pogojig/inkscape/dxf_footer.txt b/renderer/support/pogojig/inkscape/dxf_footer.txt
new file mode 100644
index 0000000..a225dd7
--- /dev/null
+++ b/renderer/support/pogojig/inkscape/dxf_footer.txt
@@ -0,0 +1,62 @@
+0
+ENDSEC
+0
+SECTION
+2
+OBJECTS
+0
+DICTIONARY
+5
+C
+330
+0
+100
+AcDbDictionary
+3
+ACAD_GROUP
+350
+D
+3
+ACAD_MLINESTYLE
+350
+17
+0
+DICTIONARY
+5
+D
+330
+C
+100
+AcDbDictionary
+0
+DICTIONARY
+5
+1A
+330
+C
+100
+AcDbDictionary
+0
+DICTIONARY
+5
+17
+330
+C
+100
+AcDbDictionary
+3
+STANDARD
+350
+18
+0
+DICTIONARY
+5
+19
+330
+C
+100
+AcDbDictionary
+0
+ENDSEC
+0
+EOF
diff --git a/renderer/support/pogojig/inkscape/dxf_header.txt b/renderer/support/pogojig/inkscape/dxf_header.txt
new file mode 100644
index 0000000..341cb1b
--- /dev/null
+++ b/renderer/support/pogojig/inkscape/dxf_header.txt
@@ -0,0 +1,580 @@
+0
+SECTION
+2
+HEADER
+9
+$ACADVER
+1
+AC1014
+9
+$HANDSEED
+5
+FFFF
+0
+ENDSEC
+0
+SECTION
+2
+TABLES
+0
+TABLE
+2
+VPORT
+5
+8
+330
+0
+100
+AcDbSymbolTable
+70
+4
+0
+VPORT
+5
+2E
+330
+8
+100
+AcDbSymbolTableRecord
+100
+AcDbViewportTableRecord
+2
+*ACTIVE
+70
+0
+10
+0.0
+20
+0.0
+11
+1.0
+21
+1.0
+12
+4.25
+22
+5.5
+13
+0.0
+23
+0.0
+14
+10.0
+24
+10.0
+15
+10.0
+25
+10.0
+16
+0.0
+26
+0.0
+36
+1.0
+17
+0.0
+27
+0.0
+37
+0.0
+40
+11
+41
+1.24
+42
+50.0
+43
+0.0
+44
+0.0
+50
+0.0
+51
+0.0
+71
+0
+72
+100
+73
+1
+74
+3
+75
+0
+76
+0
+77
+0
+78
+0
+0
+ENDTAB
+0
+TABLE
+2
+LTYPE
+5
+5
+330
+0
+100
+AcDbSymbolTable
+70
+1
+0
+LTYPE
+5
+14
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+2
+BYBLOCK
+70
+0
+3
+
+72
+65
+73
+0
+40
+0.0
+0
+LTYPE
+5
+15
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+2
+BYLAYER
+70
+0
+3
+
+72
+65
+73
+0
+40
+0.0
+0
+LTYPE
+5
+16
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+2
+CONTINUOUS
+70
+0
+3
+Solid line
+72
+65
+73
+0
+40
+0.0
+0
+ENDTAB
+0
+TABLE
+2
+LAYER
+5
+2
+330
+0
+100
+AcDbSymbolTable
+70
+1
+0
+LAYER
+5
+10
+330
+2
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+2
+0
+70
+0
+62
+7
+6
+CONTINUOUS
+0
+ENDTAB
+0
+TABLE
+2
+STYLE
+5
+3
+330
+0
+100
+AcDbSymbolTable
+70
+1
+0
+STYLE
+5
+11
+330
+3
+100
+AcDbSymbolTableRecord
+100
+AcDbTextStyleTableRecord
+2
+STANDARD
+70
+0
+40
+0.0
+41
+1.0
+50
+0.0
+71
+0
+42
+2.5
+3
+txt
+4
+
+0
+ENDTAB
+0
+TABLE
+2
+VIEW
+5
+6
+330
+0
+100
+AcDbSymbolTable
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+UCS
+5
+7
+330
+0
+100
+AcDbSymbolTable
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+APPID
+5
+9
+330
+0
+100
+AcDbSymbolTable
+70
+2
+0
+APPID
+5
+12
+330
+9
+100
+AcDbSymbolTableRecord
+100
+AcDbRegAppTableRecord
+2
+ACAD
+70
+0
+0
+ENDTAB
+0
+TABLE
+2
+DIMSTYLE
+5
+A
+330
+0
+100
+AcDbSymbolTable
+70
+1
+0
+DIMSTYLE
+105
+27
+330
+A
+100
+AcDbSymbolTableRecord
+100
+AcDbDimStyleTableRecord
+2
+ISO-25
+70
+0
+3
+
+4
+
+5
+
+6
+
+7
+
+40
+1.0
+41
+2.5
+42
+0.625
+43
+3.75
+44
+1.25
+45
+0.0
+46
+0.0
+47
+0.0
+48
+0.0
+140
+2.5
+141
+2.5
+142
+0.0
+143
+0.03937007874016
+144
+1.0
+145
+0.0
+146
+1.0
+147
+0.625
+71
+0
+72
+0
+73
+0
+74
+0
+75
+0
+76
+0
+77
+1
+78
+8
+170
+0
+171
+3
+172
+1
+173
+0
+174
+0
+175
+0
+176
+0
+177
+0
+178
+0
+270
+2
+271
+2
+272
+2
+273
+2
+274
+3
+340
+11
+275
+0
+280
+0
+281
+0
+282
+0
+283
+0
+284
+8
+285
+0
+286
+0
+287
+3
+288
+0
+0
+ENDTAB
+0
+TABLE
+2
+BLOCK_RECORD
+5
+1
+330
+0
+100
+AcDbSymbolTable
+70
+1
+0
+BLOCK_RECORD
+5
+1F
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+2
+*MODEL_SPACE
+0
+BLOCK_RECORD
+5
+1B
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+2
+*PAPER_SPACE
+0
+ENDTAB
+0
+ENDSEC
+0
+SECTION
+2
+BLOCKS
+0
+BLOCK
+5
+20
+330
+1F
+100
+AcDbEntity
+8
+0
+100
+AcDbBlockBegin
+2
+*MODEL_SPACE
+70
+0
+10
+0.0
+20
+0.0
+30
+0.0
+3
+*MODEL_SPACE
+1
+
+0
+ENDBLK
+5
+21
+330
+1F
+100
+AcDbEntity
+8
+0
+100
+AcDbBlockEnd
+0
+BLOCK
+5
+1C
+330
+1B
+100
+AcDbEntity
+67
+1
+8
+0
+100
+AcDbBlockBegin
+2
+*PAPER_SPACE
+1
+
+0
+ENDBLK
+5
+1D
+330
+1B
+100
+AcDbEntity
+67
+1
+8
+0
+100
+AcDbBlockEnd
+0
+ENDSEC
+0
+SECTION
+2
+ENTITIES
diff --git a/renderer/support/pogojig/inkscape/effect.py b/renderer/support/pogojig/inkscape/effect.py
new file mode 100644
index 0000000..89824e9
--- /dev/null
+++ b/renderer/support/pogojig/inkscape/effect.py
@@ -0,0 +1,279 @@
+"""
+Based on code from Aaron Spike. See
+http://www.bobcookdev.com/inkscape/inkscape-dxf.html
+"""
+
+import collections
+import itertools
+import os
+import pkgutil
+import re
+from lxml import etree
+
+from . import inkex, simpletransform, cubicsuperpath, cspsubdiv, inkscape
+
+
+def _get_unit_factors_map():
+ # Fluctuates somewhat between Inkscape releases _and_ between SVG version.
+ pixels_per_inch = 96.
+ pixels_per_mm = pixels_per_inch / 25.4
+
+ return {
+ 'px': 1.0,
+ 'mm': pixels_per_mm,
+ 'cm': pixels_per_mm * 10,
+ 'm': pixels_per_mm * 1e3,
+ 'km': pixels_per_mm * 1e6,
+ 'pt': pixels_per_inch / 72,
+ 'pc': pixels_per_inch / 6,
+ 'in': pixels_per_inch,
+ 'ft': pixels_per_inch * 12,
+ 'yd': pixels_per_inch * 36}
+
+
+class ExportEffect(inkex.Effect):
+ _unit_factors = _get_unit_factors_map()
+ _asymptote_all_paths_name = 'all'
+
+ def __init__(self):
+ inkex.Effect.__init__(self)
+
+ self._flatness = float(os.environ.get('INKSCAPE_DXF_FLATNESS', 0.1))
+
+ self._layers = None
+ self._paths = None
+
+ def _get_document_scale(self):
+ """
+ Return scaling factor applied to the document because of a viewBox
+ setting. This currently ignores any setting of a preserveAspectRatio
+ attribute (like Inkscape).
+ """
+ document_height = self._get_height()
+ view_box = self._get_view_box()
+
+ if view_box is None or document_height is None:
+ return 1
+ else:
+ _, _, _, view_box_height = view_box
+
+ return document_height / view_box_height
+
+ def _get_document_height(self):
+ """
+ Get the height of the document in pixels in the document coordinate
+ system as it is interpreted by Inkscape.
+ """
+ view_box = self._get_view_box()
+ document_height = self._get_height()
+
+ if view_box is not None:
+ _, _, _, view_box_height = view_box
+
+ return view_box_height
+ elif document_height is not None:
+ return document_height
+ else:
+ return 0
+
+ def _get_height(self):
+ height_attr = self.document.getroot().get('height')
+
+ if height_attr is None:
+ return None
+ else:
+ return self._measure_to_pixels(height_attr)
+
+ def _get_view_box(self):
+ view_box_attr = self.document.getroot().get('viewBox')
+
+ if view_box_attr is None:
+ return None
+ else:
+ return [float(i) for i in view_box_attr.split()]
+
+ def _get_shape_paths(self, node, transform):
+ shape = cubicsuperpath.parsePath(node.get('d'))
+
+ transform = simpletransform.composeTransform(
+ transform,
+ simpletransform.composeParents(node, [[1, 0, 0], [0, 1, 0]]))
+
+ simpletransform.applyTransformToPath(transform, shape)
+
+ def iter_paths():
+ for path in shape:
+ cspsubdiv.subdiv(path, self._flatness)
+
+ # path contains two control point coordinates and the actual
+ # coordinates per point.
+ yield [i for _, i, _ in path]
+
+ return list(iter_paths())
+
+ def effect(self):
+ document_height = self._get_document_height()
+ document_scale = self._get_document_scale()
+
+ transform = simpletransform.composeTransform(
+ [[document_scale, 0, 0], [0, document_scale, 0]],
+ [[1, 0, 0], [0, -1, document_height]])
+
+ layers = inkscape.get_inkscape_layers(self.svg_file)
+ layers_by_inkscape_name = {i.inkscape_name: i for i in layers}
+
+ def iter_paths():
+ for node in self.document.getroot().xpath('//svg:path', namespaces=inkex.NSS):
+ layer = layers_by_inkscape_name.get(self._get_inkscape_layer_name(node))
+
+ for path in self._get_shape_paths(node, transform):
+ yield layer, path
+
+ self._layers = layers
+ self._paths = list(iter_paths())
+
+ def write_dxf(self, file):
+ # Scales pixels to millimeters. This is the predominant unit in CAD.
+ unit_factor = self._unit_factors['mm']
+
+ layer_indices = {l: i for i, l in enumerate(self._layers)}
+
+ file.write(pkgutil.get_data(__name__, 'dxf_header.txt').decode('ASCII'))
+
+ def write_instruction(code, value):
+ print(code, file=file)
+ print(value, file=file)
+
+ handle_iter = itertools.count(256)
+
+ for layer, path in self._paths:
+ for (x1, y1), (x2, y2) in zip(path, path[1:]):
+ write_instruction(0, 'LINE')
+
+ if layer is not None:
+ write_instruction(8, layer.export_name)
+ write_instruction(62, layer_indices.get(layer, 0))
+
+ write_instruction(5, '{:x}'.format(next(handle_iter)))
+ write_instruction(100, 'AcDbEntity')
+ write_instruction(100, 'AcDbLine')
+ write_instruction(10, repr(x1 / unit_factor))
+ write_instruction(20, repr(y1 / unit_factor))
+ write_instruction(30, 0.0)
+ write_instruction(11, repr(x2 / unit_factor))
+ write_instruction(21, repr(y2 / unit_factor))
+ write_instruction(31, 0.0)
+
+ file.write(pkgutil.get_data(__name__, 'dxf_footer.txt').decode('ASCII'))
+
+ def write_asy(self, file):
+ def write_line(format, *args):
+ print >> file, format.format(*args) + ';'
+
+ # Scales pixels to points. Asymptote uses PostScript points (1 / 72
+ # inch) by default.
+ unit_factor = self._unit_factors['pt']
+
+ paths_by_layer = collections.defaultdict(list)
+ variable_names = []
+
+ for layer, path in self._paths:
+ paths_by_layer[layer].append(path)
+
+ for layer in self._layers + [None]:
+ paths = paths_by_layer[layer]
+ variable_name = self._asymptote_identifier_from_layer(layer)
+ write_line('path[] {}', variable_name)
+
+ variable_names.append(variable_name)
+
+ for path in paths:
+ point_strs = ['({}, {})'.format(x / unit_factor, y / unit_factor) for x, y in path]
+
+ # Hack. We should determine this from whether Z or z was used
+ # to close the path in the SVG document.
+ if path[0] == path[-1]:
+ point_strs[-1] = 'cycle'
+
+ write_line('{}.push({})', variable_name, ' -- '.join(point_strs))
+
+ if self._asymptote_all_paths_name not in variable_names:
+ write_line('path[] {}', self._asymptote_all_paths_name)
+
+ for i in variable_names:
+ write_line('{}.append({})', self._asymptote_all_paths_name, i)
+
+ @classmethod
+ def _parse_measure(cls, string):
+ value_match = re.match(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)', string)
+ unit_match = re.search('(%s)$' % '|'.join(cls._unit_factors.keys()), string)
+
+ value = float(string[value_match.start():value_match.end()])
+
+ if unit_match:
+ unit = string[unit_match.start():unit_match.end()]
+ else:
+ unit = None
+
+ return value, unit
+
+ @classmethod
+ def _measure_to_pixels(cls, string):
+ """
+ Parse a string containing a measure and return it's value converted
+ to pixels.
+ """
+ value, unit = cls._parse_measure(string)
+
+ return value * cls._get_unit_factor(unit)
+
+ @classmethod
+ def _get_inkscape_layer_name(cls, node):
+ while node is not None:
+ layer = node.get(inkex.addNS('label', 'inkscape'))
+
+ if layer is not None:
+ return layer
+
+ node = node.getparent()
+
+ return None
+
+ @classmethod
+ def _get_unit_factor(cls, unit):
+ if unit is None:
+ return 1
+ else:
+ return cls._unit_factors[unit]
+
+ @classmethod
+ def _asymptote_identifier_from_layer(cls, layer):
+ if layer is None:
+ return '_'
+ else:
+ return re.sub('[^a-zA-Z0-9]', '_', layer.export_name)
+
+ @classmethod
+ def check_document_units(cls, path):
+ with open(path, 'r') as file:
+ p = etree.XMLParser(huge_tree = True)
+ document = etree.parse(file, parser = p)
+
+ height_attr = document.getroot().get('height')
+
+ if height_attr is None:
+ raise ValueError(
+ 'SVG document has no height attribute. See '
+ 'https://github.com/Feuermurmel/openscad-template/wiki/Absolute-Measurements')
+
+ _, height_unit = cls._parse_measure(height_attr)
+
+ if height_unit is None or height_unit == 'px':
+ raise ValueError(
+ 'Height of SVG document is not an absolute measure. See '
+ 'https://github.com/Feuermurmel/openscad-template/wiki/Absolute-Measurements')
+
+ if document.getroot().get('viewBox') is None:
+ raise ValueError(
+ 'SVG document has no viewBox attribute. See '
+ 'https://github.com/Feuermurmel/openscad-template/wiki/Absolute-Measurements')
diff --git a/renderer/support/pogojig/inkscape/ffgeom.py b/renderer/support/pogojig/inkscape/ffgeom.py
new file mode 100644
index 0000000..368a29a
--- /dev/null
+++ b/renderer/support/pogojig/inkscape/ffgeom.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+"""
+ ffgeom.py
+ Copyright (C) 2005 Aaron Cyril Spike, aaron@ekips.org
+
+ This file is part of FretFind 2-D.
+
+ FretFind 2-D 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.
+
+ FretFind 2-D 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 FretFind 2-D; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""
+import math
+
+class Point:
+ precision = 5
+ def __init__(self, x, y):
+ self.__coordinates = {'x' : float(x), 'y' : float(y)}
+ def __getitem__(self, key):
+ return self.__coordinates[key]
+ def __setitem__(self, key, value):
+ self.__coordinates[key] = float(value)
+ def __repr__(self):
+ return '(%s, %s)' % (round(self['x'],self.precision),round(self['y'],self.precision))
+ def copy(self):
+ return Point(self['x'],self['y'])
+ def translate(self, x, y):
+ self['x'] += x
+ self['y'] += y
+ def move(self, x, y):
+ self['x'] = float(x)
+ self['y'] = float(y)
+
+class Segment:
+ def __init__(self, e0, e1):
+ self.__endpoints = [e0, e1]
+ def __getitem__(self, key):
+ return self.__endpoints[key]
+ def __setitem__(self, key, value):
+ self.__endpoints[key] = value
+ def __repr__(self):
+ return repr(self.__endpoints)
+ def copy(self):
+ return Segment(self[0],self[1])
+ def translate(self, x, y):
+ self[0].translate(x,y)
+ self[1].translate(x,y)
+ def move(self,e0,e1):
+ self[0] = e0
+ self[1] = e1
+ def delta_x(self):
+ return self[1]['x'] - self[0]['x']
+ def delta_y(self):
+ return self[1]['y'] - self[0]['y']
+ #alias functions
+ run = delta_x
+ rise = delta_y
+ def slope(self):
+ if self.delta_x() != 0:
+ return self.delta_x() / self.delta_y()
+ return math.nan
+ def intercept(self):
+ if self.delta_x() != 0:
+ return self[1]['y'] - (self[0]['x'] * self.slope())
+ return math.nan
+ def distanceToPoint(self, p):
+ s2 = Segment(self[0],p)
+ c1 = dot(s2,self)
+ if c1 <= 0:
+ return Segment(p,self[0]).length()
+ c2 = dot(self,self)
+ if c2 <= c1:
+ return Segment(p,self[1]).length()
+ return self.perpDistanceToPoint(p)
+ def perpDistanceToPoint(self, p):
+ len = self.length()
+ if len == 0:
+ return math.nan
+ return math.fabs(((self[1]['x'] - self[0]['x']) * (self[0]['y'] - p['y'])) - \
+ ((self[0]['x'] - p['x']) * (self[1]['y'] - self[0]['y']))) / len
+ def angle(self):
+ return math.pi * (math.atan2(self.delta_y(), self.delta_x())) / 180
+ def length(self):
+ return math.sqrt((self.delta_x() ** 2) + (self.delta_y() ** 2))
+ def pointAtLength(self, len):
+ if self.length() == 0: return Point(math.nan, math.nan)
+ ratio = len / self.length()
+ x = self[0]['x'] + (ratio * self.delta_x())
+ y = self[0]['y'] + (ratio * self.delta_y())
+ return Point(x, y)
+ def pointAtRatio(self, ratio):
+ if self.length() == 0: return Point(math.nan, math.nan)
+ x = self[0]['x'] + (ratio * self.delta_x())
+ y = self[0]['y'] + (ratio * self.delta_y())
+ return Point(x, y)
+ def createParallel(self, p):
+ return Segment(Point(p['x'] + self.delta_x(), p['y'] + self.delta_y()), p)
+ def intersect(self, s):
+ return intersectSegments(self, s)
+
+def intersectSegments(s1, s2):
+ x1 = s1[0]['x']
+ x2 = s1[1]['x']
+ x3 = s2[0]['x']
+ x4 = s2[1]['x']
+
+ y1 = s1[0]['y']
+ y2 = s1[1]['y']
+ y3 = s2[0]['y']
+ y4 = s2[1]['y']
+
+ denom = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1))
+ num1 = ((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3))
+ num2 = ((x2 - x1) * (y1 - y3)) - ((y2 - y1) * (x1 - x3))
+
+ num = num1
+
+ if denom != 0:
+ x = x1 + ((num / denom) * (x2 - x1))
+ y = y1 + ((num / denom) * (y2 - y1))
+ return Point(x, y)
+ return Point(math.nan, math.nan)
+
+def dot(s1, s2):
+ return s1.delta_x() * s2.delta_x() + s1.delta_y() * s2.delta_y()
+
+
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99
diff --git a/renderer/support/pogojig/inkscape/inkex.py b/renderer/support/pogojig/inkscape/inkex.py
new file mode 100644
index 0000000..609ffeb
--- /dev/null
+++ b/renderer/support/pogojig/inkscape/inkex.py
@@ -0,0 +1,363 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+inkex.py
+A helper module for creating Inkscape extensions
+
+Copyright (C) 2005,2010 Aaron Spike <aaron@ekips.org> and contributors
+
+Contributors:
+ Aurélio A. Heckert <aurium(a)gmail.com>
+ Bulia Byak <buliabyak@users.sf.net>
+ Nicolas Dufour, nicoduf@yahoo.fr
+ Peter J. R. Moulder <pjrm@users.sourceforge.net>
+
+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 copy
+import gettext
+import optparse
+import os
+import random
+import re
+import sys
+from math import *
+
+from lxml import etree
+
+#a dictionary of all of the xmlns prefixes in a standard inkscape doc
+NSS = {
+u'sodipodi' :u'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd',
+u'cc' :u'http://creativecommons.org/ns#',
+u'ccOLD' :u'http://web.resource.org/cc/',
+u'svg' :u'http://www.w3.org/2000/svg',
+u'dc' :u'http://purl.org/dc/elements/1.1/',
+u'rdf' :u'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+u'inkscape' :u'http://www.inkscape.org/namespaces/inkscape',
+u'xlink' :u'http://www.w3.org/1999/xlink',
+u'xml' :u'http://www.w3.org/XML/1998/namespace'
+}
+
+def localize():
+ domain = 'inkscape'
+ if sys.platform.startswith('win'):
+ import locale
+ current_locale, encoding = locale.getdefaultlocale()
+ os.environ['LANG'] = current_locale
+ try:
+ localdir = os.environ['INKSCAPE_LOCALEDIR'];
+ trans = gettext.translation(domain, localdir, [current_locale], fallback=True)
+ except KeyError:
+ trans = gettext.translation(domain, fallback=True)
+ elif sys.platform.startswith('darwin'):
+ try:
+ localdir = os.environ['INKSCAPE_LOCALEDIR'];
+ trans = gettext.translation(domain, localdir, fallback=True)
+ except KeyError:
+ try:
+ localdir = os.environ['PACKAGE_LOCALE_DIR'];
+ trans = gettext.translation(domain, localdir, fallback=True)
+ except KeyError:
+ trans = gettext.translation(domain, fallback=True)
+ else:
+ try:
+ localdir = os.environ['PACKAGE_LOCALE_DIR'];
+ trans = gettext.translation(domain, localdir, fallback=True)
+ except KeyError:
+ trans = gettext.translation(domain, fallback=True)
+ #sys.stderr.write(str(localdir) + "\n")
+ trans.install()
+
+def debug(what):
+ sys.stderr.write(str(what) + "\n")
+ return what
+
+def errormsg(msg):
+ """Intended for end-user-visible error messages.
+
+ (Currently just writes to stderr with an appended newline, but could do
+ something better in future: e.g. could add markup to distinguish error
+ messages from status messages or debugging output.)
+
+ Note that this should always be combined with translation:
+
+ import inkex
+ inkex.localize()
+ ...
+ inkex.errormsg(_("This extension requires two selected paths."))
+ """
+ if isinstance(msg, unicode):
+ sys.stderr.write(msg.encode("UTF-8") + "\n")
+ else:
+ sys.stderr.write((unicode(msg, "utf-8", errors='replace') + "\n").encode("UTF-8"))
+
+def are_near_relative(a, b, eps):
+ if (a-b <= a*eps) and (a-b >= -a*eps):
+ return True
+ else:
+ return False
+
+def check_inkbool(option, opt, value):
+ if str(value).capitalize() == 'True':
+ return True
+ elif str(value).capitalize() == 'False':
+ return False
+ else:
+ raise optparse.OptionValueError("option %s: invalid inkbool value: %s" % (opt, value))
+
+def addNS(tag, ns=None):
+ val = tag
+ if ns!=None and len(ns)>0 and ns in NSS and len(tag)>0 and tag[0]!='{':
+ val = "{%s}%s" % (NSS[ns], tag)
+ return val
+
+class InkOption(optparse.Option):
+ TYPES = optparse.Option.TYPES + ("inkbool",)
+ TYPE_CHECKER = copy.copy(optparse.Option.TYPE_CHECKER)
+ TYPE_CHECKER["inkbool"] = check_inkbool
+
+class Effect:
+ """A class for creating Inkscape SVG Effects"""
+
+ def __init__(self, *args, **kwargs):
+ self.document=None
+ self.original_document=None
+ self.ctx=None
+ self.selected={}
+ self.doc_ids={}
+ self.options=None
+ self.args=None
+ self.OptionParser = optparse.OptionParser(usage="usage: %prog [options] SVGfile",option_class=InkOption)
+ self.OptionParser.add_option("--id",
+ action="append", type="string", dest="ids", default=[],
+ help="id attribute of object to manipulate")
+
+ def effect(self):
+ pass
+
+ def getoptions(self,args=sys.argv[1:]):
+ """Collect command line arguments"""
+ self.options, self.args = self.OptionParser.parse_args(args)
+
+ def parse(self, filename=None):
+ """Parse document in specified file or on stdin"""
+
+ # First try to open the file from the function argument
+ if filename != None:
+ try:
+ stream = open(filename, 'r')
+ except Exception:
+ errormsg(_("Unable to open specified file: %s") % filename)
+ sys.exit()
+
+ # If it wasn't specified, try to open the file specified as
+ # an object member
+ elif self.svg_file != None:
+ try:
+ stream = open(self.svg_file, 'r')
+ except Exception:
+ errormsg(_("Unable to open object member file: %s") % self.svg_file)
+ sys.exit()
+
+ # Finally, if the filename was not specified anywhere, use
+ # standard input stream
+ else:
+ stream = sys.stdin
+
+ p = etree.XMLParser(huge_tree=True)
+ self.document = etree.parse(stream, parser=p)
+ self.original_document = copy.deepcopy(self.document)
+ stream.close()
+
+ # defines view_center in terms of document units
+ def getposinlayer(self):
+ #defaults
+ self.current_layer = self.document.getroot()
+ self.view_center = (0.0,0.0)
+
+ layerattr = self.document.xpath('//sodipodi:namedview/@inkscape:current-layer', namespaces=NSS)
+ if layerattr:
+ layername = layerattr[0]
+ layer = self.document.xpath('//svg:g[@id="%s"]' % layername, namespaces=NSS)
+ if layer:
+ self.current_layer = layer[0]
+
+ xattr = self.document.xpath('//sodipodi:namedview/@inkscape:cx', namespaces=NSS)
+ yattr = self.document.xpath('//sodipodi:namedview/@inkscape:cy', namespaces=NSS)
+ if xattr and yattr:
+ x = self.unittouu( xattr[0] + 'px' )
+ y = self.unittouu( yattr[0] + 'px')
+ doc_height = self.unittouu(self.document.getroot().get('height'))
+ if x and y:
+ self.view_center = (float(x), doc_height - float(y)) # FIXME: y-coordinate flip, eliminate it when it's gone in Inkscape
+
+ def getselected(self):
+ """Collect selected nodes"""
+ for i in self.options.ids:
+ path = '//*[@id="%s"]' % i
+ for node in self.document.xpath(path, namespaces=NSS):
+ self.selected[i] = node
+
+ def getElementById(self, id):
+ path = '//*[@id="%s"]' % id
+ el_list = self.document.xpath(path, namespaces=NSS)
+ if el_list:
+ return el_list[0]
+ else:
+ return None
+
+ def getParentNode(self, node):
+ for parent in self.document.getiterator():
+ if node in parent.getchildren():
+ return parent
+ break
+
+
+ def getdocids(self):
+ docIdNodes = self.document.xpath('//@id', namespaces=NSS)
+ for m in docIdNodes:
+ self.doc_ids[m] = 1
+
+ def getNamedView(self):
+ return self.document.xpath('//sodipodi:namedview', namespaces=NSS)[0]
+
+ def createGuide(self, posX, posY, angle):
+ atts = {
+ 'position': str(posX)+','+str(posY),
+ 'orientation': str(sin(radians(angle)))+','+str(-cos(radians(angle)))
+ }
+ guide = etree.SubElement(
+ self.getNamedView(),
+ addNS('guide','sodipodi'), atts )
+ return guide
+
+ def output(self):
+ """Serialize document into XML on stdout"""
+ original = etree.tostring(self.original_document)
+ result = etree.tostring(self.document)
+ if original != result:
+ self.document.write(sys.stdout)
+
+ def affect(self, args=sys.argv[1:], output=True):
+ """Affect an SVG document with a callback effect"""
+ self.svg_file = args[-1]
+ self.getoptions(args)
+ self.parse()
+ self.getposinlayer()
+ self.getselected()
+ self.getdocids()
+ self.effect()
+ if output: self.output()
+
+ def uniqueId(self, old_id, make_new_id = True):
+ new_id = old_id
+ if make_new_id:
+ while new_id in self.doc_ids:
+ new_id += random.choice('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
+ self.doc_ids[new_id] = 1
+ return new_id
+
+ def xpathSingle(self, path):
+ try:
+ retval = self.document.xpath(path, namespaces=NSS)[0]
+ except:
+ errormsg(_("No matching node for expression: %s") % path)
+ retval = None
+ return retval
+
+ #a dictionary of unit to user unit conversion factors
+ __uuconv = {'in':96.0, 'pt':1.33333333333, 'px':1.0, 'mm':3.77952755913, 'cm':37.7952755913,
+ 'm':3779.52755913, 'km':3779527.55913, 'pc':16.0, 'yd':3456.0 , 'ft':1152.0}
+
+ # Function returns the unit used for the values in SVG.
+ # For lack of an attribute in SVG that explicitly defines what units are used for SVG coordinates,
+ # try to calculate the unit from the SVG width and SVG viewbox.
+ # Defaults to 'px' units.
+ def getDocumentUnit(self):
+ svgunit = 'px' #default to pixels
+
+ svgwidth = self.document.getroot().get('width')
+ viewboxstr = self.document.getroot().get('viewBox')
+ if viewboxstr:
+ unitmatch = re.compile('(%s)$' % '|'.join(self.__uuconv.keys()))
+ param = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)')
+
+ p = param.match(svgwidth)
+ u = unitmatch.search(svgwidth)
+
+ width = 100 #default
+ viewboxwidth = 100 #default
+ svgwidthunit = 'px' #default assume 'px' unit
+ if p:
+ width = float(p.string[p.start():p.end()])
+ else:
+ errormsg(_("SVG Width not set correctly! Assuming width = 100"))
+ if u:
+ svgwidthunit = u.string[u.start():u.end()]
+
+ viewboxnumbers = []
+ for t in viewboxstr.split():
+ try:
+ viewboxnumbers.append(float(t))
+ except ValueError:
+ pass
+ if len(viewboxnumbers) == 4: #check for correct number of numbers
+ viewboxwidth = viewboxnumbers[2]
+
+ svgunitfactor = self.__uuconv[svgwidthunit] * width / viewboxwidth
+
+ # try to find the svgunitfactor in the list of units known. If we don't find something, ...
+ eps = 0.01 #allow 1% error in factor
+ for key in self.__uuconv:
+ if are_near_relative(self.__uuconv[key], svgunitfactor, eps):
+ #found match!
+ svgunit = key;
+
+ return svgunit
+
+
+ def unittouu(self, string):
+ '''Returns userunits given a string representation of units in another system'''
+ unit = re.compile('(%s)$' % '|'.join(self.__uuconv.keys()))
+ param = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)')
+
+ p = param.match(string)
+ u = unit.search(string)
+ if p:
+ retval = float(p.string[p.start():p.end()])
+ else:
+ retval = 0.0
+ if u:
+ try:
+ return retval * (self.__uuconv[u.string[u.start():u.end()]] / self.__uuconv[self.getDocumentUnit()])
+ except KeyError:
+ pass
+ else: # default assume 'px' unit
+ return retval / self.__uuconv[self.getDocumentUnit()]
+
+ return retval
+
+ def uutounit(self, val, unit):
+ return val / (self.__uuconv[unit] / self.__uuconv[self.getDocumentUnit()])
+
+ def addDocumentUnit(self, value):
+ ''' Add document unit when no unit is specified in the string '''
+ try:
+ float(value)
+ return value + self.getDocumentUnit()
+ except ValueError:
+ return value
+
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99
diff --git a/renderer/support/pogojig/inkscape/inkscape.py b/renderer/support/pogojig/inkscape/inkscape.py
new file mode 100644
index 0000000..efe7677
--- /dev/null
+++ b/renderer/support/pogojig/inkscape/inkscape.py
@@ -0,0 +1,124 @@
+import os
+import subprocess
+import xml.etree.ElementTree as etree
+
+def get_inkscape_layers(svg_path):
+ document = etree.parse(svg_path)
+
+ layers = []
+ nodes = document.findall(
+ '{http://www.w3.org/2000/svg}g[@{http://www.inkscape.org/namespaces/inkscape}groupmode="layer"]')
+
+ for i in nodes:
+ inkscape_name = i.get('{http://www.inkscape.org/namespaces/inkscape}label').strip()
+
+ if inkscape_name.endswith(']'):
+ export_name, args = inkscape_name[:-1].rsplit('[', 1)
+
+ export_name = export_name.strip()
+ args = args.strip()
+
+ use_paths = 'p' in args
+ else:
+ use_paths = False
+ export_name = inkscape_name
+
+ layers.append(Layer(inkscape_name, export_name, use_paths))
+ return layers
+
+
+def _inkscape(svg_path, verbs):
+ subprocess.run([os.environ.get('INKSCAPE', 'inkscape'), *(x for verb in verbs for x in ('--verb', verb)), svg_path])
+
+
+class Layer(object):
+ def __init__(self, inkscape_name, export_name, use_paths):
+ self.inkscape_name = inkscape_name
+ self.export_name = export_name
+ self.use_paths = use_paths
+
+
+class InkscapeCommandLine(object):
+ def __init__(self, path):
+ self._path = path
+ self._layers = get_inkscape_layers(path)
+ self._current_layer_index = None
+ self._verbs = []
+
+ def apply_to_document(self, *verb):
+ self._verbs.extend(verb)
+
+ def apply_to_layer(self, layer, *verb):
+ self._go_to_layer(layer)
+ self.apply_to_document(*verb)
+
+ def select_all_in_layer(self, layer):
+ self.apply_to_layer(layer, 'EditSelectAll')
+
+ def apply_to_layer_content(self, layer, *verbs):
+ self.select_all_in_layer(layer)
+ self.apply_to_document(*verbs)
+
+ def _go_to_layer(self, layer, with_selection=False):
+ if self._current_layer_index is None:
+ # Initialize to a known state. We cannot assume that any layer is
+ # selected and thus we need as many LayerPrev as we have layers.
+ self._current_layer_index = len(self._layers)
+ self._go_to_layer(self._layers[0])
+
+ target_index = self._layers.index(layer)
+
+ if with_selection:
+ next_command = 'LayerMoveToNext'
+ previous_command = 'LayerMoveToPrev'
+ else:
+ next_command = 'LayerNext'
+ previous_command = 'LayerPrev'
+
+ while self._current_layer_index != target_index:
+ if self._current_layer_index < target_index:
+ self.apply_to_document(next_command)
+ self._current_layer_index += 1
+ else:
+ self.apply_to_document(previous_command)
+ self._current_layer_index -= 1
+
+ if with_selection:
+ # When using LayerMoveToNext and LayerMoveToPrev, inkscape does
+ # not reliably select the next/previous layer.
+ self._current_layer_index = None
+
+ def duplicate_layer(self, layer):
+ self.apply_to_layer(layer, 'LayerDuplicate')
+
+ # Inkscape 0.91 places a duplicated layer above (after) the selected
+ # one and selects the new layer.
+ new_layer = Layer(layer.inkscape_name + ' copy', layer.export_name, layer.use_paths)
+ self._layers.insert(self._current_layer_index + 1, new_layer)
+
+ # Whether the original or the new layer is selected after the operation
+ # fluctuates between Inkscape versions.
+ self._current_layer_index = None
+
+ return new_layer
+
+ def delete_layer(self, layer):
+ self.apply_to_layer(layer, 'LayerDelete')
+
+ # Inkscape 0.91 selects the layer above (after) the deleted layer.
+ del self._layers[self._current_layer_index]
+
+ def clear_layer(self, layer):
+ self.select_all_in_layer(layer)
+ self.apply_to_document('EditDelete')
+
+ def move_content(self, source_layer, target_layer):
+ self.select_all_in_layer(source_layer)
+ self._go_to_layer(target_layer, True)
+
+ def run(self):
+ _inkscape(self._path, self._verbs)
+
+ @property
+ def layers(self):
+ return list(self._layers)
diff --git a/renderer/support/pogojig/inkscape/simplepath.py b/renderer/support/pogojig/inkscape/simplepath.py
new file mode 100644
index 0000000..8d4f0a7
--- /dev/null
+++ b/renderer/support/pogojig/inkscape/simplepath.py
@@ -0,0 +1,213 @@
+#!/usr/bin/env python
+"""
+simplepath.py
+functions for digesting paths into a simple list structure
+
+Copyright (C) 2005 Aaron Spike, aaron@ekips.org
+
+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 re, math
+
+def lexPath(d):
+ """
+ returns and iterator that breaks path data
+ identifies command and parameter tokens
+ """
+ offset = 0
+ length = len(d)
+ delim = re.compile(r'[ \t\r\n,]+')
+ command = re.compile(r'[MLHVCSQTAZmlhvcsqtaz]')
+ parameter = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)')
+ while 1:
+ m = delim.match(d, offset)
+ if m:
+ offset = m.end()
+ if offset >= length:
+ break
+ m = command.match(d, offset)
+ if m:
+ yield [d[offset:m.end()], True]
+ offset = m.end()
+ continue
+ m = parameter.match(d, offset)
+ if m:
+ yield [d[offset:m.end()], False]
+ offset = m.end()
+ continue
+ #TODO: create new exception
+ raise ValueError('Invalid path data!')
+'''
+pathdefs = {commandfamily:
+ [
+ implicitnext,
+ #params,
+ [casts,cast,cast],
+ [coord type,x,y,0]
+ ]}
+'''
+pathdefs = {
+ 'M':['L', 2, [float, float], ['x','y']],
+ 'L':['L', 2, [float, float], ['x','y']],
+ 'H':['H', 1, [float], ['x']],
+ 'V':['V', 1, [float], ['y']],
+ 'C':['C', 6, [float, float, float, float, float, float], ['x','y','x','y','x','y']],
+ 'S':['S', 4, [float, float, float, float], ['x','y','x','y']],
+ 'Q':['Q', 4, [float, float, float, float], ['x','y','x','y']],
+ 'T':['T', 2, [float, float], ['x','y']],
+ 'A':['A', 7, [float, float, float, int, int, float, float], ['r','r','a',0,'s','x','y']],
+ 'Z':['L', 0, [], []]
+ }
+
+def parsePath(d):
+ """
+ Parse SVG path and return an array of segments.
+ Removes all shorthand notation.
+ Converts coordinates to absolute.
+ """
+ retval = []
+ lexer = lexPath(d)
+
+ pen = (0.0,0.0)
+ subPathStart = pen
+ lastControl = pen
+ lastCommand = ''
+
+ while 1:
+ try:
+ token, isCommand = next(lexer)
+ except StopIteration:
+ break
+ params = []
+ needParam = True
+ if isCommand:
+ if not lastCommand and token.upper() != 'M':
+ raise ValueError('Invalid path, must begin with moveto.')
+ else:
+ command = token
+ else:
+ #command was omited
+ #use last command's implicit next command
+ needParam = False
+ if lastCommand:
+ if lastCommand.isupper():
+ command = pathdefs[lastCommand][0]
+ else:
+ command = pathdefs[lastCommand.upper()][0].lower()
+ else:
+ raise ValueError('Invalid path, no initial command.')
+ numParams = pathdefs[command.upper()][1]
+ while numParams > 0:
+ if needParam:
+ try:
+ token, isCommand = next(lexer)
+ if isCommand:
+ raise ValueError('Invalid number of parameters')
+ except StopIteration:
+ raise ValueError('Unexpected end of path')
+ cast = pathdefs[command.upper()][2][-numParams]
+ param = cast(token)
+ if command.islower():
+ if pathdefs[command.upper()][3][-numParams]=='x':
+ param += pen[0]
+ elif pathdefs[command.upper()][3][-numParams]=='y':
+ param += pen[1]
+ params.append(param)
+ needParam = True
+ numParams -= 1
+ #segment is now absolute so
+ outputCommand = command.upper()
+
+ #Flesh out shortcut notation
+ if outputCommand in ('H','V'):
+ if outputCommand == 'H':
+ params.append(pen[1])
+ if outputCommand == 'V':
+ params.insert(0,pen[0])
+ outputCommand = 'L'
+ if outputCommand in ('S','T'):
+ params.insert(0,pen[1]+(pen[1]-lastControl[1]))
+ params.insert(0,pen[0]+(pen[0]-lastControl[0]))
+ if outputCommand == 'S':
+ outputCommand = 'C'
+ if outputCommand == 'T':
+ outputCommand = 'Q'
+
+ #current values become "last" values
+ if outputCommand == 'M':
+ subPathStart = tuple(params[0:2])
+ pen = subPathStart
+ if outputCommand == 'Z':
+ pen = subPathStart
+ else:
+ pen = tuple(params[-2:])
+
+ if outputCommand in ('Q','C'):
+ lastControl = tuple(params[-4:-2])
+ else:
+ lastControl = pen
+ lastCommand = command
+
+ retval.append([outputCommand,params])
+ return retval
+
+def formatPath(a):
+ """Format SVG path data from an array"""
+ return "".join([cmd + " ".join([str(p) for p in params]) for cmd, params in a])
+
+def translatePath(p, x, y):
+ for cmd,params in p:
+ defs = pathdefs[cmd]
+ for i in range(defs[1]):
+ if defs[3][i] == 'x':
+ params[i] += x
+ elif defs[3][i] == 'y':
+ params[i] += y
+
+def scalePath(p, x, y):
+ for cmd,params in p:
+ defs = pathdefs[cmd]
+ for i in range(defs[1]):
+ if defs[3][i] == 'x':
+ params[i] *= x
+ elif defs[3][i] == 'y':
+ params[i] *= y
+ elif defs[3][i] == 'r': # radius parameter
+ params[i] *= x
+ elif defs[3][i] == 's': # sweep-flag parameter
+ if x*y < 0:
+ params[i] = 1 - params[i]
+ elif defs[3][i] == 'a': # x-axis-rotation angle
+ if y < 0:
+ params[i] = - params[i]
+
+def rotatePath(p, a, cx = 0, cy = 0):
+ if a == 0:
+ return p
+ for cmd,params in p:
+ defs = pathdefs[cmd]
+ for i in range(defs[1]):
+ if defs[3][i] == 'x':
+ x = params[i] - cx
+ y = params[i + 1] - cy
+ r = math.sqrt((x**2) + (y**2))
+ if r != 0:
+ theta = math.atan2(y, x) + a
+ params[i] = (r * math.cos(theta)) + cx
+ params[i + 1] = (r * math.sin(theta)) + cy
+
+
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99
diff --git a/renderer/support/pogojig/inkscape/simpletransform.py b/renderer/support/pogojig/inkscape/simpletransform.py
new file mode 100644
index 0000000..610cb57
--- /dev/null
+++ b/renderer/support/pogojig/inkscape/simpletransform.py
@@ -0,0 +1,242 @@
+'''
+Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr
+Copyright (C) 2010 Alvin Penner, penner@vaxxine.com
+
+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
+barraud@math.univ-lille1.fr
+
+This code defines several functions to make handling of transform
+attribute easier.
+'''
+
+import math, re
+
+from . import inkex, cubicsuperpath
+
+def parseTransform(transf,mat=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]):
+ if transf=="" or transf==None:
+ return(mat)
+ stransf = transf.strip()
+ result=re.match("(translate|scale|rotate|skewX|skewY|matrix)\s*\(([^)]*)\)\s*,?",stransf)
+#-- translate --
+ if result.group(1)=="translate":
+ args=result.group(2).replace(',',' ').split()
+ dx=float(args[0])
+ if len(args)==1:
+ dy=0.0
+ else:
+ dy=float(args[1])
+ matrix=[[1,0,dx],[0,1,dy]]
+#-- scale --
+ if result.group(1)=="scale":
+ args=result.group(2).replace(',',' ').split()
+ sx=float(args[0])
+ if len(args)==1:
+ sy=sx
+ else:
+ sy=float(args[1])
+ matrix=[[sx,0,0],[0,sy,0]]
+#-- rotate --
+ if result.group(1)=="rotate":
+ args=result.group(2).replace(',',' ').split()
+ a=float(args[0])*math.pi/180
+ if len(args)==1:
+ cx,cy=(0.0,0.0)
+ else:
+ cx,cy=map(float,args[1:])
+ matrix=[[math.cos(a),-math.sin(a),cx],[math.sin(a),math.cos(a),cy]]
+ matrix=composeTransform(matrix,[[1,0,-cx],[0,1,-cy]])
+#-- skewX --
+ if result.group(1)=="skewX":
+ a=float(result.group(2))*math.pi/180
+ matrix=[[1,math.tan(a),0],[0,1,0]]
+#-- skewY --
+ if result.group(1)=="skewY":
+ a=float(result.group(2))*math.pi/180
+ matrix=[[1,0,0],[math.tan(a),1,0]]
+#-- matrix --
+ if result.group(1)=="matrix":
+ a11,a21,a12,a22,v1,v2=result.group(2).replace(',',' ').split()
+ matrix=[[float(a11),float(a12),float(v1)], [float(a21),float(a22),float(v2)]]
+
+ matrix=composeTransform(mat,matrix)
+ if result.end() < len(stransf):
+ return(parseTransform(stransf[result.end():], matrix))
+ else:
+ return matrix
+
+def formatTransform(mat):
+ return ("matrix(%f,%f,%f,%f,%f,%f)" % (mat[0][0], mat[1][0], mat[0][1], mat[1][1], mat[0][2], mat[1][2]))
+
+def composeTransform(M1,M2):
+ a11 = M1[0][0]*M2[0][0] + M1[0][1]*M2[1][0]
+ a12 = M1[0][0]*M2[0][1] + M1[0][1]*M2[1][1]
+ a21 = M1[1][0]*M2[0][0] + M1[1][1]*M2[1][0]
+ a22 = M1[1][0]*M2[0][1] + M1[1][1]*M2[1][1]
+
+ v1 = M1[0][0]*M2[0][2] + M1[0][1]*M2[1][2] + M1[0][2]
+ v2 = M1[1][0]*M2[0][2] + M1[1][1]*M2[1][2] + M1[1][2]
+ return [[a11,a12,v1],[a21,a22,v2]]
+
+def composeParents(node, mat):
+ trans = node.get('transform')
+ if trans:
+ mat = composeTransform(parseTransform(trans), mat)
+ if node.getparent().tag == inkex.addNS('g','svg'):
+ mat = composeParents(node.getparent(), mat)
+ return mat
+
+def applyTransformToNode(mat,node):
+ m=parseTransform(node.get("transform"))
+ newtransf=formatTransform(composeTransform(mat,m))
+ node.set("transform", newtransf)
+
+def applyTransformToPoint(mat,pt):
+ x = mat[0][0]*pt[0] + mat[0][1]*pt[1] + mat[0][2]
+ y = mat[1][0]*pt[0] + mat[1][1]*pt[1] + mat[1][2]
+ pt[0]=x
+ pt[1]=y
+
+def applyTransformToPath(mat,path):
+ for comp in path:
+ for ctl in comp:
+ for pt in ctl:
+ applyTransformToPoint(mat,pt)
+
+def fuseTransform(node):
+ if node.get('d')==None:
+ #FIXME: how do you raise errors?
+ raise AssertionError('can not fuse "transform" of elements that have no "d" attribute')
+ t = node.get("transform")
+ if t == None:
+ return
+ m = parseTransform(t)
+ d = node.get('d')
+ p = cubicsuperpath.parsePath(d)
+ applyTransformToPath(m,p)
+ node.set('d', cubicsuperpath.formatPath(p))
+ del node.attrib["transform"]
+
+####################################################################
+##-- Some functions to compute a rough bbox of a given list of objects.
+##-- this should be shipped out in an separate file...
+
+def boxunion(b1,b2):
+ if b1 is None:
+ return b2
+ elif b2 is None:
+ return b1
+ else:
+ return((min(b1[0],b2[0]), max(b1[1],b2[1]), min(b1[2],b2[2]), max(b1[3],b2[3])))
+
+def roughBBox(path):
+ xmin,xMax,ymin,yMax = path[0][0][0][0],path[0][0][0][0],path[0][0][0][1],path[0][0][0][1]
+ for pathcomp in path:
+ for ctl in pathcomp:
+ for pt in ctl:
+ xmin = min(xmin,pt[0])
+ xMax = max(xMax,pt[0])
+ ymin = min(ymin,pt[1])
+ yMax = max(yMax,pt[1])
+ return xmin,xMax,ymin,yMax
+
+def refinedBBox(path):
+ xmin,xMax,ymin,yMax = path[0][0][1][0],path[0][0][1][0],path[0][0][1][1],path[0][0][1][1]
+ for pathcomp in path:
+ for i in range(1, len(pathcomp)):
+ cmin, cmax = cubicExtrema(pathcomp[i-1][1][0], pathcomp[i-1][2][0], pathcomp[i][0][0], pathcomp[i][1][0])
+ xmin = min(xmin, cmin)
+ xMax = max(xMax, cmax)
+ cmin, cmax = cubicExtrema(pathcomp[i-1][1][1], pathcomp[i-1][2][1], pathcomp[i][0][1], pathcomp[i][1][1])
+ ymin = min(ymin, cmin)
+ yMax = max(yMax, cmax)
+ return xmin,xMax,ymin,yMax
+
+def cubicExtrema(y0, y1, y2, y3):
+ cmin = min(y0, y3)
+ cmax = max(y0, y3)
+ d1 = y1 - y0
+ d2 = y2 - y1
+ d3 = y3 - y2
+ if (d1 - 2*d2 + d3):
+ if (d2*d2 > d1*d3):
+ t = (d1 - d2 + math.sqrt(d2*d2 - d1*d3))/(d1 - 2*d2 + d3)
+ if (t > 0) and (t < 1):
+ y = y0*(1-t)*(1-t)*(1-t) + 3*y1*t*(1-t)*(1-t) + 3*y2*t*t*(1-t) + y3*t*t*t
+ cmin = min(cmin, y)
+ cmax = max(cmax, y)
+ t = (d1 - d2 - math.sqrt(d2*d2 - d1*d3))/(d1 - 2*d2 + d3)
+ if (t > 0) and (t < 1):
+ y = y0*(1-t)*(1-t)*(1-t) + 3*y1*t*(1-t)*(1-t) + 3*y2*t*t*(1-t) + y3*t*t*t
+ cmin = min(cmin, y)
+ cmax = max(cmax, y)
+ elif (d3 - d1):
+ t = -d1/(d3 - d1)
+ if (t > 0) and (t < 1):
+ y = y0*(1-t)*(1-t)*(1-t) + 3*y1*t*(1-t)*(1-t) + 3*y2*t*t*(1-t) + y3*t*t*t
+ cmin = min(cmin, y)
+ cmax = max(cmax, y)
+ return cmin, cmax
+
+def computeBBox(aList,mat=[[1,0,0],[0,1,0]]):
+ bbox=None
+ for node in aList:
+ m = parseTransform(node.get('transform'))
+ m = composeTransform(mat,m)
+ #TODO: text not supported!
+ d = None
+ if node.get("d"):
+ d = node.get('d')
+ elif node.get('points'):
+ d = 'M' + node.get('points')
+ elif node.tag in [ inkex.addNS('rect','svg'), 'rect', inkex.addNS('image','svg'), 'image' ]:
+ d = 'M' + node.get('x', '0') + ',' + node.get('y', '0') + \
+ 'h' + node.get('width') + 'v' + node.get('height') + \
+ 'h-' + node.get('width')
+ elif node.tag in [ inkex.addNS('line','svg'), 'line' ]:
+ d = 'M' + node.get('x1') + ',' + node.get('y1') + \
+ ' ' + node.get('x2') + ',' + node.get('y2')
+ elif node.tag in [ inkex.addNS('circle','svg'), 'circle', \
+ inkex.addNS('ellipse','svg'), 'ellipse' ]:
+ rx = node.get('r')
+ if rx is not None:
+ ry = rx
+ else:
+ rx = node.get('rx')
+ ry = node.get('ry')
+ cx = float(node.get('cx', '0'))
+ cy = float(node.get('cy', '0'))
+ x1 = cx - float(rx)
+ x2 = cx + float(rx)
+ d = 'M %f %f ' % (x1, cy) + \
+ 'A' + rx + ',' + ry + ' 0 1 0 %f,%f' % (x2, cy) + \
+ 'A' + rx + ',' + ry + ' 0 1 0 %f,%f' % (x1, cy)
+
+ if d is not None:
+ p = cubicsuperpath.parsePath(d)
+ applyTransformToPath(m,p)
+ bbox=boxunion(refinedBBox(p),bbox)
+
+ elif node.tag == inkex.addNS('use','svg') or node.tag=='use':
+ refid=node.get(inkex.addNS('href','xlink'))
+ path = '//*[@id="%s"]' % refid[1:]
+ refnode = node.xpath(path)
+ bbox=boxunion(computeBBox(refnode,m),bbox)
+
+ bbox=boxunion(computeBBox(node,m),bbox)
+ return bbox
+
+
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99
diff --git a/renderer/support/pogojig/kicad/__init__.py b/renderer/support/pogojig/kicad/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/renderer/support/pogojig/kicad/__init__.py
diff --git a/renderer/support/pogojig/kicad/kicad-cache.lib b/renderer/support/pogojig/kicad/kicad-cache.lib
new file mode 100644
index 0000000..a98cd73
--- /dev/null
+++ b/renderer/support/pogojig/kicad/kicad-cache.lib
@@ -0,0 +1,21 @@
+EESchema-LIBRARY Version 2.4
+#encoding utf-8
+#
+# Connector_Conn_01x01_Female
+#
+DEF Connector_Conn_01x01_Female J 0 40 Y N 1 F N
+F0 "J" 0 100 50 H V C CNN
+F1 "Connector_Conn_01x01_Female" 0 -100 50 H V C CNN
+F2 "" 0 0 50 H I C CNN
+F3 "" 0 0 50 H I C CNN
+$FPLIST
+ Connector*:*
+$ENDFPLIST
+DRAW
+A 0 0 20 901 -901 1 1 6 N 0 20 0 -20
+P 2 1 1 6 -50 0 -20 0 N
+X Pin_1 1 -200 0 150 R 50 50 1 1 P
+ENDDRAW
+ENDDEF
+#
+#End Library
diff --git a/renderer/support/pogojig/kicad/kicad.pro b/renderer/support/pogojig/kicad/kicad.pro
new file mode 100644
index 0000000..5cd0983
--- /dev/null
+++ b/renderer/support/pogojig/kicad/kicad.pro
@@ -0,0 +1,34 @@
+update=05/04/2019 20:44:53
+version=1
+last_client=kicad
+[general]
+version=1
+RootSch=
+BoardNm=
+[pcbnew]
+version=1
+LastNetListRead=
+UseCmpFile=1
+PadDrill=0.600000000000
+PadDrillOvalY=0.600000000000
+PadSizeH=1.500000000000
+PadSizeV=1.500000000000
+PcbTextSizeV=1.500000000000
+PcbTextSizeH=1.500000000000
+PcbTextThickness=0.300000000000
+ModuleTextSizeV=1.000000000000
+ModuleTextSizeH=1.000000000000
+ModuleTextSizeThickness=0.150000000000
+SolderMaskClearance=0.000000000000
+SolderMaskMinWidth=0.000000000000
+DrawSegmentWidth=0.200000000000
+BoardOutlineThickness=0.100000000000
+ModuleOutlineThickness=0.150000000000
+CopperEdgeClearance=0.000000000000
+[cvpcb]
+version=1
+NetIExt=net
+[eeschema]
+version=1
+LibDir=
+[eeschema/libraries]