def realistic_stitch(start, end): """Generate a stitch vector path given a start and end point.""" end = Point(*end) start = Point(*start) stitch_length = (end - start).length() stitch_center = (end + start) / 2.0 stitch_direction = (end - start) stitch_angle = math.atan2(stitch_direction.y, stitch_direction.x) stitch_length = max(0, stitch_length - 0.2 * PIXELS_PER_MM) # create the path by filling in the length in the template path = simplepath.parsePath(stitch_path % stitch_length) # rotate the path to match the stitch rotation_center_x = -stitch_length / 2.0 rotation_center_y = stitch_height / 2.0 simplepath.rotatePath(path, stitch_angle, cx=rotation_center_x, cy=rotation_center_y) # move the path to the location of the stitch simplepath.translatePath(path, stitch_center.x - rotation_center_x, stitch_center.y - rotation_center_y) return simplepath.formatPath(path)
def test_simplepath(self): """Test simplepath API""" import simplepath data = 'M12 34L56 78Z' path = simplepath.parsePath(data) self.assertEqual(path, [['M', [12., 34.]], ['L', [56., 78.]], ['Z', []]]) d_out = simplepath.formatPath(path) d_out = d_out.replace('.0', '') self.assertEqual(data.replace(' ', ''), d_out.replace(' ', '')) simplepath.translatePath(path, -3, -4) self.assertEqual(path, [['M', [9., 30.]], ['L', [53., 74.]], ['Z', []]]) simplepath.scalePath(path, 10, 20) self.assertEqual(path, [['M', [90., 600.]], ['L', [530., 1480.]], ['Z', []]]) simplepath.rotatePath(path, math.pi / 2.0, cx=5, cy=7) approxed = [[code, approx(coords)] for (code, coords) in path] self.assertEqual( approxed, [['M', [-588., 92.]], ['L', [-1468., 532.]], ['Z', []]])
def resolve_path(node, transform=False): path = simplepath.parsePath(node.attrib['d']) if transform and 'transform' in node.attrib: transforms = re.findall( "([a-z,A-Z]+)\(([0-9,\s,.,-]*)\)", node.attrib['transform']) for k, vs in transforms: sc = ' ' if ',' in vs: sc = ',' args = map(lambda v: float(v.strip()), vs.split(sc)) if k == 'translate': simplepath.translatePath(path, *args) elif k == 'scale': simplepath.scalePath(path, *args) elif k == 'rotate': simplepath.rotatePath(path, *args) else: error( "Invalid path data: contains unsupported transform %s" % k) return path
def effect(self): # get user-entered params x_scale = self.options.x_scale y_scale = self.options.y_scale t_start = self.options.t_start t_end = self.options.t_end n_steps = self.options.n_steps fps = self.options.fps dt = self.options.dt x_eqn = self.options.x_eqn y_eqn = self.options.y_eqn x_size_eqn = self.options.x_size_eqn y_size_eqn = self.options.y_size_eqn theta_eqn = self.options.theta_eqn # get doc root svg = self.document.getroot() doc_w = self.unittouu(svg.get('width')) doc_h = self.unittouu(svg.get('height')) # get selected items and validate selected = pathmodifier.zSort(self.document.getroot(), self.selected.keys()) if not selected: inkex.errormsg( 'Exactly two objects must be selected: a rect and a template. See "help" for details.' ) return elif len(selected) != 2: inkex.errormsg( 'Exactly two objects must be selected: a rect and a template. See "help" for details.' ) return # rect rect = self.selected[selected[0]] if not rect.tag.endswith('rect'): inkex.errormsg('Bottom object must be rect. See "help" for usage.') return # object obj = self.selected[selected[1]] if not (obj.tag.endswith('path') or obj.tag.endswith('g')): inkex.errormsg( 'Template object must be path or group of paths. See "help" for usage.' ) return if obj.tag.endswith('g'): children = obj.getchildren() if not all([ch.tag.endswith('path') for ch in children]): msg = 'All elements of group must be paths, but they are: ' msg += ', '.join(['{}'.format(ch) for ch in children]) inkex.errormsg(msg) return objs = children is_group = True else: objs = [obj] is_group = False # get rect params w = float(rect.get('width')) h = float(rect.get('height')) x_rect = float(rect.get('x')) y_rect = float(rect.get('y')) # lower left corner x_0 = x_rect y_0 = y_rect + h # get object path(s) obj_ps = [simplepath.parsePath(obj_.get('d')) for obj_ in objs] n_segs = [len(obj_p_) for obj_p_ in obj_ps] obj_p = sum(obj_ps, []) # compute travel parameters if not n_steps: # compute dt if dt == 0: dt = 1. / fps ts = np.arange(t_start, t_end, dt) else: ts = np.linspace(t_start, t_end, n_steps) # compute xs, ys, stretches, and rotations in arbitrary coordinates xs = np.nan * np.zeros(len(ts)) ys = np.nan * np.zeros(len(ts)) x_sizes = np.nan * np.zeros(len(ts)) y_sizes = np.nan * np.zeros(len(ts)) thetas = np.nan * np.zeros(len(ts)) for ctr, t in enumerate(ts): xs[ctr] = eval(x_eqn) ys[ctr] = eval(y_eqn) x_sizes[ctr] = eval(x_size_eqn) y_sizes[ctr] = eval(y_size_eqn) thetas[ctr] = eval(theta_eqn) * pi / 180 # ensure no Infs if np.any(np.isinf(xs)): raise Exception('Inf detected in x(t), please remove.') return if np.any(np.isinf(ys)): raise Exception('Inf detected in y(t), please remove.') return if np.any(np.isinf(x_sizes)): raise Exception('Inf detected in x_size(t), please remove.') return if np.any(np.isinf(y_sizes)): raise Exception('Inf detected in y_size(t), please remove.') return if np.any(np.isinf(thetas)): raise Exception('Inf detected in theta(t), please remove.') return # convert to screen coordinates xs *= (w / x_scale) xs += x_0 ys *= (-h / y_scale) # neg sign to invert y for inkscape screen ys += y_0 # get obj center b_box = simpletransform.refinedBBox( cubicsuperpath.CubicSuperPath(obj_p)) c_x = 0.5 * (b_box[0] + b_box[1]) c_y = 0.5 * (b_box[2] + b_box[3]) # get rotation anchor if any([k.endswith('transform-center-x') for k in obj.keys()]): k_r_x = [ k for k in obj.keys() if k.endswith('transform-center-x') ][0] k_r_y = [ k for k in obj.keys() if k.endswith('transform-center-y') ][0] r_x = c_x + float(obj.get(k_r_x)) r_y = c_y - float(obj.get(k_r_y)) else: r_x, r_y = c_x, c_y paths = [] # compute new paths for x, y, x_size, y_size, theta in zip(xs, ys, x_sizes, y_sizes, thetas): path = deepcopy(obj_p) # move to origin simplepath.translatePath(path, -x_0, -y_0) # move rotation anchor accordingly r_x_1 = r_x - x_0 r_y_1 = r_y - y_0 # scale simplepath.scalePath(path, x_size, y_size) # scale rotation anchor accordingly r_x_2 = r_x_1 * x_size r_y_2 = r_y_1 * y_size # move to final location simplepath.translatePath(path, x, y) # move rotation anchor accordingly r_x_3 = r_x_2 + x r_y_3 = r_y_2 + y # rotate simplepath.rotatePath(path, -theta, cx=r_x_3, cy=r_y_3) paths.append(path) parent = self.current_layer group = inkex.etree.SubElement(parent, inkex.addNS('g', 'svg'), {}) for path in paths: if is_group: group_ = inkex.etree.SubElement(group, inkex.addNS('g', 'svg'), {}) path_components = split(path, n_segs) for path_component, child in zip(path_components, children): attribs = {k: child.get(k) for k in child.keys()} attribs['d'] = simplepath.formatPath(path_component) child_copy = inkex.etree.SubElement( group_, child.tag, attribs) else: attribs = {k: obj.get(k) for k in obj.keys()} attribs['d'] = simplepath.formatPath(path) obj_copy = inkex.etree.SubElement(group, obj.tag, attribs)
def rotatePath(self, a, x=0, y=0): simplepath.rotatePath(self.data, a, x, y)
def rotatePath(self,a,x=0,y=0): simplepath.rotatePath(self.data,a,x,y)