def test_equals(self): """Segments should be equalitive""" self.assertEqual(Move(10, 10), Move(10, 10)) self.assertEqual(Line(10, 10), Line(10, 10)) self.assertEqual(line(10, 10), line(10, 10)) self.assertNotEqual(line(10, 10), Line(10, 10)) self.assertEqual(Horz(10), Line(10, 0)) self.assertEqual(Vert(10), Line(0, 10)) self.assertNotEqual(Vert(10), Horz(10))
def test_to_curves(self): """Segments can become curves""" self.assertRaises(ValueError, Move(0, 0).to_curve, None) self.assertEqual( Line(10, 10).to_curve(Vector2d(10, 5)), (10, 5, 10, 10, 10, 10)) self.assertEqual( Horz(10).to_curve(Vector2d(10, 5)), (10, 5, 10, 5, 10, 5)) self.assertEqual( Vert(10).to_curve(Vector2d(5, 10)), (5, 10, 5, 10, 5, 10)) self.assertEqual( Curve(5, 5, 10, 10, 4, 4).to_curve(Vector2d(0, 0)), (5, 5, 10, 10, 4, 4)) self.assertEqual( Smooth(10, 10, 4, 4).to_curve(Vector2d(4, 4), Vector2d(10, 10)), (-2, -2, 10, 10, 4, 4), ) self.assertAlmostTuple( Quadratic(10, 10, 4, 4).to_curve(Vector2d(0, 0)).args, (6.666666666666666, 6.666666666666666, 8, 8, 4, 4), ) self.assertAlmostTuple( TepidQuadratic(4, 4).to_curve(Vector2d(14, 19), Vector2d(11, 12)).args, # (20.666666666666664, 30, 17.333333333333332, 25, 4, 4), (15.999999999999998, 23.666666666666664, 12.666666666666666, 18.666666666666664, 4, 4), ) curves = list(Arc(50, 50, 0, 0, 1, 85, 85).to_curves(Vector2d(0, 0))) self.assertEqual(len(curves), 3) self.assertAlmostTuple( curves[0].args, (19.77590700610636, -5.4865851247611115, 38.18634924829132, -10.4196482558544, 55.44095225512604, -5.796291314453416)) self.assertAlmostTuple( curves[1].args, (72.69555526196076, -1.172934373052433, 86.17293437305243, 12.30444473803924, 90.79629131445341, 29.559047744873958)) self.assertAlmostTuple( curves[2].args, (95.41964825585441, 46.81365075170867, 90.4865851247611, 65.22409299389365, 77.85533905932738, 77.85533905932738)) def apply_to_curve(obj): obj.to_curve(Vector2d()) def apply_to_curves(obj): obj.to_curve(Vector2d()) self.assertRaises(ValueError, apply_to_curve, ZoneClose()) self.assertRaises(ValueError, apply_to_curves, zoneClose()) self.assertRaises(ValueError, apply_to_curve, Move(0, 0)) self.assertRaises(ValueError, apply_to_curves, move(0, 0))
def draw_line(x1, y1, x2, y2, width, name, parent): elem = parent.add(inkex.PathElement()) elem.style = { 'stroke': '#000000', 'stroke-width': str(width), 'fill': 'none' } elem.set('inkscape:label', name) elem.path = [Move(x1, y1), Line(x2, y2)]
def plot_beam(beam: List[Tuple[Ray, float]], node: inkex.ShapeElement) -> None: path = inkex.Path() if len(beam) > 0: path += [Move(beam[0][0].origin[0], beam[0][0].origin[1])] for ray, t in beam: p1 = ray.origin + t * ray.direction path += [Line(p1[0], p1[1])] element = node.getparent().add(inkex.PathElement()) element.style = node.get("style") element.path = path.transform(-node.composed_transform())
def plot_beam(self, beam: List[Ray], node: inkex.ShapeElement) -> None: path = inkex.Path() if len(beam) > 0: path += [Move(beam[0].origin[0], beam[0].origin[1])] for ray in beam: p1 = ray.origin + ray.travel * ray.direction path += [Line(p1[0], p1[1])] element = self._beam_layer.add(inkex.PathElement()) # Need to convert to path to get the correct style for inkex.Use element.style = node.to_path_element().style element.path = path
def plot_beam(self, beam: List[Tuple[Ray, float]], node: inkex.ShapeElement) -> None: path = inkex.Path() if len(beam) > 0: path += [Move(beam[0][0].origin[0], beam[0][0].origin[1])] for ray, t in beam: p1 = ray.origin + t * ray.direction path += [Line(p1[0], p1[1])] svg = self.document.getroot() element = svg.add(inkex.PathElement()) element.style = node.get("style") element.path = path
def effect(self): for node in self.svg.selection.filter(inkex.PathElement): result = Path() prev = Vector2d() start = None for seg in node.path.to_absolute(): if start is None: start = seg.end_point(start, prev) if isinstance(seg, Curve): result += [ Move(seg.x2, seg.y2), Line(prev.x, prev.y), Move(seg.x3, seg.y3), Line(seg.x4, seg.y4), ] elif isinstance(seg, Quadratic): result += [ Move(seg.x2, seg.y2), Line(prev.x, prev.y), Move(seg.x2, seg.y2), Line(seg.x3, seg.y3) ] prev = seg.end_point(start, prev) if not result: continue elem = node.getparent().add(inkex.PathElement()) elem.path = result.transform(node.transform) elem.style = { 'stroke-linejoin': 'miter', 'stroke-width': '1.0px', 'stroke-opacity': '1.0', 'fill-opacity': '1.0', 'stroke': '#000000', 'stroke-linecap': 'butt', 'fill': 'none' }
def draw_poly(pts, face, st, name, parent): """Draw polygone""" style = {'stroke': '#000000', 'stroke-width': str(st.th), 'stroke-linejoin': st.linejoin, 'stroke-opacity': st.s_opac, 'fill': st.fill, 'fill-opacity': st.f_opac} path = inkex.Path() for facet in face: if not path: # for first point path.append(Move(pts[facet - 1][0], -pts[facet - 1][1])) else: path.append(Line(pts[facet - 1][0], -pts[facet - 1][1])) path.close() poly = parent.add(inkex.PathElement()) poly.label = name poly.style = style poly.path = path
def effect(self): for node in self.svg.selection.filter(inkex.PathElement): path = node.path.to_absolute() result = [] for cmd_proxy in path.proxy_iterator( ): # type: inkex.Path.PathCommandProxy prev = cmd_proxy.previous_end_point end = cmd_proxy.end_point if cmd_proxy.letter == 'M': result.append(Move(*cmd_proxy.args)) else: for seg in self.fractalize((prev.x, prev.y, end.x, end.y), self.options.subdivs, self.options.smooth): result.append(Line(*seg)) result.append(Line(end.x, end.y)) node.path = result
def test_random_path_1(self): import random from inkex.paths import Line, Vert, Horz, Curve, Move, Arc, Quadratic, TepidQuadratic, Smooth, ZoneClose klasses = (Line, Vert, Horz, Curve, Move, Quadratic) # , ZoneClose, Arc def random_segment(klass): args = [random.randint(1, 100) for _ in range(klass.nargs)] if klass is Arc: args[2] = 0 # random.randint(0, 1) args[3] = 0 # random.randint(0, 1) args[4] = 0 # random.randint(0, 1) return klass(*args) random.seed(2128506) # random.seed(datetime.now()) n_trials = 10 n_elements = 15 for i in range(n_trials): path = Path() path.append(Move(0, 0)) for j in range(n_elements): k = random.choice(klasses) path.append(random_segment(k)) if k is Curve: while random.randint(0, 1) == 1: path.append(random_segment(Smooth)) if k is Quadratic: while random.randint(0, 1) == 1: path.append(random_segment(TepidQuadratic)) pe = PathElement() pe.path = path ibb = self.get_inkscape_bounding_box(pe) self.assert_bounding_box_is_equal(pe, *ibb, disable_inkscape_check=True)
def process_segment(cmd_proxy, facegroup, delx, dely): """Process each segments""" segments = [] if isinstance(cmd_proxy.command, (Curve, Smooth, TepidQuadratic, Quadratic, Arc)): prev = cmd_proxy.previous_end_point for curve in cmd_proxy.to_curves(): bez = [prev] + curve.to_bez() prev = curve.end_point(cmd_proxy.first_point, prev) tees = [ t for t in beziertatslope(bez, (dely, delx)) if 0 < t < 1 ] tees.sort() if len(tees) == 1: one, two = beziersplitatt(bez, tees[0]) segments.append(Curve(*(one[1] + one[2] + one[3]))) segments.append(Curve(*(two[1] + two[2] + two[3]))) elif len(tees) == 2: one, two = beziersplitatt(bez, tees[0]) two, three = beziersplitatt(two, tees[1]) segments.append(Curve(*(one[1] + one[2] + one[3]))) segments.append(Curve(*(two[1] + two[2] + two[3]))) segments.append(Curve(*(three[1] + three[2] + three[3]))) else: segments.append(curve) elif isinstance(cmd_proxy.command, (Line, Curve)): segments.append(cmd_proxy.command) elif isinstance(cmd_proxy.command, ZoneClose): segments.append( Line(cmd_proxy.first_point.x, cmd_proxy.first_point.y)) elif isinstance(cmd_proxy.command, (Vert, Horz)): segments.append(cmd_proxy.command.to_line(cmd_proxy.end_point)) for seg in Path([Move(*cmd_proxy.previous_end_point)] + segments).proxy_iterator(): if isinstance(seg.command, Move): continue Motion.makeface(seg.previous_end_point, seg.command, facegroup, delx, dely)
def makeface(last, segment, facegroup, delx, dely): """translate path segment along vector""" elem = facegroup.add(inkex.PathElement()) npt = segment.translate([delx, dely]) # reverse direction of path segment if isinstance(segment, Curve): rev = Curve(npt.x3, npt.y3, npt.x2, npt.y2, last[0] + delx, last[1] + dely) elif isinstance(segment, Line): rev = Line(last[0] + delx, last[1] + dely) else: raise RuntimeError("Unexpected segment type {}".format( type(segment))) elem.path = inkex.Path([ Move(last[0], last[1]), segment, npt.to_line(Vector2d()), rev, ZoneClose(), ])
def test_new_path(self): """Test new path element""" path = PathElement.new(path=[Move(10, 10), Line(20, 20)]) self.assertEqual(path.get('d'), 'M 10 10 L 20 20')
def effect(self): path_num = 0 elems = [] npaths = [] sstr = None for selem in self.svg.selection.filter(PathElement): elems.append(copy.deepcopy(selem)) if len(elems) == 0: raise inkex.AbortExtension("Nothing selected") for elem in elems: escale = 1.0 npaths.clear() #inkex.utils.debug(elem.attrib) if 'style' in elem.attrib: sstr = elem.attrib['style'] if 'transform' in elem.attrib: transforms = elem.attrib['transform'].split() for tf in transforms: if tf.startswith('scale'): escale = float(tf.split('(')[1].split(')')[0]) if sstr != None: lsstr = sstr.split(';') for stoken in range(len(lsstr)): if lsstr[stoken].startswith('stroke-width'): swt = lsstr[stoken].split(':')[1] if not swt[2:].isalpha( ): # is value expressed in units (e.g. px)? swf = str(float(swt) * escale) # no. scale it lsstr[stoken] = lsstr[stoken].replace(swt, swf) if lsstr[stoken].startswith('stroke-miterlimit'): swt = lsstr[stoken].split(':')[1] if not swt[2:].isalpha( ): # is value expressed in units (e.g. px)? swf = str(float(swt) * escale) # no. scale it lsstr[stoken] = lsstr[stoken].replace(swt, swf) sstr = ";".join(lsstr) else: sstr = None elem.apply_transform() xbound, ybound = elem.bounding_box() # Get bounds of this element xmin, xmax = xbound ymin, ymax = ybound ntotal = len(elem.path) nodecnt = 0 startx = 0 starty = ymax + 10 * escale endx = 0 endy = starty xoffset = 0 orig_sx = 0 orig_sy = 0 orig_ex = 0 orig_ey = 0 sx1 = 0 sy1 = 0 orig_length = 0 prev = Vector2d() for ptoken in elem.path.to_absolute( ): # For each point in the path startx = xmin + xoffset if ptoken.letter == 'M': # Starting a new line orig_sx = ptoken.x orig_sy = ptoken.y cd = Path() cd.append(Move(startx, starty)) sx1 = orig_sx sy1 = orig_sy else: if last_letter != 'M': orig_sx = orig_ex orig_sy = orig_ey if ptoken.letter == 'L': orig_ex = ptoken.x orig_ey = ptoken.y orig_length = math.sqrt((orig_sx - orig_ex)**2 + (orig_sy - orig_ey)**2) endx = startx + orig_length cd.append(Line(endx, endy)) elif ptoken.letter == 'H': orig_ey = ptoken.to_line(prev).y orig_length = abs(orig_sx - ptoken.x) orig_ex = ptoken.x endx = startx + orig_length cd.append(Line(endx, endy)) elif ptoken.letter == 'V': orig_ex = ptoken.to_line(prev).x orig_length = abs(orig_sy - ptoken.y) orig_ey = ptoken.y endx = startx + orig_length cd.append(Line(endx, endy)) elif ptoken.letter == 'Z': orig_ex = sx1 orig_ey = sy1 orig_length = math.sqrt((orig_sx - orig_ex)**2 + (orig_sy - orig_ey)**2) endx = startx + orig_length cd.append(Line(endx, endy)) elif ptoken.letter == 'C': bez = ptoken.to_bez() # [[x0,y0][x1,y1][x2,y2][x3,y3]] bez.insert(0, [prev.x, prev.y]) orig_ex = bez[3][0] #x3 orig_ey = bez[3][1] #y3 orig_length = inkex.bezier.bezierlength(bez) endx = startx + orig_length cd.append(Line(endx, endy)) else: raise inkex.AbortExtension( "Unknown letter - {0}".format(ptoken.letter)) nodecnt = nodecnt + 1 if ptoken.letter != 'M': if nodecnt == ntotal: self.drawline(str(cd), "hline{0}".format(path_num), self.svg.get_current_layer(), sstr) path_num = path_num + 1 xoffset = xoffset + orig_length prev.x = orig_ex prev.y = orig_ey else: prev.x = orig_sx prev.y = orig_sy last_letter = ptoken.letter
def render_stbar(self, keys, values): """Draw stacked bar chart""" # Iterate over all values to draw the different slices color = 0 # create value sum in order to divide the bars try: valuesum = sum(values) except ValueError: valuesum = 0.0 # Init offset offset = 0 width = self.options.bar_width height = self.options.bar_height x = self.width / 2 y = self.height / 2 if self.blur: if self.options.rotate: width, height = height, width shy = y else: shy = str(y - self.options.bar_height) # Create rectangle element shadow = Rectangle( x=str(x), y=str(shy), width=str(width), height=str(height), ) # Set shadow blur (connect to filter object in xml path) shadow.style = self.blur yield shadow # Draw Single bars for cnt, value in enumerate(values): # Calculate the individual heights normalized on 100units normedvalue = (self.options.bar_height / valuesum) * float(value) # Create rectangle element rect = Rectangle() # Set chart position to center of document. if not self.options.rotate: rect.set('x', str(self.width / 2)) rect.set('y', str(self.height / 2 - offset - normedvalue)) rect.set("width", str(self.options.bar_width)) rect.set("height", str(normedvalue)) else: rect.set('x', str(self.width / 2 + offset)) rect.set('y', str(self.height / 2)) rect.set("height", str(self.options.bar_width)) rect.set("width", str(normedvalue)) rect.set("style", "fill:" + self.get_color()) # If text is given, draw short paths and add the text # TODO: apply overlap workaround for visible gaps in between if keys: if not self.options.rotate: x1 = (self.width + self.options.bar_width) / 2 y1 = y - offset - (normedvalue / 2) x2 = self.options.bar_width / 2 + self.options.text_offset y2 = 0 txt = self.width / 2 + self.options.bar_width + self.options.text_offset + 1 tyt = y - offset + self.fontoff - (normedvalue / 2) else: x1 = x + offset + normedvalue / 2 y1 = y + self.options.bar_width / 2 x2 = 0 y2 = self.options.bar_width / 2 + (self.options.font_size \ * cnt) + self.options.text_offset txt = x + offset + normedvalue / 2 - self.fontoff tyt = (y) + self.options.bar_width + (self.options.font_size \ * (cnt + 1)) + self.options.text_offset elem = inkex.PathElement() elem.path = [Move(x1, y1), line(x2, y2)] elem.style = { 'fill': 'none', 'stroke': self.options.font_color, 'stroke-width': self.options.stroke_width, 'stroke-linecap': 'butt', } yield elem yield self.draw_text(keys[cnt], x=str(txt), y=str(tyt)) # Increase Offset and Color offset = offset + normedvalue color = (color + 1) % 8 # Draw rectangle yield rect yield self.draw_header(self.width / 2 + offset + normedvalue)
def render_pie(self, keys, values, pie_abs=False): """Draw pie chart""" pie_radius = self.options.pie_radius # Iterate all values to draw the different slices color = 0 x = float(self.width) / 2 y = float(self.height) / 2 # Create the shadow first (if it should be created): if self.blur: shadow = Circle(cx=str(x), cy=str(y)) shadow.set('r', str(pie_radius)) shadow.style = self.blur + inkex.Style(fill='#000000') yield shadow # Add a grey background circle with a light stroke background = Circle(cx=str(x), cy=str(y)) background.set("r", str(pie_radius)) background.set("style", "stroke:#ececec;fill:#f9f9f9") yield background # create value sum in order to divide the slices try: valuesum = sum(values) except ValueError: valuesum = 0 if pie_abs: valuesum = 100 # Set an offsetangle offset = 0 # Draw single slices for cnt, value in enumerate(values): # Calculate the PI-angles for start and end angle = (2 * 3.141592) / valuesum * float(value) start = offset end = offset + angle # proper overlapping if self.options.segment_overlap: if cnt != len(values) - 1: end += 0.09 # add a 5° overlap if cnt == 0: start -= 0.09 # let the first element overlap into the other direction # then add the slice pieslice = inkex.PathElement() pieslice.set('sodipodi:type', 'arc') pieslice.set('sodipodi:cx', x) pieslice.set('sodipodi:cy', y) pieslice.set('sodipodi:rx', pie_radius) pieslice.set('sodipodi:ry', pie_radius) pieslice.set('sodipodi:start', start) pieslice.set('sodipodi:end', end) pieslice.set( "style", "fill:" + self.get_color() + ";stroke:none;fill-opacity:1") ang = angle / 2 + offset # If text is given, draw short paths and add the text if keys: elem = inkex.PathElement() elem.path = [ Move( (self.width / 2) + pie_radius * math.cos(ang), (self.height / 2) + pie_radius * math.sin(ang), ), line( (self.options.text_offset - 2) * math.cos(ang), (self.options.text_offset - 2) * math.sin(ang), ), ] elem.style = { 'fill': 'none', 'stroke': self.options.font_color, 'stroke-width': self.options.stroke_width, 'stroke-linecap': 'butt', } yield elem label = keys[cnt] if self.options.show_values: label += ' ({}{})'.format(str(value), ('', '%')[pie_abs]) # check if it is right or left of the Pie anchor = 'start' if math.cos(ang) > 0 else 'end' text = self.draw_text(label, anchor=anchor) off = pie_radius + self.options.text_offset text.set("x", (self.width / 2) + off * math.cos(ang)) text.set("y", (self.height / 2) + off * math.sin(ang) + self.fontoff) yield text # increase the rotation-offset and the colorcycle-position offset = offset + angle color = (color + 1) % 8 # append the objects to the extension-layer yield pieslice yield self.draw_header(self.width / 2 - pie_radius)