def fromFontpartsGlyph(klass, glyph): """Returns an *array of BezierPaths* from a FontParts glyph object.""" paths = [] if hasattr(glyph, "contours"): contouriterator = glyph.contours else: contouriterator = glyph for c in contouriterator: path = BezierPath() path.closed = False nodeList = [] if hasattr(c, "points"): pointiterator = c.points else: pointiterator = c for p in pointiterator: if hasattr(p, "segmentType"): t = p.segmentType else: t = p.type nodeList.append(Node(p.x, p.y, t)) path.activeRepresentation = NodelistRepresentation(path, nodeList) if nodeList[0].point == nodeList[-1].point: path.closed = True paths.append(path) return paths
def drawIt(s, c, segs): import matplotlib.pyplot as plt fig, ax = plt.subplots() s.plot(ax, drawNodes=False) c.plot(ax) for s in segs: BezierPath.fromSegments([s]).plot(ax, drawNodes=False, color="red") plt.show()
def test_cubic_cubic(self): # q1 = Bezier(10,100, 90,30, 40,140, 220,220) # q2 = Bezier(5,150, 180,20, 80,250, 210,190) # console.log(q1.intersects(q2)) q1 = CubicBezier(Point(10, 100), Point(90, 30), Point(40, 140), Point(220, 220)) q2 = CubicBezier(Point(5, 150), Point(180, 20), Point(80, 250), Point(210, 190)) i = q1.intersections(q2) # self.assertEqual(len(i),3) # self.assertAlmostEqual(i[0].point.x,81.7904225873) # self.assertAlmostEqual(i[0].point.y,109.899396337) # self.assertAlmostEqual(i[1].point.x,133.186831292) # self.assertAlmostEqual(i[1].point.y,167.148173322) # self.assertAlmostEqual(i[2].point.x,179.869157678) # self.assertAlmostEqual(i[2].point.y,199.661989162) import matplotlib.pyplot as plt fig, ax = plt.subplots() path = BezierPath() path.closed = False path.activeRepresentation = SegmentRepresentation(path, [q1]) path.plot(ax) path.activeRepresentation = SegmentRepresentation(path, [q2]) path.plot(ax) for n in i: circle = plt.Circle((n.point.x, n.point.y), 2, fill=True, color="red") ax.add_artist(circle)
def test_cubic_line(self): q = CubicBezier(Point(100, 240), Point(30, 60), Point(210, 230), Point(160, 30)) l = Line(Point(25, 260), Point(230, 20)) path = BezierPath() path.closed = False path.activeRepresentation = SegmentRepresentation(path, [q]) i = q.intersections(l) self.assertEqual(len(i), 3) self.assertEqual(i[0].point, q.pointAtTime(0.117517031451)) self.assertEqual(i[1].point, q.pointAtTime(0.518591792307)) self.assertEqual(i[2].point, q.pointAtTime(0.867886610031))
def fromFontpartsGlyph(klass, glyph): """Returns an *array of BezierPaths* from a FontParts glyph object.""" paths = [] for c in glyph.contours: path = BezierPath() path.closed = False nodeList = [] for p in c.points: nodeList.append(Node(p.x,p.y,p.type)) path.activeRepresentation = NodelistRepresentation(path, nodeList) if nodeList[0].point == nodeList[-1].point: path.closed = True paths.append(path) return paths
def fromFontpartsGlyph(klass, glyph): """Returns an *array of BezierPaths* from a FontParts glyph object.""" paths = [] for c in glyph.contours: path = BezierPath() path.closed = False nodeList = [] for p in c.points: nodeList.append(Node(p.x, p.y, p.type)) path.activeRepresentation = NodelistRepresentation(path, nodeList) if nodeList[0].point == nodeList[-1].point: path.closed = True paths.append(path) return paths
def test_addextremes(self): q = CubicBezier(Point(42, 135), Point(129, 242), Point(167, 77), Point(65, 59)) ex = q.findExtremes() self.assertEqual(len(ex), 2) path = BezierPath() path.closed = False path.activeRepresentation = SegmentRepresentation(path, [q]) path.addExtremes() path.balance() segs = path.asSegments() self.assertEqual(len(segs), 3)
def get_cached_glyph(self, name): if name in self.glyphcache: return self.glyphcache[name] paths = BezierPath.fromFonttoolsGlyph(self.font, name) pathbounds = [] paths = list(filter(lambda p: p.length > 0, paths)) for p in paths: p.hasAnchor = False p.glyphname = name if name in self.anchors: for a in self.anchors[name]: if p.pointIsInside(Point(*a)): p.hasAnchor = True bounds = p.bounds() pathbounds.append(bounds) glyphbounds = BoundingBox() if pathbounds: for p in pathbounds: glyphbounds.extend(p) else: glyphbounds.tr = Point(0, 0) glyphbounds.bl = Point(0, 0) self.glyphcache[name] = { "name": name, "paths": paths, "pathbounds": pathbounds, "glyphbounds": glyphbounds, "category": categorize_glyph(self.font, name)[0], "pathconvexhull": None # XXX } assert (len(self.glyphcache[name]["pathbounds"]) == len( self.glyphcache[name]["paths"])) return self.glyphcache[name]
def calcStart(f, n, verbose): g = f['glyf'][n] #bs = BezierPath.fromFonttoolsGlyph(g, gset, f['glyf']) bs = BezierPath.fromFonttoolsGlyph(f, n) # for b in bs: # b.removeOverlap() bs = removeEncompassed(bs, verbose) segs = sum((b.asSegments() for b in bs), []) area = sum(s.area for s in segs) start = Octabox(segs) return start
def test_overlap(self): nodes = [ Node(698.0, 413.0, "offcurve"), Node(401.0, 179.0, "offcurve"), Node(401.0, 274.0, "curve"), Node(401.0, 368.0, "offcurve"), Node(315.0, 445.0, "offcurve"), Node(210.0, 445.0, "curve"), Node(104.0, 445.0, "offcurve"), Node(18.0, 368.0, "offcurve"), Node(18.0, 274.0, "curve"), Node(18.0, 179.0, "offcurve"), Node(439.0, 400.0, "offcurve"), Node(533.0, 405.0, "curve") ] p = BezierPath.fromNodelist(nodes) p.closed = True i = p.getSelfIntersections() self.assertEqual(len(i), 1) self.assertEqual(i[0].point, Point(377.714262786, 355.53493137)) # import matplotlib.pyplot as plt # fig, ax = plt.subplots() # p.plot(ax) # for n in i: # circle = plt.Circle((n.point.x, n.point.y), 2, fill=True, color="red") # ax.add_artist(circle) # plt.show() p = BezierPath.fromNodelist([ Node(310.0, 389.0, "line"), Node(453.0, 222.0, "line"), Node(289.0, 251.0, "line"), Node(447.0, 367.0, "line"), Node(578.0, 222.0, "line"), Node(210.0, -8.0, "line"), ]) i = p.getSelfIntersections() self.assertEqual(len(i), 1) self.assertEqual(i[0].point, Point(374.448829525, 313.734583702))
def Rectangle(width, height, origin=None): """Returns a path representing an rectangle of given width and height. You can specify the `origin` as a Point.""" if not origin: origin = Point(0, 0) tl = origin + west * width / 2.0 + north * height / 2.0 tr = origin + east * width / 2.0 + north * height / 2.0 bl = origin + west * width / 2.0 + south * height / 2.0 br = origin + east * width / 2.0 + south * height / 2.0 return BezierPath.fromSegments( [Line(tl, tr), Line(tr, br), Line(br, bl), Line(bl, tl)])
def annotate_glyph(glyphname): paths = FontParts.fromFontpartsGlyph(font[glyphname]) paths2 = [] if drawarrows: for i in range(0, len(paths)): arrowP = paths[i].clone() arrowP.translate(arrowvector) arrowSeg, _ = (arrowP.asSegments())[0].splitAtTime(0.25) s = arrowSeg.start arrowPath = BezierPath.fromSegments([arrowSeg]) # arrowPath.closed = False paths2.append(arrowPath) number = FontParts.fromFontpartsGlyph( nf.numbersfont[nf.numbers[i]]) # print(number[0].asNodelist()) number[0].scale(0.05) number[0].translate(Point(s.x + 5, s.y + 5)) number[0].closed = True paths2.append(number[0]) # print('<text x="%s" y="%s">%i</text>' % (s.x+5.0,height-(s.y+5.0),1+i)) # print('<path d="%s" stroke="black" stroke-width="2" fill="transparent" marker-end="url(#arrowhead)"/>\n' % path2svg([arrowSeg])) splitlist = [] for i in range(0, len(paths)): for j in range(i + 1, len(paths)): one = paths[i] two = paths[j] for s1 in one.asSegments(): for s2 in two.asSegments(): for inter in s1.intersections(s2): splitlist.append((inter.seg1, inter.t1)) splitlist.append((inter.seg2, inter.t2)) for path in paths: path.splitAtPoints(splitlist) segs = [] for i in range(0, len(paths)): segs = paths[i].asSegments() for s in segs: paths2.append(Circle(dotradius, origin=s.start)) if s.length > dotspacing * dotradius: closeEnough = int(s.length / (dotspacing * dotradius)) samples = s.regularSample(closeEnough) for p in samples[1:]: paths2.append(Circle(dotradius, origin=p)) if len(segs) > 0: paths2.append(Circle(dotradius, origin=segs[-1].end)) for p in paths2: FontParts.drawToFontpartsGlyph(font[glyphname], p)
def __get_centreline(self, n1: str, n2: str) -> BezierPath: #========================================================= if (n1, n2) in self.__path_network.edges: edge = self.__path_network.edges[n1, n2] bezier_path = edge.get('geometry') if bezier_path is not None: if n1 == edge.get('start-node'): return bezier_path else: segments = [ bz.reversed() for bz in bezier_path.asSegments() ] segments.reverse() return BezierPath.fromSegments(segments) return None
def test_corners(self): nl = [ Node(302.0, 492.0, "line"), Node(176.0, 432.0, "line"), Node(-51.0, 325.0, "offcurve"), Node(-74.0, 484.0, "offcurve"), Node(73.0, 570.0, "curve"), Node(85.0, 764.0, "offcurve"), Node(290.0, 748.0, "offcurve"), Node(418.0, 688.0, "curve"), ] path = BezierPath.fromNodelist(nl) path.closed = False for seg1, seg2 in path.segpairs(): print(seg1.endAngle * 57.2958, seg2.startAngle * 57.2958)
def _curve_from_lines(self, point_tuple_list: list) -> list: error = 50.0 cornerTolerance = 20.0 maxSegments = 20 curve_points = SCBezierPath().fromPoints( [SCPoint(p[0], p[1]) for p in point_tuple_list], error=1.0, cornerTolerance=1.0, maxSegments=10000, ) # Reconvert the BezierPath segments to our segment type point_tuple_list = [] first = True for segment in curve_points.asSegments(): segment_tuple = [] if first: # For the first segment, add the move point p = segment[0] point_tuple_list.append([(p.x, p.y)]) for p in segment[1:]: segment_tuple.append((p.x, p.y)) point_tuple_list.append(segment_tuple) return point_tuple_list
def get_bezier_paths(font, glyphname): """Retrieve beziers from a glyph Args: font: A Babelfont font Returns: An array of ``beziers.path.BezierPath`` objects representing the outlines of the glyph. """ layer = font.default_master.get_glyph_layer(glyphname) layer.decompose() return BezierPath.fromDrawable(layer, glyphSet = { k:font.default_master.get_glyph_layer(k) for k in font.exportedGlyphs() })
def test_inside(self): p = BezierPath.fromNodelist([ Node(329,320,"line"), Node(564,190,"line"), Node(622,332,"offcurve"), Node(495,471,"offcurve"), Node(329,471,"curve"), Node(164,471,"offcurve"), Node(34,334,"offcurve"), Node(93,190,"curve") ]) self.assertTrue(p.pointIsInside(Point(326,423))) self.assertFalse(p.pointIsInside(Point(326,123))) self.assertFalse(p.pointIsInside(Point(326,251))) self.assertTrue(p.pointIsInside(Point(526,251))) self.assertTrue(p.pointIsInside(Point(126,251)))
def test_cf2(self): nodes = [ Point(100, 50), Point(50, 150), Point(100, 220), Point(200, 200), Point(250, 80), Point(220, 50) ] path = BezierPath.fromPoints(nodes) segs = path.asSegments() self.assertEqual(len(segs), 2) self.assertEqual(segs[0].start, Point(100.0, 50.0)) self.assertAlmostEqual(segs[0][1].x, 83.333333333) self.assertAlmostEqual(segs[0][1].y, 83.333333333) self.assertEqual(segs[0].end, Point(50.0, 150.0)) self.assertAlmostEqual(segs[1][1].x, 50) self.assertEqual(segs[1].end, Point(220.0, 50.0))
def xheight_intersections(ttFont, glyph): glyphset = ttFont.getGlyphSet() if glyph not in glyphset: return [] paths = BezierPath.fromFonttoolsGlyph(ttFont, glyph) if len(paths) != 1: return [] path = paths[0] xheight = ttFont["OS/2"].sxHeight bounds = path.bounds() bounds.addMargin(10) ray = Line(Point(bounds.left, xheight), Point(bounds.right, xheight)) intersections = [] for seg in path.asSegments(): intersections.extend(seg.intersections(ray)) return sorted(intersections, key=lambda i: i.point.x)
def Ellipse(x_radius, y_radius, origin=None, superness=CIRCULAR_SUPERNESS): """Returns a path representing an ellipse of given x and y radii. You can specify the `origin` as a Point and the `superness` of the ellipse.""" if not origin: origin = Point(0, 0) w = origin + west * x_radius e = origin + east * x_radius n = origin + north * y_radius s = origin + south * y_radius w_n = CubicBezier(w, w + north * y_radius * superness, n + west * x_radius * superness, n) n_e = CubicBezier(n, n + east * x_radius * superness, e + north * y_radius * superness, e) e_s = CubicBezier(e, e + south * y_radius * superness, s + east * x_radius * superness, s) s_w = CubicBezier(s, s + west * x_radius * superness, w + south * y_radius * superness, w) return BezierPath.fromSegments([w_n, n_e, e_s, s_w])
def not_a_test_cf3(self): import matplotlib.pyplot as plt import math fig, ax = plt.subplots() points = [ Point(100, 50), Point(50, 150), Point(150, 250), Point(200, 220), Point(250, 80), Point(220, 50) ] path = BezierPath.fromPoints(points) centroid = path.bounds().centroid path.rotate(centroid, math.pi / 2) path.balance() path.plot(ax) path.offset(Point(5, 5)).plot(ax, color="red") path.offset(Point(-5, -5)).plot(ax, color="green") plt.show()
def test_splitatpoints(self): p = BezierPath.fromNodelist([ Node(297.0,86.0,"offcurve"), Node(344.0,138.0,"offcurve"), Node(344.0,203.0,"curve"), Node(344.0,267.0,"offcurve"), Node(297.0,319.0,"offcurve"), Node(240.0,319.0,"curve"), Node(183.0,319.0,"offcurve"), Node(136.0,267.0,"offcurve"), Node(136.0,203.0,"curve"), Node(136.0,138.0,"offcurve"), Node(183.0,86.0,"offcurve"), Node(240.0,86.0,"curve"), ]) splitlist = [] for seg in p.asSegments(): for t in seg.regularSampleTValue(5): splitlist.append((seg,t)) p.splitAtPoints(splitlist) self.assertEqual(len(p.asSegments()),24)
def bezier_paths_from_arc_endpoints(r, phi, flagA, flagS, p1, p2, T): #==================================================================== arc = arc_endpoints_to_centre(r, phi, flagA, flagS, p1, p2) end_theta = arc.theta + arc.delta_theta t = arc.theta dt = math.pi / 4 segments = [] while (t + dt) < end_theta: control_points = (BezierPoint(*T.transform_point(cp)) for cp in cubic_bezier_control_points( arc.centre, arc.radii, phi, t, t + dt)) segments.append(CubicBezier(*control_points)) t += dt control_points = (BezierPoint(*T.transform_point(cp)) for cp in cubic_bezier_control_points( arc.centre, arc.radii, phi, t, end_theta)) segments.append( CubicBezier(*(tuple(control_points)[:3]), BezierPoint(*T.transform_point(p2)))) path = BezierPath.fromSegments(segments) path.closed = False return path
def outlines_dict(ttFont): return {g: BezierPath.fromFonttoolsGlyph(ttFont, g) for g in ttFont.getGlyphOrder()}
def clip(self, clip, cliptype, flat=False): splitlist1 = [] splitlist2 = [] intersections = {} cloned = self.clone() clip = clip.clone() # Split all segments at intersections for s1 in self.asSegments(): for s2 in clip.asSegments(): for i in s1.intersections(s2): if i.t1 > 1e-8 and i.t1 < 1 - 1e-8: if i.seg1 == s1: splitlist1.append((i.seg1, i.t1)) splitlist2.append((i.seg2, i.t2)) else: splitlist2.append((i.seg1, i.t1)) splitlist1.append((i.seg2, i.t2)) intersections[i.point] = i logging.debug("Split list: %s" % splitlist1) logging.debug("Split list 2: %s" % splitlist2) cloned.splitAtPoints(splitlist1) clip.splitAtPoints(splitlist2) logging.debug("Self:") logging.debug(cloned.asSegments()) logging.debug("Clip:") logging.debug(clip.asSegments()) segs1unflattened = cloned.asSegments() segs2unflattened = clip.asSegments() # Replace with flattened versions, building a dictionary of originals segs1 = [] reconstructionLUT = {} precision = 100. def fillLUT(flats): for line in flats: key = ((line.start * precision).rounded(), (line.end * precision).rounded()) reconstructionLUT[key] = (line._orig or line) key2 = ((line.end * precision).rounded(), (line.start * precision).rounded()) reconstructionLUT[key2] = (line._orig or line).reversed() for s in segs1unflattened: flats = s.flatten(2) fillLUT(flats) segs1.extend(flats) segs2 = [] for s in segs2unflattened: flats = s.flatten(2) fillLUT(flats) segs2.extend(flats) # Leave it to the professionals subj = [(s[0].x * precision, s[0].y * precision) for s in segs1] clip = [(s[0].x * precision, s[0].y * precision) for s in segs2] pc = pyclipper.Pyclipper() pc.AddPath(clip, pyclipper.PT_CLIP, True) pc.AddPath(subj, pyclipper.PT_SUBJECT, True) paths = pc.Execute(cliptype, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) outpaths = [] # Now reconstruct Bezier segments from flattened paths def pairwise(points): a = (p for p in points) b = (p for p in points) next(b) for curpoint, nextpoint in zip(a, b): yield curpoint, nextpoint newpaths = [] from beziers.path import BezierPath for p in paths: newpath = [] for scaledstart, scaledend in pairwise(p): key = (Point(*scaledstart), Point(*scaledend)) if key in reconstructionLUT and not flat: orig = reconstructionLUT[key] if len(newpath) == 0 or newpath[-1] != orig: newpath.append(orig) else: newpath.append(Line(key[0] / precision, key[1] / precision)) outpaths.append(BezierPath.fromSegments(newpath)) return outpaths
def not_a_test_offset(self): b = DotMap({ "closed": False, "nodes": [{ "x": 412.0, "y": 500.0, "type": "line" }, { "x": 308.0, "y": 665.0, "type": "offcurve" }, { "x": 163.0, "y": 589.0, "type": "offcurve" }, { "x": 163.0, "y": 504.0, "type": "curve" }, { "x": 163.0, "y": 424.0, "type": "offcurve" }, { "x": 364.0, "y": 321.0, "type": "offcurve" }, { "x": 366.0, "y": 216.0, "type": "curve" }, { "x": 368.0, "y": 94.0, "type": "offcurve" }, { "x": 260.0, "y": 54.0, "type": "offcurve" }, { "x": 124.0, "y": 54.0, "type": "curve" }] }) path = BezierPath() path.activeRepresentation = GSPathRepresentation(path, b) import matplotlib.pyplot as plt fig, ax = plt.subplots() path.addExtremes() path.plot(ax) for n in path.asSegments(): p = n.tunniPoint if p: circle = plt.Circle((p.x, p.y), 1, fill=False, color="blue") ax.add_artist(circle) n.balance() path.translate(Point(5, 5)) path.plot(ax, color="red") # o1 = path.offset(Point(10,10)) # o2 = path.offset(Point(-10,-10)) # o2.reverse() # o1.append(o2) # o1.plot(ax) plt.show()
def geometry(self) -> [GeometricShape]: """ Returns: A list of geometric objects. This are LineStrings describing paths between nodes and possibly additional features (e.g. way markers) of the paths. """ display_bezier_points = True #False ### To come from settings... if self.__path_layout == 'automatic': log("Automated pathway layout. Path ID: ", self.__path_id) evaluate_settings = self.__sheath.settings() # TODO: use evenly-distributed offsets for the final product. number_of_neurons = len(evaluate_settings['derivatives']) # locations = [0.01 + x*(0.99-0.01)/number_of_neurons for x in range(number_of_neurons)] location = 0.5 geometry = [] for scaffold, path_id, derivative in zip( evaluate_settings['scaffolds'], evaluate_settings['path_ids'], evaluate_settings['derivatives']): scaffold.generate() connectivity = Connectivity(path_id, scaffold, derivative, location) auto_beziers = connectivity.get_neuron_line_beziers() path = BezierPath.fromSegments(auto_beziers) geometry.append( GeometricShape( shapely.geometry.LineString(bezier_sample(path)))) end_nodes = set(self.__source_nodes) end_nodes.update(self.__target_nodes) for node in end_nodes: for edge in self.__graph.edges(node, data=True): if edge[2].get('type') == 'terminal': line = self.__line_from_edge(edge) if line is not None: geometry.append(GeometricShape(line)) if display_bezier_points: for beziers in self.__sheath.path_beziers.values(): for bezier in beziers: bz_pts = tuple([p.x, p.y] for p in bezier.points) for pt in [bz_pts[0], bz_pts[3]]: geometry.append( GeometricShape(GeometricShape.circle(pt), { 'type': 'bezier', 'kind': 'bezier-end' })) for pt in bz_pts[1:3]: geometry.append( GeometricShape(GeometricShape.circle(pt), { 'type': 'bezier', 'kind': 'bezier-control' })) geometry.append( GeometricShape(GeometricShape.line(*bz_pts[0:2]), {'type': 'bezier'})) geometry.append( GeometricShape(GeometricShape.line(*bz_pts[2:4]), {'type': 'bezier'})) return geometry # Fallback is centreline layout geometry = [] for edge in self.__graph.edges.data(): nerve = edge[2].get('nerve') properties = {'nerve': nerve} if nerve is not None else None bezier = edge[2].get('geometry') if self.__path_layout != 'linear' and bezier is not None: geometry.append( GeometricShape( shapely.geometry.LineString(bezier_sample(bezier)), properties)) else: line = self.__line_from_edge(edge) if line is not None: geometry.append(GeometricShape(line, properties)) return geometry
def __get_geometry(self, shape, properties, transform): #====================================================== ## ## Returns shape's geometry as `shapely` object. ## coordinates = [] bezier_segments = [] pptx_geometry = Geometry(shape) for path in pptx_geometry.path_list: bbox = (shape.width, shape.height) if path.w is None or path.h is None else (path.w, path.h) T = transform@DrawMLTransform(shape, bbox) moved = False first_point = None current_point = None closed = False for c in path.getchildren(): if c.tag == DML('arcTo'): (wR, hR) = ((pptx_geometry.attrib_value(c, 'wR'), pptx_geometry.attrib_value(c, 'hR'))) stAng = radians(pptx_geometry.attrib_value(c, 'stAng')) swAng = radians(pptx_geometry.attrib_value(c, 'swAng')) p1 = ellipse_point(wR, hR, stAng) p2 = ellipse_point(wR, hR, stAng + swAng) pt = (current_point[0] - p1[0] + p2[0], current_point[1] - p1[1] + p2[1]) large_arc_flag = 1 if swAng >= math.pi else 0 path = bezier_path_from_arc_endpoints(tuple2(wR, hR), 0, large_arc_flag, 1, tuple2(*current_point), tuple2(*pt), T) bezier_segments.extend(path.asSegments()) coordinates.extend(bezier_sample(path)) current_point = pt elif c.tag == DML('close'): if first_point is not None and current_point != first_point: coordinates.append(T.transform_point(first_point)) closed = True first_point = None # Close current pptx_geometry and start a new one... elif c.tag == DML('cubicBezTo'): coords = [BezierPoint(*T.transform_point(current_point))] for p in c.getchildren(): pt = pptx_geometry.point(p) coords.append(BezierPoint(*T.transform_point(pt))) current_point = pt bz = CubicBezier(*coords) bezier_segments.append(bz) coordinates.extend(bezier_sample(bz)) elif c.tag == DML('lnTo'): pt = pptx_geometry.point(c.pt) if moved: coordinates.append(T.transform_point(current_point)) moved = False coordinates.append(T.transform_point(pt)) current_point = pt elif c.tag == DML('moveTo'): pt = pptx_geometry.point(c.pt) if first_point is None: first_point = pt current_point = pt moved = True elif c.tag == DML('quadBezTo'): coords = [BezierPoint(*T.transform_point(current_point))] for p in c.getchildren(): pt = pptx_geometry.point(p) coords.append(BezierPoint(*T.transform_point(pt))) current_point = pt bz = QuadraticBezier(*coords) bezier_segments.append(bz) coordinates.extend(bezier_sample(bz)) else: log.warn('Unknown path element: {}'.format(c.tag)) if len(bezier_segments) > 0: properties['bezier-path'] = BezierPath.fromSegments(bezier_segments) if closed: geometry = shapely.geometry.Polygon(coordinates) else: geometry = shapely.geometry.LineString(coordinates) if properties.get('closed', False): # Return a polygon if flagged as `closed` coordinates.append(coordinates[0]) return shapely.geometry.Polygon(coordinates) return geometry
def __get_geometry(self, element, properties, transform): #======================================================= ## ## Returns path element as a `shapely` object. ## coordinates = [] bezier_segments = [] moved = False first_point = None current_point = None closed = False path_tokens = [] T = transform@SVGTransform(element.attrib.get('transform')) if element.tag == SVG_NS('path'): path_tokens = list(parse_svg_path(element.attrib.get('d', ''))) elif element.tag == SVG_NS('rect'): x = length_as_pixels(element.attrib.get('x', 0)) y = length_as_pixels(element.attrib.get('y', 0)) width = length_as_pixels(element.attrib.get('width', 0)) height = length_as_pixels(element.attrib.get('height', 0)) rx = length_as_pixels(element.attrib.get('rx')) ry = length_as_pixels(element.attrib.get('ry')) if width == 0 or height == 0: return None if rx is None and ry is None: rx = ry = 0 elif ry is None: ry = rx elif rx is None: rx = ry rx = min(rx, width/2) ry = min(ry, height/2) if rx == 0 and ry == 0: path_tokens = ['M', x, y, 'H', x+width, 'V', y+height, 'H', x, 'V', y, 'Z'] else: path_tokens = ['M', x+rx, y, 'H', x+width-rx, 'A', rx, ry, 0, 0, 1, x+width, y+ry, 'V', y+height-ry, 'A', rx, ry, 0, 0, 1, x+width-rx, y+height, 'H', x+rx, 'A', rx, ry, 0, 0, 1, x, y+height-ry, 'V', y+ry, 'A', rx, ry, 0, 0, 1, x+rx, y, 'Z'] elif element.tag == SVG_NS('line'): x1 = length_as_pixels(element.attrib.get('x1', 0)) y1 = length_as_pixels(element.attrib.get('y1', 0)) x2 = length_as_pixels(element.attrib.get('x2', 0)) y2 = length_as_pixels(element.attrib.get('y2', 0)) path_tokens = ['M', x1, y1, x2, y2] elif element.tag == SVG_NS('polyline'): points = element.attrib.get('points', '').replace(',', ' ').split() path_tokens = ['M'] + points elif element.tag == SVG_NS('polygon'): points = element.attrib.get('points', '').replace(',', ' ').split() path_tokens = ['M'] + points + ['Z'] elif element.tag == SVG_NS('circle'): cx = length_as_pixels(element.attrib.get('cx', 0)) cy = length_as_pixels(element.attrib.get('cy', 0)) r = length_as_pixels(element.attrib.get('r', 0)) if r == 0: return None path_tokens = ['M', cx+r, cy, 'A', r, r, 0, 0, 0, cx, cy-r, 'A', r, r, 0, 0, 0, cx-r, cy, 'A', r, r, 0, 0, 0, cx, cy+r, 'A', r, r, 0, 0, 0, cx+r, cy, 'Z'] elif element.tag == SVG_NS('ellipse'): cx = length_as_pixels(element.attrib.get('cx', 0)) cy = length_as_pixels(element.attrib.get('cy', 0)) rx = length_as_pixels(element.attrib.get('rx', 0)) ry = length_as_pixels(element.attrib.get('ry', 0)) if rx == 0 or ry == 0: return None path_tokens = ['M', cx+rx, cy, 'A', rx, ry, 0, 0, 0, cx, cy-ry, 'A', rx, ry, 0, 0, 0, cx-rx, cy, 'A', rx, ry, 0, 0, 0, cx, cy+ry, 'A', rx, ry, 0, 0, 0, cx+rx, cy, 'Z'] elif element.tag == SVG_NS('image'): if 'id' in properties or 'class' in properties: width = length_as_pixels(element.attrib.get('width', 0)) height = length_as_pixels(element.attrib.get('height', 0)) path_tokens = ['M', 0, 0, 'H', width, 'V', height, 'H', 0, 'V', 0, 'Z'] pos = 0 while pos < len(path_tokens): if isinstance(path_tokens[pos], str) and path_tokens[pos].isalpha(): cmd = path_tokens[pos] pos += 1 # Else repeat previous command with new coordinates # with `moveTo` becoming `lineTo` elif cmd == 'M': cmd = 'L' elif cmd == 'm': cmd = 'l' if cmd not in ['s', 'S']: second_cubic_control = None if cmd not in ['t', 'T']: second_quad_control = None if cmd in ['a', 'A']: params = [float(x) for x in path_tokens[pos:pos+7]] pos += 7 pt = params[5:7] if cmd == 'a': pt[0] += current_point[0] pt[1] += current_point[1] phi = radians(params[2]) path = bezier_path_from_arc_endpoints(tuple2(*params[0:2]), phi, *params[3:5], tuple2(*current_point), tuple2(*pt), T) bezier_segments.extend(path.asSegments()) coordinates.extend(bezier_sample(path)) current_point = pt elif cmd in ['c', 'C', 's', 'S']: coords = [BezierPoint(*T.transform_point(current_point))] if cmd in ['c', 'C']: n_params = 6 else: n_params = 4 if second_cubic_control is None: coords.append(BezierPoint(*T.transform_point(current_point))) else: coords.append(BezierPoint(*T.transform_point( reflect_point(second_cubic_control, current_point)))) params = [float(x) for x in path_tokens[pos:pos+n_params]] pos += n_params for n in range(0, n_params, 2): pt = params[n:n+2] if cmd.islower(): pt[0] += current_point[0] pt[1] += current_point[1] if n == (n_params - 4): second_cubic_control = pt coords.append(BezierPoint(*T.transform_point(pt))) bz = CubicBezier(*coords) bezier_segments.append(bz) coordinates.extend(bezier_sample(bz)) current_point = pt elif cmd in ['l', 'L', 'h', 'H', 'v', 'V']: if cmd in ['l', 'L']: params = [float(x) for x in path_tokens[pos:pos+2]] pos += 2 pt = params[0:2] if cmd == 'l': pt[0] += current_point[0] pt[1] += current_point[1] else: param = float(path_tokens[pos]) pos += 1 if cmd == 'h': param += current_point[0] elif cmd == 'v': param += current_point[1] if cmd in ['h', 'H']: pt = [param, current_point[1]] else: pt = [current_point[0], param] if moved: coordinates.append(T.transform_point(current_point)) moved = False coordinates.append(T.transform_point(pt)) current_point = pt elif cmd in ['m', 'M']: params = [float(x) for x in path_tokens[pos:pos+2]] pos += 2 pt = params[0:2] if first_point is None: # First `m` in a path is treated as `M` first_point = pt else: if cmd == 'm': pt[0] += current_point[0] pt[1] += current_point[1] current_point = pt moved = True elif cmd in ['q', 'Q', 't', 'T']: coords = [BezierPoint(*T.transform_point(current_point))] if cmd in ['q', 'Q']: n_params = 4 else: n_params = 2 if second_quad_control is None: coords.append(BezierPoint(*T.transform_point(current_point))) else: coords.append(BezierPoint(*T.transform_point( reflect_point(second_quad_control, current_point)))) params = [float(x) for x in path_tokens[pos:pos+n_params]] pos += n_params for n in range(0, n_params, 2): pt = params[n:n+2] if cmd.islower(): pt[0] += current_point[0] pt[1] += current_point[1] if n == (n_params - 4): second_quad_control = pt coords.append(BezierPoint(*T.transform_point(pt))) bz = QuadraticBezier(*coords) bezier_segments.append(bz) coordinates.extend(bezier_sample(bz)) current_point = pt elif cmd in ['z', 'Z']: if first_point is not None and current_point != first_point: coordinates.append(T.transform_point(first_point)) closed = True first_point = None else: log.warn('Unknown path command: {}'.format(cmd)) if len(bezier_segments) > 0: properties['bezier-path'] = BezierPath.fromSegments(bezier_segments) if closed and len(coordinates) >= 3: geometry = shapely.geometry.Polygon(coordinates) elif properties.get('closed', False) and len(coordinates) >= 3: # Return a polygon if flagged as `closed` coordinates.append(coordinates[0]) geometry = shapely.geometry.Polygon(coordinates) elif len(coordinates) >= 2: geometry = shapely.geometry.LineString(coordinates) else: geometry = None return geometry
def __extract_components(self) -> None: #====================================== """ Extracts and stores centreline components (i.e, coordinates & derivatives) in a dictionary for every nerve set. Each nerve set is a dict with keys corresponding to the keys in self.__continuous_paths. """ node_geometry = self.__path_network.nodes(data='geometry') for path_id, path_nodes in self.__continuous_paths.items(): # First derive the segments that connect the path's nodes centrelines = [] node_regions = [] for node_1, node_2 in pairwise(path_nodes): centreline = self.__get_centreline(node_1, node_2) if len(centrelines) > 0: ##join_region = node_geometry[node_1] # Join previous and current centreline in circle centred at region's centroid join_region = node_geometry[node_1].centroid.buffer( JOIN_RADIUS) joined_beziers = join_beziers_in_region( centrelines[-1], join_region, centreline) # Adjust previous centreline segments = centrelines[-1].asSegments() segments[-1] = joined_beziers[0] centrelines[-1] = BezierPath.fromSegments(segments) # Add centreline of join centrelines.append( BezierPath.fromSegments(joined_beziers[1:2])) node_regions.append((None, None)) # Adjust current centreline segments = centreline.asSegments() segments[0] = joined_beziers[2] centreline = BezierPath.fromSegments(segments) centrelines.append(centreline) node_regions.append( (node_geometry[node_1], node_geometry[node_2])) # Get the path segment for each centreline path_segments = [] for n, centreline in enumerate(centrelines): regions = node_regions[n] path_segment = PathSegment( regions[0], centreline, regions[1], subdivision_parts=(1 if regions[0] is None else NUMBER_OF_BEZIER_PARTS)) path_segments.append(path_segment) # And use them to set the control points for the path's centreline sheath for path_segment in path_segments: control_points = path_segment.control_points if len(self.__control_points[path_id]) == 0: self.__control_points[path_id].append(control_points[0]) self.__control_points[path_id].extend(control_points[1:]) # The sheath starts and ends at the respective node centroids self.__control_points[path_id][0].set_position( path_segments[0].start_point) self.__control_points[path_id][-1].set_position( path_segments[-1].end_point)
def clip(self,clip,cliptype): import pyclipper splitlist1 = [] splitlist2 = [] intersections = {} cloned = self.clone() clip = clip.clone() # Split all segments at intersections for s1 in self.asSegments(): for s2 in clip.asSegments(): for i in s1.intersections(s2): if i.t1 > 1e-8 and i.t1 < 1-1e-8: if i.seg1 == s1: splitlist1.append((i.seg1,i.t1)) splitlist2.append((i.seg2,i.t2)) else: splitlist2.append((i.seg1,i.t1)) splitlist1.append((i.seg2,i.t2)) intersections[i.point] = i logging.debug("Split list: %s" % splitlist1) logging.debug("Split list 2: %s" % splitlist2) cloned.splitAtPoints(splitlist1) clip.splitAtPoints(splitlist2) logging.debug("Self:") logging.debug(cloned.asSegments()) logging.debug("Clip:") logging.debug(clip.asSegments()) segs1unflattened = cloned.asSegments() segs2unflattened = clip.asSegments() # Replace with flattened versions, building a dictionary of originals segs1 = [] reconstructionLUT = {} precision = 100. def fillLUT(flats): for line in flats: key = ((line.start * precision).rounded(), (line.end * precision).rounded()) reconstructionLUT[key] = (line._orig or line) key2 = ((line.end * precision).rounded(), (line.start * precision).rounded()) reconstructionLUT[key2] = (line._orig or line).reversed() for s in segs1unflattened: flats = s.flatten(2) fillLUT(flats) segs1.extend(flats) segs2 = [] for s in segs2unflattened: flats = s.flatten(2) fillLUT(flats) segs2.extend(flats) # Leave it to the professionals subj = [(s[0].x*precision, s[0].y*precision) for s in segs1] clip = [(s[0].x*precision, s[0].y*precision) for s in segs2] pc = pyclipper.Pyclipper() pc.AddPath(clip, pyclipper.PT_CLIP, True) pc.AddPath(subj, pyclipper.PT_SUBJECT, True) paths = pc.Execute(cliptype, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) outpaths = [] # Now reconstruct Bezier segments from flattened paths def pairwise(points): a = (p for p in points) b = (p for p in points) next(b) for curpoint,nextpoint in zip(a, b): yield curpoint, nextpoint newpaths = [] from beziers.path import BezierPath for p in paths: newpath = [] for scaledstart,scaledend in pairwise(p): key = (Point(*scaledstart), Point(*scaledend)) if key in reconstructionLUT: orig = reconstructionLUT[key] if len(newpath) == 0 or newpath[-1] != orig: newpath.append(orig) else: newpath.append(Line(key[0]/precision, key[1]/precision)) outpaths.append(BezierPath.fromSegments(newpath)) return outpaths
def test_representations(self): b = DotMap({ "closed": True, "nodes": [{ "x": 385.0, "y": 20.0, "type": "offcurve" }, { "x": 526.0, "y": 79.0, "type": "offcurve" }, { "x": 566.0, "y": 135.0, "type": "curve" }, { "x": 585.0, "y": 162.0, "type": "offcurve" }, { "x": 566.0, "y": 260.0, "type": "offcurve" }, { "x": 484.0, "y": 281.0, "type": "curve" }, { "x": 484.0, "y": 407.0, "type": "offcurve" }, { "x": 381.0, "y": 510.0, "type": "offcurve" }, { "x": 255.0, "y": 510.0, "type": "curve" }, { "x": 26.0, "y": 281.0, "type": "line" }, { "x": 26.0, "y": 155.0, "type": "offcurve" }, { "x": 129.0, "y": 20.0, "type": "offcurve" }, { "x": 255.0, "y": 20.0, "type": "curve" }] }) path = BezierPath() path.activeRepresentation = GSPathRepresentation(path, b) nl = path.asNodelist() self.assertEqual(len(nl), 13) self.assertIsInstance(nl[1], Node) self.assertEqual(nl[1].type, "offcurve") self.assertAlmostEqual(nl[1].x, 526.0) segs = path.asSegments() self.assertEqual(len(segs), 5) self.assertIsInstance(segs[1], CubicBezier) self.assertIsInstance(segs[2], Line)