def path_segments_hv(start, end, max_bend, adj): # j-shaped path 'horizontal' to 'vertical' dx = end.real - start.real dy = end.imag - start.imag dv = adj[0] * dy/abs(dy) dh = adj[1] * dx/abs(dx) h = Line(start, complex(start.real+dh, start.imag)) v = Line(complex(end.real, end.imag-dv), end) p1 = h.end + complex(0.75*(dx-dh), 0) p2 = h.end + complex(dx-dh, 0.25*(dy-dv)) p3 = h.end + complex(dx-dh, dy-dv) if abs(dx) > max_bend: dh = dx - max_bend*dx/abs(dx) h = Line(start, complex(start.real+dh, start.imag)) p1 = complex(h.end.real + 0.75*max_bend*dx/abs(dx), p1.imag) p2 = complex(h.end.real + max_bend*dx/abs(dx), p2.imag) p3 = complex(h.end.real + max_bend*dx/abs(dx), p3.imag) if abs(dy) > max_bend: dv = dy - max_bend*dy/abs(dy) v = Line(complex(end.real, end.imag-dv), end) p2 = complex(p2.real, h.end.imag + 0.25*max_bend*dy/abs(dy)) p3 = complex(p3.real, h.end.imag + max_bend*dy/abs(dy)) c = CubicBezier(complex(start.real+dh, start.imag), p1, p2, p3) segs = [h,c,v] if v.start == v.end: segs.remove(v) if h.start == h.end: segs.remove(h) return segs
def path_to_polygon(path): polygon = [] global max_x, max_y, min_x, min_y new_path = Path() for segment in parse_path(path): new_path.append(Line(segment.start, segment.end)) new_path.closed = True raw_path = new_path.d() # search for compound path bits and remove them path_bits = re.findall('M.+?[ZM]', raw_path) if len(path_bits) > 0: raw_path = path_bits[0] raw_path_size = len(re.findall(',', raw_path)) for bit in path_bits: bit_size = len(re.findall(',', bit)) if bit_size > raw_path_size: raw_path = bit raw_path_size = bit_size # convert to simple list of nodes nodes = re.findall('[ML]\s*(\d+\.*\d*,\d+\.*\d*)\s*', raw_path) for n in nodes: coords = n.split(',') if max_x < int(coords[0]): max_x = int(coords[0]) if max_y < int(coords[1]): max_y = int(coords[1]) if min_x > int(coords[0]): min_x = int(coords[0]) if min_y > int(coords[1]): min_y = int(coords[1]) polygon.append([int(coords[0]), int(coords[1])]) polygon.pop() return polygon
def mkLine(x1, y1, x2, y2): x1 = round(x1, 4) y1 = round(y1, 4) x2 = round(x2, 4) y2 = round(y2, 4) if (x1 == x2) and (y1 == y2): return None return Line(complex(x1, y1), complex(x2, y2))
def test_path_is_homogeneously_sampled(self): test_path = Path(Line(start=(0 + 0j), end=(5 + 0j))) expected_result = [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0)] traj = Trajectory() traj.paths.append(test_path) result = traj.get_path(0, 0, 1) for (x1, y1), (x2, y2) in zip(result, expected_result): self.assertAlmostEqual(x1, x2, delta=0.01) self.assertAlmostEqual(y1, y2, delta=0.01)
def clean(points: List, new_path: svg.path.Path, height): count = len(points) i = 0 while i < count: x1, y1 = points[i] while count >= 3: x2, y2 = points[(i + 1) % count] x3, y3 = points[(i + 2) % count] a = atan2(y2 - y1, x2 - x1) b = atan2(y3 - y2, x3 - x2) test = abs(a - b) <= ANGLE_TOLERANCE if not test: break points.pop((i + 1) % count) if i + 1 >= count: i -= 1 count -= 1 i += 1 prev_point = complex( points[0][0] * INPUT_SCALE + INPUT_TRANSLATE_X, points[0][1] * INPUT_SCALE + INPUT_TRANSLATE_Y + height) start_point = prev_point new_path.append(Move(to=prev_point)) for point in points[1:]: new_point = complex( point[0] * INPUT_SCALE + INPUT_TRANSLATE_X, point[1] * INPUT_SCALE + INPUT_TRANSLATE_Y + height) new_path.append(Line(start=prev_point, end=new_point)) prev_point = new_point pass new_path.append(Close(start=prev_point, end=start_point)) pass
def test_get_path_not_starting_at_0(self): test_path = Path(Line(start=(0 + 0j), end=(2 + 2j)), Line(start=(2 + 2j), end=(7 + 1j)), Line(start=(7 + 1j), end=(0 + 0j))) expected_result = Trajectory.discretize( Path(Line(start=(2 + 2j), end=(7 + 1j)), Line(start=(7 + 1j), end=(0 + 0j)), Line(start=(0 + 0j), end=(2 + 2j))), 1) traj = Trajectory() traj.paths.append(test_path) result = traj.get_path(0, 1, 1) self.assertEqual(result, expected_result)
def test_get_normalized_path(self): traj = Trajectory() test_path = Path(Line(start=(0 + 0j), end=(2 + 2j)), Line(start=(2 + 2j), end=(7 + 1j)), Line(start=(7 + 1j), end=(0 + 0j))) expected_path = Trajectory.scale( Trajectory.discretize( Path(Line(start=(0 + 0j), end=(2 + 2j)), Line(start=(2 + 2j), end=(7 + 1j)), Line(start=(7 + 1j), end=(0 + 0j))), 1), 1 / 7.0, 1 / 2.0) traj.paths.append(test_path) normalized_trajectory = traj.get_normalized_path(0) self.assertGreater(len(normalized_trajectory), 3) for (x1, y1), (x2, y2) in zip(normalized_trajectory, expected_path): self.assertAlmostEqual(x1, x2, delta=0.01) self.assertAlmostEqual(y1, y2, delta=0.01)
def get_path_segments(start, end, start_direction, end_direction, max_bend= 40, adjust= None): """get an svg.Path() object from 'start' to 'end'. directionality is 'v' (vertical) or 'h' (horizontal). resulting path type is something like: R------------S (l-shaped) R----- \ \ -----S (s-shaped) or R------- \ | | S (j-shaped) depending on given path directionality of reaction and species. """ start_direction = start_direction[0].lower() end_direction = end_direction[0].lower() shape = start_direction + end_direction dx = end.real - start.real dy = end.imag - start.imag if start.real==end.real or start.imag==end.imag: segs = [Line(start, end)] elif shape == 'vh': # j-shaped if adjust == None: adjust = [0,0] segs = path_segments_vh(start, end, max_bend, adjust) elif shape == 'hv': # j-shaped if adjust == None: adjust = [0,0] segs = path_segments_hv(start, end, max_bend, adjust) elif shape == 'vv': # s-shaped if adjust == None: adjust = [0, 0] midpoint = [start.real + 0.5*dx, start.imag + 0.5*dy] elif len(adjust) == 2: midpoint = [start.real + 0.5*dx, start.imag + 0.5*dy + adjust[0]*dy/abs(dy)] else: midpoint = [start.real + 0.5*dx, start.imag + (adjust[2] + adjust[0])*dy/abs(dy)] # same as vh -- hv seg1 = path_segments_vh( start, complex(*midpoint), max_bend, adj = [adjust[0], 0]) seg2 = path_segments_hv( complex(*midpoint), end, max_bend, adj = [0, adjust[1]]) segs = seg1+seg2 elif shape == 'hh': # s-shaped if adjust == None: adjust = [0, 0] midpoint = [start.real + 0.5*dx, start.imag + 0.5*dy] elif len(adjust) == 2: midpoint = [start.real + 0.5*dx + adjust[0]*dx/abs(dx), start.imag + 0.5*dy] else: midpoint = [start.real + (adjust[2] + adjust[0])*dx/abs(dx), start.imag + 0.5*dy] # same as hv -- vh seg1 = path_segments_hv( start, complex(*midpoint), max_bend, adj = [adjust[0], 0]) seg2 = path_segments_vh( complex(*midpoint), end, max_bend, adj = [0, adjust[1]]) segs = seg1+seg2 return segs
def test_eq(self, element): Elements.test_eq(self, element) assert not (element['obj'] == Line(0, 1 + 1j))
def test_eq(self, element): Elements.test_eq(self, element) assert not element['obj'] == QuadraticBezier(0, 1 + 1j, 2) assert not element['obj'] == SymbolicLine(Line(0 + 0j, 10 + 0j))
def element(self, request): element_ = Line(*request.param) obj = SymbolicLine(element_) return {'obj': obj, 'base': element_}
SVG2 = ''' </g> </svg> ''' svgpath1 = '''<path style="fill:none;fill-rule:evenodd;stroke:#{0:02x}{1:02x}{2:02x};stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="''' svgpath2 = ''' Z"/> ''' print(SVG1) for p in paths: path = parse_path(p.attrib["d"]) start = None myrange = list(frange(0, 1, decimal.Decimal('0.01'))) mycolors = list(red.range_to(blue, len(myrange))) for i, px in enumerate(myrange): new_path = Path() if start is None: start = path.point(float(px)) else: end = path.point(float(px)) new_path.append(Line(start, end)) start = end color = mycolors[i] r = int(color.red * 255) g = int(color.green * 255) b = int(color.blue * 255) print((svgpath1 + new_path.d() + svgpath2).format(clamp(r), clamp(g), clamp(b))) print(SVG2)
def createLineSegment(currentPoint, previousPoint): lineSegment = Line(previousPoint, currentPoint) # print(lineSegment) return lineSegment
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="''' svgpath2 = ''' Z"/> ''' print(SVG1) for p in paths: new_path = Path() path = parse_path(p.attrib["d"]) start = None for command in path: if type(command) == Line: new_path.append(command) start = command.end ##print(command) else: not_line_path = Path(command) for px in list(frange(0, 1, decimal.Decimal(factor))): if start is None: start = not_line_path.point(float(px)) else: end = not_line_path.point(float(px)) new_command = Line(start, end) new_path.append(new_command) ##print(new_command) start = end new_path_str = new_path.d() start = None print(svgpath1 + new_path_str + svgpath2) print(SVG2)
def _pairwise_overlap_check(lines, to_update, to_remove): """Naive N^2 search for overlapping lines within a slope-intersect bucket. Adds lines to the to_remove dictionary when they are fully redundant, and adds updated line info to the to_update dictionary if a line needs to be lengthened to simplify partially overlapping lines. Returns True if a line update was produced (which means another pass of overlap-checking is required with the updated line info. """ for i in range(len(lines)): if lines[i]['overall_index'] in to_remove: continue line1 = lines[i]['line'] for j in range(i + 1, len(lines)): if lines[j]['overall_index'] in to_remove: continue line2 = lines[j]['line'] if _lines_are_collinear(line1, line2): # Check for overlap using the min/max x and y values of the lines l1x1 = min(line1.start.real, line1.end.real) l1x2 = max(line1.start.real, line1.end.real) l1y1 = min(line1.start.imag, line1.end.imag) l1y2 = max(line1.start.imag, line1.end.imag) l2x1 = min(line2.start.real, line2.end.real) l2x2 = max(line2.start.real, line2.end.real) l2y1 = min(line2.start.imag, line2.end.imag) l2y2 = max(line2.start.imag, line2.end.imag) if l1x1 <= l2x1 + eps and l1x2 + eps >= l2x2 and l1y1 <= l2y1 + eps and l1y2 + eps >= l2y2: # Line 1 is bigger, fully contains line 2 assert line1.length() + eps >= line2.length() to_remove[lines[j]['overall_index']] = (lines[j]['path_index'], lines[j]['line_index'], line2) elif l1x1 + eps >= l2x1 and l1x2 <= l2x2 + eps and l1y1 + eps >= l2y1 and l1y2 <= l2y2 + eps: # Line 2 is bigger, fully contains line 1 assert line2.length() + eps >= line1.length() to_remove[lines[i]['overall_index']] = (lines[i]['path_index'], lines[i]['line_index'], line1) # Check for partial overlap, i.e. one point of line 2 is between points of line 1 or vice versa # To avoid cases with 2 line segments end-to-end, we check for point containment with either # X inclusive OR Y inclusive, but not both (which would mean they share an endpoint and therefore # must be end-to-end rather than overlapping since we already covered the fully-contained cases # above) elif ( (l1x1 <= l2x1 + eps and l2x1 <= l1x2 + eps and l1y1 + eps < l2y1 and l2y1 + eps < l1y2) or (l1x1 + eps < l2x1 and l2x1 + eps < l1x2 and l1y1 <= l2y1 + eps and l2y1 <= l1y2 + eps) or (l1x1 <= l2x2 + eps and l2x2 <= l1x2 + eps and l1y1 + eps < l2y2 and l2y2 + eps < l1y2) or (l1x1 + eps < l2x2 and l2x2 + eps < l1x2 and l1y1 <= l2y2 + eps and l2y2 <= l1y2 + eps) ): print 'Partial overlap of these lines:\n {!r}\n {!r}'.format(line1, line2) # Arbitrarily pick line1 to remove, and update line2 to cover the full length to_remove[lines[i]['overall_index']] = ( lines[i]['path_index'], lines[i]['line_index'], line1, ) if lines[i]['overall_index'] in to_update: # In case we're now deleting a line that was previously updated, remove it from # to_update to be safe del to_update[lines[i]['overall_index']] # To form a line that covers the full length, try all pairs of points and select the pair # that produces the longest length. # # Simply sorting the points as x,y tuples and choosing the first/last wouldn't work because # of possible floating point error: if the 2 lines have the exact same x coordinate then the # sort will fall back to sorting on y and work as expected, but if the 2 lines have the # "same" x coordinate but one is actually a miniscule amount smaller, that x difference will # take precedence in the sort, potentially resulting in the wrong endpoints being selected. points = [ line1.start, line1.end, line2.start, line2.end, ] longest_line = line1 for x in range(len(points)): for y in range(x + 1, len(points)): new_line = Line(points[x], points[y]) if new_line.length() > longest_line.length(): longest_line = new_line # Update the original line's values (needed for subsequent comparisons of collinear lines) # and log an entry in to_update for the final SVG path generation. line2.start = longest_line.start line2.end = longest_line.end to_update[lines[j]['overall_index']] = ( lines[j]['path_index'], lines[j]['line_index'], line2, ) print ' -- merged into a single line: {!r}'.format(line2) return True return False
def parse_polygon(points: str) -> Path: coordinates = [tuple(map(float, c.split(','))) for c in points.split()] path = Path() for a, b in pairwise(coordinates): path.append(Line(complex(*a), complex(*b))) return path
def StraightenCurve(self, data, samples): #todo: implement samples line = Line(data.point(0), data.point(1)) return line