def test_single_point_transform(self): from math import sqrt, sin, cos self.assertAlmostTuple(list(Path("M 10 10 30 20").control_points), ((10, 10), (30, 20))) self.assertAlmostTuple( list( Path("M 10 10 30 20").transform( Transform(translate=(10, 7))).control_points), ((20, 17), (40, 27))) self.assertAlmostTuple( list( Path("M 20 20 5 0 0 7 ").transform( Transform(scale=10)).control_points), ((200, 200), (50, 0), (0, 70))) self.assertAlmostTuple( list( Path("M 20 20 1 0").transform( Transform(rotate=90)).control_points), ((-20, 20), (0, 1))) self.assertAlmostTuple( list( Path("M 20 20 1 0").transform( Transform(rotate=45)).control_points), ((0, sqrt(20**2 + 20**2)), (sqrt(2) / 2, sqrt(2) / 2))) self.assertAlmostTuple( list( Path("M 1 0 0 1").transform( Transform(rotate=30)).control_points), ((sqrt(3) / 2, 0.5), (-0.5, sqrt(3) / 2)))
def test_interpolate(self): """Test interpolate with other transform""" t1 = Transform((0, 0, 0, 0, 0, 0)) t2 = Transform((1, 1, 1, 1, 1, 1)) val = t1.interpolate(t2, 0.5) assert all( getattr(val, a) == pytest.approx(0.5, 1e-3) for a in 'abcdef')
def test_scale(self): """In-place scaling from blank transform""" elem = self.svg.getElementById('F') self.assertEqual(elem.transform, Transform()) self.assertEqual(elem.get('transform'), None) elem.transform.add_scale(1.0666666666666667, 1.0666666666666667) self.assertEqual(elem.get('transform'), Transform(scale=1.06667)) self.assertIn(b'transform', etree.tostring(elem))
def exportDrill(self, kicad_mod=False): self.setInkscapeScaling() kicad_drill_string = "" i = 0 if kicad_mod: pad_template = "(pad {n} thru_hole circle (at {x} {y}) (size {d} {d}) (drill {d}) (layers *.Cu *.Mask))\n" else: pad_template = """ (module Wire_Pads:SolderWirePad_single_0-8mmDrill (layer F.Cu) (tedit 0) (tstamp 5ABD66D0) (at {x} {y}) (pad {n} thru_hole circle (at 0 0) (size {d} {d}) (drill {d}) (layers *.Cu *.Mask)) ) """ layerPath = '//svg:g[@inkscape:groupmode="layer"][@inkscape:label="Drill"]' for layer in self.document.getroot().xpath(layerPath, namespaces=inkex.NSS): layer_trans = layer.get('transform') if layer_trans: layer_m = Transform(layer_trans).matrix else: layer_m = IDENTITY_MATRIX nodePath = 'descendant::svg:circle' count = 0 for node in layer.xpath(nodePath, namespaces=inkex.NSS): count = count + 1 cx = float(node.get('cx')) cy = float(node.get('cy')) radius = float(node.get('r')) drill_size = radius * 2 t = node.get('transform') if t: m = Transform(t).matrix trans = (Transform(layer_m) * Transform(m)).matrix else: trans = layer_m pt = Transform(trans).apply_to_point([cx, cy]) padCoord = self.coordToKicad(pt) kicad_drill_string += pad_template.format(x=padCoord[0], y=padCoord[1], n=count, d=drill_size) return kicad_drill_string
def test_pop_wrapped_attribute(self): """Remove wrapped attribute using .pop()""" group = Group() self.assertEqual(group.pop('transform'), Transform()) group.update( transform=Transform(scale=2) ) self.assertEqual(group.pop('transform'), Transform(scale=2)) self.assertEqual(group.pop('transform'), Transform()) self.assertRaises(AttributeError, getattr, group, 'foo')
def test_update_consistant(self): """Update doesn't keep callbacks around""" elem = self.svg.getElementById('D') tr_a = Transform(translate=(10, 10)) tr_b = Transform(translate=(-20, 15)) elem.transform = tr_a elem.transform = tr_b self.assertEqual(str(elem.transform), 'translate(-20, 15)') tr_a.add_translate(10, 10) self.assertEqual(str(elem.transform), 'translate(-20, 15)') elem.set('transform', None) self.assertEqual(elem.get('transform'), None)
def test_inline_transformations(self): path = Path() self.assertTrue(path is not path.translate(10, 20)) self.assertTrue(path is not path.transform(Transform(scale=10))) self.assertTrue(path is not path.rotate(10)) self.assertTrue(path is not path.scale(10, 20)) self.assertTrue(path is path.translate(10, 20, inplace=True)) self.assertTrue( path is path.transform(Transform(scale=10), inplace=True)) self.assertTrue(path is path.rotate(10, inplace=True)) self.assertTrue(path is path.scale(10, 20, inplace=True))
def test_set_wrapped_attribute(self): """Remove wrapped attribute using .set()""" group = Group().update( transform=Transform(scale=2) ) self.assertEqual(group.transform.matrix[0][0], 2) self.assertEqual(group.transform.matrix[1][1], 2) group.update( transform=None ) self.assertEqual(group.transform, Transform())
def recursiveFuseTransform(self, node, transf=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]): transf = Transform(transf) * Transform(node.get("transform", None)) if 'transform' in node.attrib: del node.attrib['transform'] node = ApplyTransform.objectToPath(node) if 'd' in node.attrib: d = node.get('d') p = CubicSuperPath(d) p = Path(p).to_absolute().transform(transf, True) node.set('d', Path(CubicSuperPath(p).to_path())) self.scaleStrokeWidth(node, transf) elif node.tag in [ inkex.addNS('polygon', 'svg'), inkex.addNS('polyline', 'svg') ]: points = node.get('points') points = points.strip().split(' ') for k, p in enumerate(points): if ',' in p: p = p.split(',') p = [float(p[0]), float(p[1])] applyTransformToPoint(transf, p) p = [str(p[0]), str(p[1])] p = ','.join(p) points[k] = p points = ' '.join(points) node.set('points', points) self.scaleStrokeWidth(node, transf) elif node.tag in [ inkex.addNS('rect', 'svg'), inkex.addNS('text', 'svg'), inkex.addNS('image', 'svg'), inkex.addNS('use', 'svg'), inkex.addNS('circle', 'svg') ]: # node.set('transform', str(Transform(transf))) inkex.utils.errormsg("Shape %s not yet supported" % node.tag) else: # e.g. <g style="..."> self.scaleStrokeWidth(node, transf) for child in node.getchildren(): self.recursiveFuseTransform(child, transf)
def test_in_place_transforms(self): """Do style and transforms update correctly""" elem = self.svg.getElementById('D') self.assertEqual(type(elem.transform), Transform) self.assertEqual(type(elem.style), Style) self.assertTrue(elem.transform) elem.transform = Transform() self.assertEqual(elem.transform, Transform()) self.assertEqual(elem.get('transform'), None) self.assertNotIn(b'transform', etree.tostring(elem)) elem.transform.add_translate(10, 10) self.assertIn(b'transform', etree.tostring(elem)) elem.transform.add_translate(-10, -10) self.assertNotIn(b'transform', etree.tostring(elem))
def getGlobalTransform(self, node): parent = node.getparent() myTrans = Transform(node.get('transform')).matrix if myTrans: if parent is not None: parentTrans = self.getGlobalTransform(parent) if parentTrans: return Transform(parentTrans) * Transform(myTrans) else: return myTrans else: if parent is not None: return self.getGlobalTransform(parent) else: return None
def test_arc_transformation(self): cases = [ ("M 10 10 A 100 100 0 1 0 100 100 Z", ((1, 0, 1), (0, 1, 0)), "M 11 10 A 100 100 0 1 0 101 100 Z"), ("M 10 10 A 100 100 0 1 0 100 100 Z", ((1, 0, 0), (0, 1, 1)), "M 10 11 A 100 100 0 1 0 100 101 Z"), ("M 10 10 A 100 100 0 1 0 100 100 Z", ((1, 0, 1), (0, 1, 1)), "M 11 11 A 100 100 0 1 0 101 101 Z"), ("M 10 10 A 100 100 0 1 0 100 100 Z", ((2, 0, 0), (0, 1, 0)), "M 20 10 A 200 100 0 1 0 200 100 Z"), ("M 10 10 A 100 100 0 1 0 100 100 Z", ((1, 0, 0), (0, 2, 0)), "M 10 20 A 200 100 90 1 0 100 200 Z"), ("M 10 10 A 100 100 0 1 0 100 100 Z", ((1, 0, 0), (0, -1, 0)), "M 10 -10 A 100 100 0 1 1 100 -100 Z"), ("M 10 10 A 100 100 0 1 0 100 100 Z", ((1, 2, 0), (0, 2, 0)), "M 30 20 " "A 292.081 68.4742 41.4375 1 0 300 200 Z"), ("M 10 10 " "A 100 100 0 1 0 100 100 " "A 300 200 0 1 0 50 20 Z", ((1, 2, 0), (5, 6, 0)), "M 30,110 " "A 810.90492,49.327608 74.368134 1 1 " "300,1100 1981.2436,121.13604 75.800007 1 1 90,370 Z"), ] for path, transform, expected in cases: expected = Path(expected) result = Path(path).transform(Transform(matrix=transform)) self.assertDeepAlmostEqual(expected.to_arrays(), result.to_arrays(), places=4)
def applyTransformToPoint(mat, pt): """Transform(mat).apply_to_point(pt)""" pt2 = Transform(mat).apply_to_point(pt) # Apply in place as original method was modifying arrays in place. # but don't do this in your code! This is not good code design. pt[0] = pt2[0] pt[1] = pt2[1]
def _expand_defs(root): from inkex.transforms import Transform from inkex.elements import ShapeElement from copy import deepcopy for el in root: if isinstance(el, inkex.elements.Use): # <group> element will replace <use> node group = inkex.elements.Group() # add all objects from symbol node for obj in el.href: group.append(deepcopy(obj)) # translate group group.transform = Transform( translate=(float(el.attrib["x"]), float(el.attrib["y"]))) # replace use node with group node parent = el.getparent() parent.remove(el) parent.add(group) el = group # required for recursive defs # expand children defs TexTextElement._expand_defs(el)
def test_transform(self): """Transform by a whole matrix""" ret = Path("M 100 100 L 110 120 L 140 140 L 300 300") ret = ret.transform(Transform(translate=(10, 10))) self.assertEqual(str(ret), 'M 110 110 L 120 130 L 150 150 L 310 310') ret = ret.transform(Transform(translate=(-10, -10))) self.assertEqual(str(ret), 'M 100 100 L 110 120 L 140 140 L 300 300') ret = Path('M 5 5 H 10 V 15') ret = ret.transform(Transform(rotate=-10)) self.assertEqual( 'M 5.79228 4.0558 ' 'L 10.7163 3.18756 ' 'L 12.4528 13.0356', str(ret)) ret = Path("M 10 10 A 50,50 0 0 1 85.355333,85.355341 L 100 0") ret = ret.transform(Transform(scale=10)) self.assertEqual(str(ret), 'M 100 100 A 500 500 0 0 1 853.553 853.553 L 1000 0') self.assertRaises(ValueError, Horz([10]).transform, Transform())
def test_transformation_preserve_type(self): import re paths = [ "M 10 10 A 100 100 0 1 0 100 100 C 10 15 20 20 5 5 Z", "m 10 10 a 100 100 0 1 0 100 100 c 10 15 20 20 5 5 z", "m 10 10 l 100 200 L 20 30 C 10 20 30 40 11 12", "M 10 10 Q 12 13 14 15 T 11 32 T 32 11", "m 10 10 q 12 13 14 15 t 11 32 t 32 11", ] t = Transform(matrix=((1, 2, 3), (4, 5, 6))) for path_str in paths: path = Path(path_str) new_path = path.transform(t) cmds = "".join([cmd.letter for cmd in new_path]) expected = re.sub(r"\d|\s|,", "", path_str) self.assertEqual(expected, cmds) self.assertAlmostTuple( [t.apply_to_point(p) for p in path.control_points], list(new_path.control_points))
def exportDrillLayer(self, layerPath, pad_template): kicad_drill_string = "" for layer in self.document.getroot().xpath(layerPath, namespaces=inkex.NSS): layer_trans = layer.get('transform') if layer_trans: layer_m = Transform(layer_trans).matrix else: layer_m = IDENTITY_MATRIX nodePath = 'descendant::svg:circle' count = 0 for node in layer.xpath(nodePath, namespaces=inkex.NSS): count = count + 1 cx = float(node.get('cx')) cy = float(node.get('cy')) radius = float(node.get('r')) drill_size = radius * 2 t = node.get('transform') if t: m = Transform(t).matrix trans = (Transform(layer_m) * Transform(m)).matrix else: trans = layer_m pt = Transform(trans).apply_to_point([cx, cy]) padCoord = self.coordToKicad(pt) kicad_drill_string += pad_template.format(x=padCoord[0], y=padCoord[1], n=count, d=drill_size) return kicad_drill_string
def align_to_node(self, ref_node, alignment, relative_scale): """ Aligns the node represented by self to a reference node according to the settings defined by the user :param (TexTextElement) ref_node: Reference node subclassed from SvgElement to which self is going to be aligned :param (str) alignment: A 2-element string list defining the alignment :param (float) relative_scale: Scaling of the new node relative to the scale of the reference node """ from inkex.transforms import Transform scale_transform = Transform("scale(%f)" % relative_scale) old_transform = Transform(ref_node.transform) # Account for vertical flipping of pstoedit nodes when recompiled via pdf2svg and vice versa revert_flip = Transform("scale(1)") if ref_node.get_meta("pdfconverter") == "pdf2svg": revert_flip = Transform(matrix=((1, 0, 0), (0, -1, 0))) # vertical reflection composition = old_transform * revert_flip composition = scale_transform * composition # keep alignment point of drawing intact, calculate required shift self.transform = composition ref_bb = ref_node.bounding_box() x, y, w, h = ref_bb.left, ref_bb.top, ref_bb.width, ref_bb.height bb = self.bounding_box() new_x, new_y, new_w, new_h = bb.left, bb.top, bb.width, bb.height p_old = self._get_pos(x, y, w, h, alignment) p_new = self._get_pos(new_x, new_y, new_w, new_h, alignment) dx = p_old[0] - p_new[0] dy = p_old[1] - p_new[1] composition = Transform(translate=(dx, dy)) * composition self.transform = composition self.set_meta("jacobian_sqrt", str(self.get_jacobian_sqrt()))
def test_add_transform(self): """Test add_TRANSFORM syntax for quickly composing known transforms""" tr1 = Transform() tr1.add_scale(5.0, 1.0) self.assertEqual(str(tr1), 'scale(5, 1)') tr1.add_translate(10, 10) self.assertEqual(str(tr1), 'matrix(5 0 0 1 50 10)')
def test_rotation_degrees(self): """Test parsing and composition of different rotations""" self.assertAlmostEqual(Transform(rotate=30).rotation_degrees(), 30) self.assertAlmostEqual( Transform(translate=(10, 20)).rotation_degrees(), 0) self.assertAlmostEqual(Transform(scale=(1, 1)).rotation_degrees(), 0) self.assertAlmostEqual( Transform(rotate=35, translate=(10, 20)).rotation_degrees(), 35) self.assertAlmostEqual( Transform(rotate=35, translate=(10, 20), scale=5).rotation_degrees(), 35) self.assertAlmostEqual( Transform(rotate=35, translate=(10, 20), scale=(5, 5)).rotation_degrees(), 35) def rotation_degrees(**kwargs): return Transform(**kwargs).rotation_degrees() self.assertRaises(ValueError, rotation_degrees, rotate=35, skewx=1) self.assertRaises(ValueError, rotation_degrees, rotate=35, skewy=1) self.assertRaises(ValueError, rotation_degrees, rotate=35, scale=(10, 11)) self.assertRaises(ValueError, rotation_degrees, rotate=35, scale=(10, 11))
def test_construction_order(self): """Test transform kwargs construction order""" if not PY3: self.skipTest( "Construction order is known to fail on python2 (by design).") return self.assertEqual(str(Transform(scale=2.0, translate=(5, 6))), 'matrix(2 0 0 2 5 6)') self.assertEqual(str(Transform(scale=2.0, rotate=45)), 'matrix(1.41421 1.41421 -1.41421 1.41421 0 0)') x, y, angle = 5, 7, 31 rotation = Transform(rotate=angle) translation = Transform(translate=(x, y)) rotation_then_translation = translation * rotation translation_then_rotation = rotation * translation tr1 = Transform(rotate=angle, translate=(x, y)) tr2 = Transform(translate=(x, y), rotate=angle) self.assertNotEqual(tr1, tr2) self.assertDeepAlmostEqual(tr1.matrix, rotation_then_translation.matrix) self.assertDeepAlmostEqual(tr2.matrix, translation_then_rotation.matrix)
def fill_row(self, node): #max_line_width = self.svg.unittouu('450mm') #x_start = self.svg.unittouu('3mm') #y_start = self.svg.unittouu('1600mm') - self.svg.unittouu('3mm') #gap_x = gap_y = self.svg.unittouu('10mm') svg = self.document.getroot() x_start = 0 y_start = self.svg.unittouu(svg.attrib['height']) max_line_width = self.svg.unittouu(svg.get('width')) total_width = 0 total_height = self.total_height group = etree.SubElement(self.svg.get_current_layer(), addNS('g', 'svg')) bbox = node.bounding_box() x = bbox.left y = bbox.top node_width = self.options.gap_x + bbox.width while total_width + node_width < max_line_width: node_copy = deepcopy(node) group.append(node_copy) x_dest = x_start + total_width y_dest = y_start - (total_height + bbox.height) # translation logic if node_copy.tag == addNS('path', 'svg'): x_delta = x_dest - x y_delta = y_dest - y path = Path(node_copy.attrib['d']) path.translate(x_delta, y_delta, True) node_copy.attrib['d'] = str(Path(path)) elif node_copy.tag == addNS('g', 'svg'): x_delta = x_dest - x y_delta = y_dest - y translation_matrix = [[1.0, 0.0, x_delta], [0.0, 1.0, y_delta]] Transform(translation_matrix) * node_copy.transform else: node_copy.attrib['x'] = str(x_dest) node_copy.attrib['y'] = str(y_dest) total_width += node_width self.total_height += group.bounding_box().height + self.options.gap_y
def drawLabel(self, group, angle, segment_angle, outer_diameter, labelNum): outer_radius = outer_diameter / 2 label_angle = angle + (segment_angle / 2) point = calculatePoint(label_angle, outer_radius) matrix = Transform('rotate(' + str(label_angle + 90) + ')').matrix matrix_str = str(matrix[0][0]) + "," + str(matrix[0][1]) matrix_str += "," + str(matrix[1][0]) + "," + str(matrix[1][1]) + ",0,0" text = { 'id': 'text' + str(labelNum), #'sodipodi:linespacing': '0%', 'style': 'font-size: 6px;font-style: normal;font-family: Sans', #'transform': 'matrix(' + matrix_str + ')', 'x': str(point[0]), 'y': str(point[1]), #'xml:space': 'preserve' } textElement = etree.SubElement(group, inkex.addNS('text', 'svg'), text) #tspanElement = etree.Element( # textElement, '{%s}%s' % (svg_uri, 'tspan'), tspan) textElement.text = string.printable[labelNum % len(string.printable)] self.svg.get_current_layer().append(textElement)
def effect(self): opt = self.options if not opt.placeholderid: raise inkex.AbortExtension('Please enter the placeholder ID') elif not opt.qrcodeid: raise inkex.AbortExtension('Please enter the QRCode ID') placeholder = self.svg.getElementById(opt.placeholderid) qrcode = self.svg.getElementById(opt.qrcodeid) if placeholder is None or qrcode is None: # Delete the generated qrcode qrcode.getparent().remove(qrcode) return # Reset scale before processing qrcode.set('transform', 'scale(1,1)') # Get scaling factors scalex = placeholder.bounding_box().width / qrcode.bounding_box().width scaley = placeholder.bounding_box().height / qrcode.bounding_box().height # Apply scaling and translating tr = Transform() tr.add_translate(placeholder.bounding_box().left, placeholder.bounding_box().top) qrcode.set('transform', tr) tr.add_scale(scalex, scaley) qrcode.set('transform', tr) # Move qrcode inplace of the placeholder placeholder.getparent().append(qrcode) # Delete the placeholder placeholder.getparent().remove(placeholder)
def recursiveFuseTransform(self, node, transf=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]): # Modification by David Burghoff: # Since transforms apply to an object's clips, before applying the transform # we will need to duplicate the clip path and transform it clippathurl = node.get('clip-path') if clippathurl is not None and node.get("transform") is not None: myn = node while not (myn.getparent() == None): # get svg handle myn = myn.getparent() svg = myn clippath = svg.getElementById(clippathurl[5:-1]) if clippath is not None: d = clippath.duplicate() clippathurl = "url(#" + d.get("id") + ")" node.set("clip-path", clippathurl) for k in d.getchildren(): if k.get('transform') is not None: tr = Transform(node.get("transform")) * Transform( k.get('transform')) else: tr = Transform(node.get("transform")) k.set('transform', tr) transf = Transform(transf) * Transform(node.get("transform", None)) if 'transform' in node.attrib: del node.attrib['transform'] node = ApplyTransform.objectToPath(node) if transf == NULL_TRANSFORM: # Don't do anything if there is effectively no transform applied # reduces alerts for unsupported nodes pass elif 'd' in node.attrib: d = node.get('d') p = CubicSuperPath(d) p = Path(p).to_absolute().transform(transf, True) node.set('d', str(Path(CubicSuperPath(p).to_path()))) self.scaleStrokeWidth(node, transf) elif node.tag in [ inkex.addNS('polygon', 'svg'), inkex.addNS('polyline', 'svg') ]: points = node.get('points') points = points.strip().split(' ') for k, p in enumerate(points): if ',' in p: p = p.split(',') p = [float(p[0]), float(p[1])] p = transf.apply_to_point(p) p = [str(p[0]), str(p[1])] p = ','.join(p) points[k] = p points = ' '.join(points) node.set('points', points) self.scaleStrokeWidth(node, transf) elif node.tag in [ inkex.addNS("ellipse", "svg"), inkex.addNS("circle", "svg") ]: def isequal(a, b): return abs(a - b) <= transf.absolute_tolerance if node.TAG == "ellipse": rx = float(node.get("rx")) ry = float(node.get("ry")) else: rx = float(node.get("r")) ry = rx cx = float(node.get("cx")) cy = float(node.get("cy")) sqxy1 = (cx - rx, cy - ry) sqxy2 = (cx + rx, cy - ry) sqxy3 = (cx + rx, cy + ry) newxy1 = transf.apply_to_point(sqxy1) newxy2 = transf.apply_to_point(sqxy2) newxy3 = transf.apply_to_point(sqxy3) node.set("cx", (newxy1[0] + newxy3[0]) / 2) node.set("cy", (newxy1[1] + newxy3[1]) / 2) edgex = math.sqrt( abs(newxy1[0] - newxy2[0])**2 + abs(newxy1[1] - newxy2[1])**2) edgey = math.sqrt( abs(newxy2[0] - newxy3[0])**2 + abs(newxy2[1] - newxy3[1])**2) if not isequal(edgex, edgey) and ( node.TAG == "circle" or not isequal(newxy2[0], newxy3[0]) or not isequal(newxy1[1], newxy2[1])): inkex.utils.errormsg( "Warning: Shape %s (%s) is approximate only, try Object to path first for better results" % (node.TAG, node.get("id"))) if node.TAG == "ellipse": node.set("rx", edgex / 2) node.set("ry", edgey / 2) else: node.set("r", edgex / 2) # Modficiation by David Burghoff: Added support for lines elif node.tag in inkex.addNS('line', 'svg'): x1 = node.get('x1') x2 = node.get('x2') y1 = node.get('y1') y2 = node.get('y2') p1 = transf.apply_to_point([x1, y1]) p2 = transf.apply_to_point([x2, y2]) node.set('x1', str(p1[0])) node.set('y1', str(p1[1])) node.set('x2', str(p2[0])) node.set('y2', str(p2[1])) self.scaleStrokeWidth(node, transf) elif node.typename in ['Rectangle']: x = float(node.get('x')) y = float(node.get('y')) w = float(node.get('width')) h = float(node.get('height')) pts = [[x, y], [x + w, y], [x + w, y + h], [x, y + h], [x, y]] xs = [] ys = [] for p in pts: p = transf.apply_to_point(p) xs.append(p.x) ys.append(p.y) node.set('x', str(min(xs))) node.set('y', str(min(ys))) node.set('width', str(max(xs) - min(xs))) node.set('height', str(max(ys) - min(ys))) self.scaleStrokeWidth(node, transf) elif node.tag in [ inkex.addNS('text', 'svg'), inkex.addNS('image', 'svg'), inkex.addNS('use', 'svg') ]: inkex.utils.errormsg( "Shape %s (%s) not yet supported, try Object to path first" % (node.TAG, node.get("id"))) else: # e.g. <g style="..."> self.scaleStrokeWidth(node, transf) for child in node.getchildren(): self.recursiveFuseTransform(child, transf)
#!/usr/bin/env python3 # # License: GPL2 # Copyright Mark "Klowner" Riedesel # https://github.com/Klowner/inkscape-applytransforms # Modified by David Burghoff import inkex import math from inkex.paths import CubicSuperPath, Path from inkex.transforms import Transform from inkex.styles import Style NULL_TRANSFORM = Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]) from lxml import etree class ApplyTransform(inkex.EffectExtension): def __init__(self): super(ApplyTransform, self).__init__() def effect(self): if self.svg.selected: for (_, shape) in self.svg.selected.items(): self.recursiveFuseTransform(shape) else: self.recursiveFuseTransform(self.document.getroot()) @staticmethod def objectToPath(node): if node.tag == inkex.addNS('g', 'svg'):
def recursiveFuseTransform(self, node, transf=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]): transf = Transform(transf) * Transform(node.get("transform", None)) if 'transform' in node.attrib: del node.attrib['transform'] node = ApplyTransform.objectToPath(node) if transf == NULL_TRANSFORM: # Don't do anything if there is effectively no transform applied # reduces alerts for unsupported nodes pass elif 'd' in node.attrib: d = node.get('d') p = CubicSuperPath(d) p = Path(p).to_absolute().transform(transf, True) node.set('d', Path(CubicSuperPath(p).to_path())) self.scaleStrokeWidth(node, transf) elif node.tag in [ inkex.addNS('polygon', 'svg'), inkex.addNS('polyline', 'svg') ]: points = node.get('points') points = points.strip().split(' ') for k, p in enumerate(points): if ',' in p: p = p.split(',') p = [float(p[0]), float(p[1])] p = transf.apply_to_point(p) p = [str(p[0]), str(p[1])] p = ','.join(p) points[k] = p points = ' '.join(points) node.set('points', points) self.scaleStrokeWidth(node, transf) elif node.tag in [ inkex.addNS("ellipse", "svg"), inkex.addNS("circle", "svg") ]: def isequal(a, b): return abs(a - b) <= transf.absolute_tolerance if node.TAG == "ellipse": rx = float(node.get("rx")) ry = float(node.get("ry")) else: rx = float(node.get("r")) ry = rx cx = float(node.get("cx")) cy = float(node.get("cy")) sqxy1 = (cx - rx, cy - ry) sqxy2 = (cx + rx, cy - ry) sqxy3 = (cx + rx, cy + ry) newxy1 = transf.apply_to_point(sqxy1) newxy2 = transf.apply_to_point(sqxy2) newxy3 = transf.apply_to_point(sqxy3) node.set("cx", (newxy1[0] + newxy3[0]) / 2) node.set("cy", (newxy1[1] + newxy3[1]) / 2) edgex = math.sqrt( abs(newxy1[0] - newxy2[0])**2 + abs(newxy1[1] - newxy2[1])**2) edgey = math.sqrt( abs(newxy2[0] - newxy3[0])**2 + abs(newxy2[1] - newxy3[1])**2) if not isequal(edgex, edgey) and ( node.TAG == "circle" or not isequal(newxy2[0], newxy3[0]) or not isequal(newxy1[1], newxy2[1])): inkex.utils.errormsg( "Warning: Shape %s (%s) is approximate only, try Object to path first for better results" % (node.TAG, node.get("id"))) if node.TAG == "ellipse": node.set("rx", edgex / 2) node.set("ry", edgey / 2) else: node.set("r", edgex / 2) elif node.tag in [ inkex.addNS('rect', 'svg'), inkex.addNS('text', 'svg'), inkex.addNS('image', 'svg'), inkex.addNS('use', 'svg') ]: inkex.utils.errormsg( "Shape %s (%s) not yet supported, try Object to path first" % (node.TAG, node.get("id"))) else: # e.g. <g style="..."> self.scaleStrokeWidth(node, transf) for child in node.getchildren(): self.recursiveFuseTransform(child, transf)
def recursively_traverse_svg(self, node_list, trans_current=Transform(((1.0, 0.0, 0.0), (0.0, -1.0, 0.0))), parent_visibility='visible'): """ Recursively traverse the svg file to plot out all of the paths. The function keeps track of the composite transformation that should be applied to each path. This function handles path, group, line, rect, polyline, polygon, circle, ellipse and use (clone) elements. Notable elements not handled include text. Unhandled elements should be converted to paths in Inkscape. TODO: There's a lot of inlined code in the eggbot version of this that would benefit from the Entities method of dealing with things. """ for node in node_list: # Ignore invisible nodes v = node.get('visibility', parent_visibility) if v == 'inherit': v = parent_visibility if v == 'hidden' or v == 'collapse': pass # first apply the current matrix transform to this node's transform trans = Transform(node.get("transform")) trans_new = trans_current * trans if node.tag == inkex.addNS('g', 'svg') or node.tag == 'g': if node.get(inkex.addNS('groupmode', 'inkscape')) == 'layer': node.get(inkex.addNS('label', 'inkscape')) self.recursively_traverse_svg(node, trans_new, parent_visibility=v) elif node.tag == inkex.addNS('use', 'svg') or node.tag == 'use': ref_id = node.get(inkex.addNS('href', 'xlink')) if ref_id: # [1:] to ignore leading '#' in reference path = '//*[@id="%s"]' % ref_id[1:] ref_node = node.xpath(path) if ref_node: x = float(node.get('x', '0')) y = float(node.get('y', '0')) # Note: the transform has already been applied if (x != 0) or (y != 0): trans_new2 = trans_new * Transform('translate(%f,%f)' % (x, y)) else: trans_new2 = trans_new v = node.get('visibility', v) self.recursively_traverse_svg(ref_node, trans_new2, parent_visibility=v) else: pass else: pass elif not isinstance(node.tag, str): pass else: entity = self.make_entity(node, trans_new) if entity is None: inkex.errormsg( 'Warning: unable to draw object, please convert it to a path first. objID: ' + node.get('id'))
def parse(self): # 0.28222 scale determined by comparing pixels-per-mm in a default Inkscape file. # self.svgWidth = self.getLength('width', 354) * 0.28222 # self.svgHeight = self.getLength('height', 354) * 0.28222 self.recursively_traverse_svg(self.svg, Transform(((1.0, 0.0, 0), (0.0, -1.0, self.svgHeight))))
def exportEdgeCut(self, kicad_mod=False): x0 = 0 y0 = 0 mirror = 1.0 line_type = "fp_line" if kicad_mod else "gr_line" kicad_edgecut_string = "" i = 0 layerPath = '//svg:g[@inkscape:groupmode="layer"]' if (self.options.autoflatten): self.flatten_bezier() for layer in self.document.getroot().xpath(layerPath, namespaces=inkex.NSS): i += 1 label_attrib_name = "{%s}label" % layer.nsmap['inkscape'] if label_attrib_name not in layer.attrib: continue layer_name = (layer.attrib[label_attrib_name]) if layer_name != "Edge.Cuts": continue layer_trans = layer.get('transform') if layer_trans: layer_m = Transform(layer_trans).matrix else: layer_m = IDENTITY_MATRIX nodePath = ( '//svg:g[@inkscape:groupmode="layer"][%d]/descendant::svg:path' ) % i for node in self.document.getroot().xpath(nodePath, namespaces=inkex.NSS): p = node.path.to_arrays() points = [] if p: #sanity check if p[0][0] == 'M': t = node.get('transform') if t: trans = (Transform(layer_m) * Transform(t)).matrix else: trans = layer_m for path in p: if path[0] != "Z": x = (path[1][0]) y = (path[1][1]) xy = Transform(trans).apply_to_point([x, y]) points.append( self.coordToKicad([(xy[0] - x0), xy[1] * mirror - y0])) points_count = len(points) points.append(points[0]) for x in range(0, points_count): kicad_edgecut_string = kicad_edgecut_string + ( "(%s (start %f %f) (end %f %f) (layer Edge.Cuts) (width 0.1))\n" % (line_type, points[x][0], points[x][1], points[x + 1][0], points[x + 1][1])) return kicad_edgecut_string