def process_clone(self, node): """Process a clone node, looking for internal paths""" trans = node.get('transform') x = node.get('x') y = node.get('y') mat = Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]) if trans: mat *= Transform(trans) if x: mat *= Transform([[1.0, 0.0, float(x)], [0.0, 1.0, 0.0]]) if y: mat *= Transform([[1.0, 0.0, 0.0], [0.0, 1.0, float(y)]]) # push transform if trans or x or y: self.groupmat.append(Transform(self.groupmat[-1]) * mat) # get referenced node refid = node.get('xlink:href') refnode = self.svg.getElementById(refid[1:]) if refnode is not None: if isinstance(refnode, Group): self.process_group(refnode) elif isinstance(refnode, Use): self.process_clone(refnode) else: self.process_shape(refnode, self.groupmat[-1]) # pop transform if trans or x or y: self.groupmat.pop()
def match(p1, p2, a1, a2): x = 0 y = 1 # distances dp = [p2[x] - p1[x], p2[y] - p1[y]] da = [a2[x] - a1[x], a2[y] - a1[y]] # angles angle_p = math.atan2(dp[x], dp[y]) angle_a = math.atan2(da[x], da[y]) # radians #rp = math.sqrt( dp[x]*dp[x] + dp[y]*dp[y] ) #ra = math.sqrt( da[x]*da[x] + da[y]*da[y] ) rp = math.hypot(dp[x], dp[y]) ra = math.hypot(da[x], da[y]) # scale scale = ra / rp # transforms in the order they are applied t1 = Transform("translate(%f,%f)" % (-p1[x], -p1[y])).matrix #t2 = Transform( "rotate(%f)"%(-angle_p) ).matrix #t3 = Transform( "scale(%f,%f)"%(scale,scale) ).matrix #t4 = Transform( "rotate(%f)"%angle_a ).matrix t2 = rotateTransform(-angle_p) t3 = scale_transform(scale, scale) t4 = rotateTransform(angle_a) t5 = Transform("translate(%f,%f)" % (a1[x], a1[y])).matrix # transforms in the order they are multiplied t = t5 t = Transform(t) * Transform(t4) t = Transform(t) * Transform(t3) t = Transform(t) * Transform(t2) t = Transform(t) * Transform(t1) # return the combined transform return t
def process_group(self, group): """Process group elements""" if isinstance(group, Layer): style = group.style if style.get( 'display', '' ) == 'none' and self.options.layer_option and self.options.layer_option == 'visible': return layer = group.label if self.options.layer_name and self.options.layer_option == 'name': if not layer.lower() in self.options.layer_name: return layer = layer.replace(' ', '_') if layer in self.layers: self.layer = layer trans = group.get('transform') if trans: self.groupmat.append( Transform(self.groupmat[-1]) * Transform(trans)) for node in group: if isinstance(node, Group): self.process_group(node) elif isinstance(node, Use): self.process_clone(node) else: self.process_shape(node, self.groupmat[-1]) if trans: self.groupmat.pop()
def process_clone(self, node): trans = node.get('transform') x = node.get('x') y = node.get('y') mat = Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]) if trans: mat *= Transform(trans) if x: mat *= Transform([[1.0, 0.0, float(x)], [0.0, 1.0, 0.0]]) if y: mat *= Transform([[1.0, 0.0, 0.0], [0.0, 1.0, float(y)]]) # push transform if trans or x or y: self.groupmat.append(Transform(self.groupmat[-1]) * mat) # get referenced node refnode = node.href if refnode is not None: if isinstance(refnode, inkex.Group): self.process_group(refnode) elif refnode.tag == 'svg:use': self.process_clone(refnode) else: self.process_shape(refnode, self.groupmat[-1]) # pop transform if trans or x or y: self.groupmat.pop()
def effect(self): if len(self.svg.selected) != 1: sys.exit("Error: You must select exactly one rectangle") if list(self.svg.selected.items())[0][1].tag != inkex.addNS('rect','svg'): sys.exit("Error: You must select exactly one rectangle") sel = None for pathId in self.svg.selected: sel = self.svg.selected[pathId] mat = [[1,0,0],[0,1,0]] cur = sel while cur is not None: curMat = Transform(cur.get('transform')) mat = Transform(curMat) * Transform(mat) cur = cur.getparent() [x,y,w,h] = map(lambda attr: float(sel.get(attr)), ['x','y','width','height']) (x1,y1) = transformPoint(mat, (x,y)) (x2,y2) = transformPoint(mat, (x+w,y+h)) ww = x2-x1 hh = y2-y1 root = self.svg.getElement('//svg:svg'); root.set('viewBox', '%f %f %f %f' % (x1,y1,ww,hh)) root.set('width', str(ww)) root.set('height', str(hh))
def snap_path_scale(self, elem, parent_transform=None): path = elem.original_path.to_arrays() transform = elem.transform * Transform(parent_transform) bbox = elem.bounding_box() # In case somebody tries to snap a 0-high element, # or a curve/arc with all nodes in a line, and of course # because we should always check for divide-by-zero! if not bbox.width or not bbox.height: return width, height = bbox.width, bbox.height min_xy, max_xy = bbox.minimum, bbox.maximum rescale = round(width) / width, round(height) / height min_xy = transform_point(transform, min_xy, inverse=True) max_xy = transform_point(transform, max_xy, inverse=True) for i in range(len(path)): translate = Transform(translate=min_xy) self.transform_path_node(-translate, path, i) # center transform self.transform_path_node(Transform(scale=rescale), path, i) self.transform_path_node(translate, path, i) # uncenter transform elem.original_path = path
def _merge_transform(node, transform): """Propagate style and transform to remove inheritance Originally from https://github.com/nikitakit/svg2sif/blob/master/synfig_prepare.py#L370 """ def _get_dimension(s="1024"): """Convert an SVG length string from arbitrary units to pixels""" if s == "": return 0 try: last = int(s[-1]) except: last = None if type(last) == int: return float(s) elif s[-1] == "%": return 1024 elif s[-2:] == "px": return float(s[:-2]) elif s[-2:] == "pt": return float(s[:-2]) * 1.25 elif s[-2:] == "em": return float(s[:-2]) * 16 elif s[-2:] == "mm": return float(s[:-2]) * 3.54 elif s[-2:] == "pc": return float(s[:-2]) * 15 elif s[-2:] == "cm": return float(s[:-2]) * 35.43 elif s[-2:] == "in": return float(s[:-2]) * 90 else: return 1024 # Compose the transformations if node.tag == addNS("svg", "svg") and node.get("viewBox"): vx, vy, vw, vh = [ _get_dimension(x) for x in node.get("viewBox").split() ] dw = _get_dimension(node.get("width", vw)) dh = _get_dimension(node.get("height", vh)) t = ("translate(%f, %f) scale(%f, %f)" % (-vx, -vy, dw / vw, dh / vh)) this_transform = simpletransform.parseTransform(t, transform) this_transform = simpletransform.parseTransform( node.get("transform"), this_transform) del node.attrib["viewBox"] else: this_transform = Transform(transform) * Transform( node.get("transform")) # this_transform = simpletransform.parseTransform(node.get("transform"), transform) # deprecated, https://inkscape.gitlab.io/inkscape/doxygen-extensions/simpletransform_8py_source.html # Set the node's transform attrib # node.set("transform", simpletransform.formatTransform(this_transform)) # deprecated node.set("transform", str(this_transform))
def effect(self): unit_factor = 1.0 / self.svg.uutounit(1.0, self.options.unit) for id, node in self.svg.selected.items(): #get recent XY coordinates (top left corner of the bounding box) bbox = node.bounding_box() new_horiz_scale = self.options.expected_size * unit_factor / bbox.width new_vert_scale = self.options.expected_size * unit_factor / bbox.height if self.options.scale_type == "Horizontal": translation_matrix = [[new_horiz_scale, 0.0, 0.0], [0.0, 1.0, 0.0]] elif self.options.scale_type == "Vertical": translation_matrix = [[1.0, 0.0, 0.0], [0.0, new_vert_scale, 0.0]] else: #Uniform translation_matrix = [[new_horiz_scale, 0.0, 0.0], [0.0, new_vert_scale, 0.0]] node.transform = Transform(translation_matrix) * node.transform # now that the node moved we need to get the nodes XY coordinates again to fix them bbox_new = node.bounding_box() #inkex.utils.debug(cx) #inkex.utils.debug(cy) #inkex.utils.debug(cx_new) #inkex.utils.debug(cy_new) # We remove the transformation attribute from SVG XML tree in case it's regular path. In case the node is an object like arc,circle, ellipse or star we have the attribute "sodipodi:type". Then we do not play with it's path because the size transformation will not apply (this code block is ugly) if node.get('sodipodi:type') is None and 'd' in node.attrib: #inkex.utils.debug("it's a path!") d = node.get('d') p = CubicSuperPath(d) transf = Transform(node.get("transform", None)) if 'transform' in node.attrib: del node.attrib['transform'] p = Path(p).to_absolute().transform(transf, True) node.set('d', Path(CubicSuperPath(p).to_path())) #else: #inkex.utils.debug("it's an object!") #we perform second translation to reset the center of the path translation_matrix = [[ 1.0, 0.0, bbox.left - bbox_new.left + (bbox.width - bbox_new.width) / 2 ], [ 0.0, 1.0, bbox.top - bbox_new.top + (bbox.height - bbox_new.height) / 2 ]] node.transform = Transform(translation_matrix) * node.transform
def process_shape(self, node, mat): rgb = (0, 0, 0) style = node.get('style') if style: style = dict(inkex.Style.parse_str(style)) if 'stroke' in style: if style['stroke'] and style['stroke'] != 'none' and style[ 'stroke'][0:3] != 'url': rgb = inkex.Color(style['stroke']).to_rgb() hsl = colors.rgb_to_hsl(rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0) self.color = 7 # default is black if hsl[2]: self.color = 1 + (int(6 * hsl[0] + 0.5) % 6) # use 6 hues if not isinstance(node, (PathElement, Rectangle, Line, Circle, Ellipse)): return # Transforming /after/ superpath is more reliable than before # because of some issues with arcs in transformations for sub in node.path.to_superpath().transform( Transform(mat) * node.transform): for i in range(len(sub) - 1): s = sub[i] e = sub[i + 1] if s[1] == s[2] and e[0] == e[1]: if self.options.POLY: self.LWPOLY_line([s[1], e[1]]) else: self.dxf_line([s[1], e[1]]) elif self.options.ROBO: self.ROBO_spline([s[1], s[2], e[0], e[1]]) else: self.dxf_spline([s[1], s[2], e[0], e[1]])
def addtransform(self, el, trnsfrm): # Adds a transform and fuses it to any paths, preserving stroke myt = el.get('transform') prt = self.getparenttransform(el) # if parent is transformed, use transformed coordinate system if myt == None: newtr = (-prt) * trnsfrm * prt else: newtr = (-prt) * trnsfrm * prt * Transform(myt) sw = dh.Get_Composed_Width(el, 'stroke-width') sd = dh.Get_Composed_List(el, 'stroke-dasharray') el.set('transform', newtr) # Add the new transform if not (el.typename in ['TextElement', 'Image', 'Group']): # sty=str(el.composed_style()); # sw=dh.Get_Style_Comp(sty,'stroke-width'); ApplyTransform().recursiveFuseTransform(el) if sw is not None: nw = float(dh.Get_Style_Comp(el.get('style'), 'stroke-width')) sw = nw * sw / dh.Get_Composed_Width(el, 'stroke-width') dh.Set_Style_Comp(el, 'stroke-width', str(sw)) # fix width if not (sd == None) and not (sd == 'none'): nd = dh.Get_Style_Comp(el.get('style'), 'stroke-dasharray').split(',') cd = dh.Get_Composed_List(el, 'stroke-dasharray') for ii in range(len(sd)): sd[ii] = float(nd[ii]) * sd[ii] / cd[ii] dh.Set_Style_Comp(el, 'stroke-dasharray', str(sd).strip('[').strip(']'))
def _expand_defs(root): from inkex import Transform, ShapeElement from copy import deepcopy for el in root: if isinstance(el, inkex.Use): # <group> element will replace <use> node group = inkex.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 effect(self): for node in self.svg.selected.values(): min_bbox_angle = rotate_helper.optimal_rotations(node)[1] if min_bbox_angle is not None: node.transform = Transform( rotate_helper.rotate_matrix( node, min_bbox_angle)) * node.transform
def map_points_to_morph(axes, percentage, morphed, num_points): # rename the axes for legibility y_cubic_0 = axes[1] y_cubic_1 = axes[3] x_cubic_0 = axes[0] x_cubic_1 = axes[2] # morph each point for i in range(0, num_points): x = i * 2 y = i * 2 + 1 # tween between the morphed y axes according to the x percentage tweened_y = tween_cubic(y_cubic_0, y_cubic_1, percentage[x]) # get 2 points on the morphed x axes x_spot_0 = calc_point_on_cubic(x_cubic_0, percentage[x]) x_spot_1 = calc_point_on_cubic(x_cubic_1, percentage[x]) # create a transform that stretches the # y axis tween between these 2 points y_anchor_0 = [tweened_y[0], tweened_y[1]] y_anchor_1 = [tweened_y[6], tweened_y[7]] x_transform = match(y_anchor_0, y_anchor_1, x_spot_0, x_spot_1) # map the y axis tween to the 2 points by # applying the stretch transform for j in range(4): x2 = j * 2 y2 = j * 2 + 1 point_on_y = [tweened_y[x2], tweened_y[y2]] Transform(x_transform).apply_to_point(point_on_y) tweened_y[x2] = point_on_y[0] tweened_y[y2] = point_on_y[1] # get the point on the tweened and transformed y axis # according to the y percentage morphed_point = calc_point_on_cubic(tweened_y, percentage[y]) morphed[x] = morphed_point[0] morphed[y] = morphed_point[1]
def mapPointsToMorph(axes, percentage, morphed, numPts): # rename the axes for legibility yCubic0 = axes[1] yCubic1 = axes[3] xCubic0 = axes[0] xCubic1 = axes[2] # morph each point for i in range(0, numPts): x = i * 2 y = i * 2 + 1 # tween between the morphed y axes according to the x percentage tweenedY = tweenCubic(yCubic0, yCubic1, percentage[x]) # get 2 points on the morphed x axes xSpot0 = pointOnCubic(xCubic0, percentage[x]) xSpot1 = pointOnCubic(xCubic1, percentage[x]) # create a transform that stretches the y axis tween between these 2 points yAnchor0 = [tweenedY[0], tweenedY[1]] yAnchor1 = [tweenedY[6], tweenedY[7]] xTransform = match(yAnchor0, yAnchor1, xSpot0, xSpot1) # map the y axis tween to the 2 points by applying the stretch transform for j in range(0, 4): x2 = j * 2 y2 = j * 2 + 1 pointOnY = [tweenedY[x2], tweenedY[y2]] Transform(xTransform).apply_to_point(pointOnY) tweenedY[x2] = pointOnY[0] tweenedY[y2] = pointOnY[1] # get the point on the tweened and transformed y axis according to the y percentage morphedPoint = pointOnCubic(tweenedY, percentage[y]) morphed[x] = morphedPoint[0] morphed[y] = morphedPoint[1]
def ungroup(groupnode): # Pops a node out of its group, unless it's already in a layer or the base # Preserves style and clipping # inkex.utils.debug(groupnode.typename) global ncall ncall += 1 node_index = list(groupnode.getparent()).index( groupnode) # parent's location in grandparent # node_style = simplestyle.parseStyle(node_parent.get("style")) # deprecated node_style = dict(Style.parse_str(groupnode.get("style"))) # node_transform = simpletransform.parseTransform(node_parent.get("transform")) # deprecated node_transform = Transform(groupnode.get("transform")).matrix node_clippathurl = groupnode.get('clip-path') els = groupnode.getchildren() for el in list(reversed(els)): if not (isinstance(el, (NamedView, Defs, Metadata, ForeignObject))): _merge_transform(el, node_transform) _merge_style(el, node_style) _merge_clippath(el, node_clippathurl) groupnode.getparent().insert(node_index + 1, el) # places above # node_parent.getparent().insert(node_index,node); # places below if len(groupnode.getchildren()) == 0: groupnode.delete()
def test_group_with_number_of_rects_translated(self): group = Group() dx, dy = 5, 10 xmin, ymin = 1000, 1000 xmax, ymax = -1000, -1000 rects = [] for x, y, w, h in [ (10, 20, 5, 7), (30, 40, 5, 7), ]: rect = Rectangle(width=str(w), height=str(h), x=str(x), y=str(y)) rects.append(rect) xmin = min(xmin, x) xmax = max(xmax, x + w) ymin = min(ymin, y) ymax = max(ymax, y + h) group.add(rect) group.transform = Transform(translate=(dx, dy)) self.assert_bounding_box_is_equal(group, (dx + xmin, dx + xmax), (dy + ymin, dy + ymax))
def test_apply_transform(self): """Transformation can be applied to path""" path = self.svg.getElementById('D') path.transform = Transform(translate=(10, 10)) self.assertEqual(path.get('d'), 'M30,130 L60,130 L60,120 L70,140 L60,160 L60,150 L30,150') path.apply_transform() self.assertEqual(path.get('d'), 'M 40 140 L 70 140 L 70 130 L 80 150 ' 'L 70 170 L 70 160 L 40 160') self.assertFalse(path.transform)
def op_transform(self, layers, mtx, name="Transform", is_end=False): """Apply a matrix transformation to the given layers Keyword arguments: layers -- list of layers mtx -- transformation matrix name -- name of the Transform layer that is added is_end -- set to True if layers are at the end of a canvas Returns: list of layers """ if not layers: return layers if mtx is None or mtx == [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]: return layers src_tl = [100, 100] src_br = [200, 200] dest_tl = [100, 100] dest_tr = [200, 100] dest_br = [200, 200] dest_bl = [100, 200] dest_tl = Transform(mtx).apply_to_point(dest_tl) dest_tr = Transform(mtx).apply_to_point(dest_tr) dest_br = Transform(mtx).apply_to_point(dest_br) dest_bl = Transform(mtx).apply_to_point(dest_bl) warp = self.create_layer("warp", name, params={ "src_tl": self.coor_svg2sif(src_tl), "src_br": self.coor_svg2sif(src_br), "dest_tl": self.coor_svg2sif(dest_tl), "dest_tr": self.coor_svg2sif(dest_tr), "dest_br": self.coor_svg2sif(dest_br), "dest_bl": self.coor_svg2sif(dest_bl) }) if is_end: return layers + [warp] else: return self.op_encapsulate(layers + [warp])
def process_group(self, group): if isinstance(group, inkex.Layer): style = group.style if 'display' in style: if style['display'] == 'none' and self.visibleLayers: return trans = group.get('transform') if trans: self.groupmat.append( Transform(self.groupmat[-1]) * Transform(trans)) for node in group: if isinstance(node, Group): self.process_group(node) elif isinstance(node, Use): self.process_clone(node) else: self.process_shape(node, self.groupmat[-1]) if trans: self.groupmat.pop()
def process_shape(self, node, mat): """Process shape""" rgb = (0, 0, 0) # stroke color fillcolor = None # fill color stroke = 1 # pen width in printer pixels # Very NB : If the pen width is greater than 1 then the output will Not be a vector output ! style = node.style if style: if 'stroke' in style: if style['stroke'] and style['stroke'] != 'none' and style[ 'stroke'][0:3] != 'url': rgb = inkex.Color(style['stroke']).to_rgb() if 'stroke-width' in style: stroke = self.svg.unittouu( style['stroke-width']) / self.svg.unittouu('1px') stroke = int(stroke * self.scale) if 'fill' in style: if style['fill'] and style['fill'] != 'none' and style['fill'][ 0:3] != 'url': fill = inkex.Color(style['fill']).to_rgb() fillcolor = fill[0] + 256 * fill[1] + 256 * 256 * fill[2] color = rgb[0] + 256 * rgb[1] + 256 * 256 * rgb[2] if isinstance(node, PathElement): p = node.path.to_superpath() if not p: return elif isinstance(node, Rectangle): x = float(node.get('x')) y = float(node.get('y')) width = float(node.get('width')) height = float(node.get('height')) p = [[[x, y], [x, y], [x, y]]] p.append([[x + width, y], [x + width, y], [x + width, y]]) p.append([[x + width, y + height], [x + width, y + height], [x + width, y + height]]) p.append([[x, y + height], [x, y + height], [x, y + height]]) p.append([[x, y], [x, y], [x, y]]) p = [p] else: return mat += node.transform p = Path(p).transform(Transform(mat)).to_arrays() hPen = mygdi.CreatePen(0, stroke, color) mygdi.SelectObject(self.hDC, hPen) self.emit_path(p) if fillcolor is not None: brush = LOGBRUSH(0, fillcolor, 0) hBrush = mygdi.CreateBrushIndirect(ctypes.addressof(brush)) mygdi.SelectObject(self.hDC, hBrush) mygdi.BeginPath(self.hDC) self.emit_path(p) mygdi.EndPath(self.hDC) mygdi.FillPath(self.hDC) return
def test_group_nested_transform(self): group = Group() x, y = 10, 20 w, h = 7, 20 scale = 2 rect = Rectangle(width=str(w), height=str(h), x=str(x), y=str(y)) rect.transform = Transform(rotate=45, scale=scale) group.add(rect) group.transform = Transform(rotate=-45) # rotation is compensated, but scale is not a = rect.composed_transform() self.assert_bounding_box_is_equal(group, (scale * x, scale * (x + w)), (scale * y, scale * (y + h)))
def test_path_straight_line_scaled(self): path = PathElement() scale_x = 2 scale_y = 3 path.set_path("M 10 10 " "L 20 20") path.transform = Transform(scale=(scale_x, scale_y)) self.assert_bounding_box_is_equal(path, (scale_x * 10, 20 * scale_x), (scale_y * 10, 20 * scale_y))
def convert_url(self, url_id, mtx, d): """Return a list Synfig layers that represent the gradient with the given id""" gradient = d.get_gradient(url_id) if gradient is None: # Patterns and other URLs not supported return [None] if gradient["type"] == "linear": layer = d.create_layer("linear_gradient", url_id, d.gradient_to_params(gradient), guids={"gradient": gradient["stops_guid"]}) if gradient["type"] == "radial": layer = d.create_layer("radial_gradient", url_id, d.gradient_to_params(gradient), guids={"gradient": gradient["stops_guid"]}) trm = Transform(mtx) * Transform(gradient["mtx"]) return d.op_transform([layer], trm.matrix)
def snap_path_pos(self, elem, parent_transform=None): path = elem.original_path.to_arrays() transform = elem.transform * Transform(parent_transform) bbox = elem.bounding_box() min_xy, max_xy = bbox.minimum, bbox.maximum fractional_offset = min_xy[0] - round(min_xy[0]), min_xy[1] - round( min_xy[1]) - self.document_offset fractional_offset = transform_dimensions(transform, fractional_offset[0], fractional_offset[1], inverse=True) for i in range(len(path)): self.transform_path_node(-Transform(translate=fractional_offset), path, i) path = str(inkex.Path(path)) if elem.get('inkscape:original-d'): elem.set('inkscape:original-d', path) else: elem.set('d', path)
def effect(self): offset = 1.0 #in documents' units # create a new bounding box and get the bbox size of all elements of the document (we cannot use the page's bbox) bbox = inkex.BoundingBox() for element in self.svg.root.getchildren(): if isinstance(element, inkex.ShapeElement): bbox += element.bounding_box() # adjust the viewBox to the bbox size and add the desired offset self.document.getroot().attrib[ 'viewBox'] = f'{-offset} {-offset} {bbox.width + offset * 2} {bbox.height + offset * 2}' self.document.getroot( ).attrib['width'] = f'{bbox.width + offset * 2}' + self.svg.unit self.document.getroot( ).attrib['height'] = f'{bbox.height + offset * 2}' + self.svg.unit # translate all elements to fit the adjusted viewBox mat = Transform("translate(%f, %f)" % (-bbox.left, -bbox.top)).matrix for element in self.svg.root.getchildren(): if isinstance(element, inkex.ShapeElement): element.transform = Transform(mat) * element.transform
def effect(self): mesh = om.read_trimesh(self.options.inputfile) fullUnfolded, unfoldedComponents = unfold(mesh) # Compute maxSize of the components # All components must be scaled to the same size as the largest component maxSize = 0 for unfolding in unfoldedComponents: [xmin, ymin, boxSize] = findBoundingBox(unfolding[0]) if boxSize > maxSize: maxSize = boxSize # Create a new container group to attach all paperfolds paperfoldMainGroup = self.document.getroot().add( inkex.Group(id=self.svg.get_unique_id( "paperfold-"))) #make a new group at root level for i in range(len(unfoldedComponents)): paperfoldPageGroup = writeSVG(self, unfoldedComponents[i], maxSize, self.options.printNumbers) #translate the groups next to each other to remove overlappings if i != 0: previous_bbox = paperfoldMainGroup[i - 1].bounding_box() this_bbox = paperfoldPageGroup.bounding_box() paperfoldPageGroup.set( "transform", "translate(" + str(previous_bbox.left + previous_bbox.width - this_bbox.left) + ", 0.0)") paperfoldMainGroup.append(paperfoldPageGroup) #apply scale factor translation_matrix = [[self.options.scalefactor, 0.0, 0.0], [0.0, self.options.scalefactor, 0.0]] paperfoldMainGroup.transform = Transform( translation_matrix) * paperfoldMainGroup.transform #paperfoldMainGroup.set('transform', 'scale(%f,%f)' % (self.options.scalefactor, self.options.scalefactor)) #adjust canvas to the inserted unfolding if self.options.resizetoimport: bbox = paperfoldMainGroup.bounding_box() namedView = self.document.getroot().find( inkex.addNS('namedview', 'sodipodi')) doc_units = namedView.get(inkex.addNS('document-units', 'inkscape')) root = self.svg.getElement('//svg:svg') offset = self.svg.unittouu( str(self.options.extraborder) + self.options.extraborder_units) root.set( 'viewBox', '%f %f %f %f' % (bbox.left - offset, bbox.top - offset, bbox.width + 2 * offset, bbox.height + 2 * offset)) root.set('width', str(bbox.width + 2 * offset) + doc_units) root.set('height', str(bbox.height + 2 * offset) + doc_units)
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 import Transform scale_transform = Transform("scale(%f)" % relative_scale) old_transform = Transform(ref_node.transform) # Account for vertical flipping of nodes created via pstoedit in TexText <= 0.11.x revert_flip = Transform("scale(1)") if ref_node.get_meta("pdfconverter", "pstoedit") == "pstoedit": revert_flip = Transform(matrix=((1, 0, 0), (0, -1, 0))) # vertical reflection composition = scale_transform * old_transform * revert_flip # 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 snap_transform(self, elem): # Only snaps the x/y translation of the transform, nothing else. # Doesn't take any parent_transform into account -- assumes # that the parent's transform has already been snapped. transform = elem.transform # if we've got any skew/rotation, get outta here if transform.c or transform.b: raise TransformError( "TR: Selection contains transformations with skew/rotation") trm = list(transform.to_hexad()) trm[4] = round(transform.e) trm[5] = round(transform.f) elem.transform *= Transform(trm)
def test_regular_rectangle_scaled(self): x, y = 10, 20 w, h = 7, 20 scale_x = 2 scale_y = 3 rect = Rectangle(width=str(w), height=str(h), x=str(x), y=str(y)) rect.transform = Transform(scale=(scale_x, scale_y)) self.assert_bounding_box_is_equal(rect, (scale_x * x, scale_x * (x + w)), (scale_y * y, scale_y * (y + h)))
def effect(self): w = self.svg.width h = self.svg.height s = 1 / self.svg.unittouu('1mm') w = w * s h = h * s #print(f"w: {w} h: {h} s: {s}", file=sys.stderr) bbox = Rect() bbox.setBounds(Vec2(0, 0), Vec2(w, h)) converter = nodeconverter.NodeToPolylines(bbox) converter.accept(self.document.getroot(), Transform([[s, 0.0, 0.0], [0.0, -s, h]])) planner = Planner() planner.optimize(converter.drawing) gcode = drawing_to_gcode(converter.drawing, self.options) if self.options.tab == 'filetab': filename = os.path.join( os.path.abspath(os.path.expanduser(self.options.directory)), os.path.splitext(self.options.filename)[0] + '.gcode') with open(filename, 'w') as f: f.write('\n'.join(gcode)) elif self.options.tab == 'serialtab': # Open serial port try: serial = Serial(self.options.serialPort, self.options.serialBaudRate, timeout=None) # Wake up grbl serial.write("\r\n\r\n") time.sleep(2) # Wait for grbl to initialize serial.flushInput() # Flush startup text in serial input # Stream g-code for line in gcode: serial.write(line + '\n') # Send g-code block grbl_out = serial.readline( ) # Wait for response with carriage return time.sleep(2) # Close serial port serial.close() except (OSError, serial.SerialException): inkex.errormsg(_("Problem connecting to serial device."))