示例#1
0
def add_grid(obj: svgwrite.Drawing, s=10):
    """
    function to add grid to SVG drawing, must be called after set_bg_color if used
    :param obj: svgwrite.Drawing object
    :param s: grid step
    :return: None
    """
    pattern_small_grid = obj.pattern(insert=None, size=(s, s), patternUnits='userSpaceOnUse')
    pattern_small_grid.attribs['id'] = "smallGrid"
    path_small = obj.path(f'M {s} 0 L 0 0 0 {s}', stroke='gray', fill='none', style=f'stroke-width:{0.5}')
    pattern_small_grid.add(path_small)

    pattern_large_grid = obj.pattern(insert=None, size=(s * 10, s * 10), patternUnits='userSpaceOnUse')
    pattern_large_grid.attribs['id'] = "grid"
    path_large = obj.path(f'M {s * 10} 0 L 0 0 0 {s * 10}', stroke='gray', fill='none', style=f'stroke-width:{1}')
    pattern_large_grid.add(path_large)

    rect_large = obj.rect(fill="url(#smallGrid)", size=(s * 10, s * 10))
    pattern_large_grid.add(rect_large)

    obj.defs.add(pattern_small_grid)
    obj.defs.add(pattern_large_grid)

    rect_grid = obj.rect(size=('100%', '100%'), fill="url(#grid)")
    obj.add(rect_grid)
示例#2
0
    def render(self, dwg: Drawing) -> Group:
        g = dwg.g()

        leg = dwg.path(fill=self.leg_color)
        leg.push("M 0 0")
        leg.push("L 0 %f" % self.leg_length)
        leg.push("l %f 0" % self.thickness_foot)
        leg.push("L %f 0" % self.thickness_thigh)
        leg.push("Z")

        g.add(leg)

        boot_start = .7
        boot_height = self.boot_height
        foot_length = self.foot_length

        boot = dwg.path(fill=self.foot_color)
        boot.push("M 0 %f" % (self.leg_length * boot_start))
        boot.push("L 0 %f" % (self.leg_length + boot_height))
        boot.push("l %f 0" % foot_length)
        boot.push(
            "a %f %f 0 0 0 %f %f" %
            (min(boot_height, abs(foot_length - self.thickness_foot)),
             boot_height, -min(boot_height, foot_length - self.thickness_foot),
             -boot_height))
        boot.push("L %f %f" %
                  (self.thickness_thigh -
                   (self.thickness_thigh - self.thickness_foot) * boot_start +
                   1, self.leg_length * boot_start))

        g.add(boot)

        return g
示例#3
0
def create_path(legendspec, curve, dwg: svgwrite.Drawing, s):
    path_base = dwg.path(d=curve)
    path_base.fill("none").stroke(legendspec[0], width=6 if not s else 3)
    paths = [path_base]
    if legendspec[1]:
        path_dashes = dwg.path(d=curve)
        path_dashes.fill("none").stroke(legendspec[2][1],
                                        width=6 if not s else 3)
        path_dashes.dasharray(legendspec[2][0])
        paths.append(path_dashes)
    for i in paths:
        dwg.add(i)
    return paths
示例#4
0
文件: write.py 项目: i5anoff/gridfont
class Writer():
    def __init__(self, fontfn, outfn, size, pad=0, sw=0.2, nl=10):
        self.pos = (pad, pad)
        self.sw = sw
        self.pad = pad
        self.nl = nl
        self.xdst = 1
        self.dwg = Drawing(str(outfn), size=size, profile='tiny', debug=False)
        with open(str(fontfn), 'r') as f:
            self.symbols = load(f)['symbols']

    def newline(self):
        self.pos = (self.pad, self.pos[1] + self.nl)

    def write(self, phrase):
        for s in phrase:
            if s in self.symbols:
                o = self.symbols[s]
                gw = o['w']
                paths = o['paths']
                for path in paths:
                    self.dwg.add(
                        self.dwg.path(d=tosvgpath(
                            list(shift_path(path, self.pos))),
                                      stroke=black,
                                      fill='none',
                                      stroke_width=self.sw))
                self.pos = _rel_move(self.pos, (gw + self.xdst, 0))
            else:
                print('symbol not found: {:s}'.format(s))
        self.dwg.save(pretty=True, indent=2)
示例#5
0
def test_jump_reduction():
    paths = []
    rect_width = 100
    rect_height = rect_width / 2
    for i in range(3):
        y_offset = rect_width*i*1j
        corners = [rect_height, rect_width+rect_height,
                   rect_width+rect_height + rect_height*1j,
                   rect_height*1j+ rect_height]
        corners = [c+y_offset for c in corners]
        lines = [Line(start=corners[j], end=corners[(j+1) % len(corners)])
                 for j in range(len(corners))]
        _path = Path(*lines)
        _path = _path.rotated(i*20)
        paths += list(_path)

    max_y = max([p.start.imag for p in paths]+[p.end.imag for p in paths])
    max_x = max([p.start.real for p in paths]+[p.end.real for p in paths])
    filename = "test_jump_reduction.svg"
    viewbox = [0, -rect_height, max_x+2*rect_height, max_y+2*rect_height]
    dwg = Drawing(filename, width="10cm",
                  viewBox=" ".join([str(b) for b in viewbox]))
    dwg.add(dwg.path(d=Path(*paths).d()))
    dwg.save()
    dig = Digitizer()
    dig.filecontents = open(filename, "r").read()
    dig.svg_to_pattern()
    pattern_to_svg(dig.pattern, join(filename + ".svg"))
示例#6
0
def save_pie_chart(filename, all_angles, step_size, colors):

    #  create the drawing surface
    svg_drawing = Drawing(filename=filename,
                          size=(SVG_SIZE, SVG_SIZE),
                          debug=True)

    start_x = SVG_SIZE // 2
    start_y = SVG_SIZE // 2
    radius = SVG_SIZE // 2

    radians0 = all_angles[-1]
    for i in range(len(all_angles)):
        radians1 = all_angles[i]
        dx0 = radius * (math.sin(radians0))
        dy0 = radius * (math.cos(radians0))
        dx1 = radius * (math.sin(radians1))
        dy1 = radius * (math.cos(radians1))

        m0 = round(dy0, 9)
        n0 = round(-dx0, 9)
        m1 = round(-dy0 + dy1, 9)
        n1 = round(dx0 - dx1, 9)

        w = svg_drawing.path(
            d="M {0},{1} l {2},{3} a {4},{4} 0 0,0 {5},{6} z".format(
                start_x, start_y, m0, n0, radius, m1, n1),
            fill=colors[i],
            stroke="none",
        )
        svg_drawing.add(w)
        radians0 = radians1

    svg_drawing.save()
示例#7
0
 def _draw_circle_segment(
     self,
     dr: svgwrite.Drawing,
     tracks: List[Track],
     a1: float,
     a2: float,
     rr: ValueRange,
     center: XY,
 ):
     length = sum([t.length for t in tracks])
     has_special = len([t for t in tracks if t.special]) > 0
     color = self.color(self.poster.length_range_by_date, length,
                        has_special)
     r1 = rr.lower()
     r2 = (
         rr.lower() +
         rr.diameter() * length / self.poster.length_range_by_date.upper())
     sin_a1, cos_a1 = math.sin(a1), math.cos(a1)
     sin_a2, cos_a2 = math.sin(a2), math.cos(a2)
     path = dr.path(
         d=("M", center.x + r1 * sin_a1, center.y - r1 * cos_a1),
         fill=color,
         stroke="none",
     )
     path.push("l", (r2 - r1) * sin_a1, (r1 - r2) * cos_a1)
     path.push(
         f"a{r2},{r2} 0 0,0 {r2 * (sin_a2 - sin_a1)},{r2 * (cos_a1 - cos_a2)}"
     )
     path.push("l", (r1 - r2) * sin_a2, (r2 - r1) * cos_a2)
     dr.add(path)
示例#8
0
def _line_to_path(dwg: svgwrite.Drawing, lines: Union[np.ndarray,
                                                      LineCollection]):
    """Convert a line into a SVG path element.

    Accepts either a single line or a :py:class:`LineCollection`.

    Args:
        lines: line(s) to convert to path

    Returns:
        (svgwrite element): path element

    """

    if isinstance(lines, np.ndarray):
        lines = [lines]

    def single_line_to_path(line: np.ndarray) -> str:
        if line[0] == line[-1]:
            closed = True
            line = line[:-1]
        else:
            closed = False
        return ("M" + " L".join(f"{x},{y}" for x, y in as_vector(line)) +
                (" Z" if closed else ""))

    return dwg.path(" ".join(single_line_to_path(line) for line in lines))
示例#9
0
 def _draw_circle_segment(
     self,
     dr: svgwrite.Drawing,
     g: svgwrite.container.Group,
     tracks: typing.List[Track],
     a1: float,
     a2: float,
     rr: ValueRange,
     center: XY,
 ) -> None:
     length = sum([t.length() for t in tracks])
     has_special = len([t for t in tracks if t.special]) > 0
     color = self.color(self.poster.length_range_by_date, length,
                        has_special)
     max_length = self.poster.length_range_by_date.upper()
     assert max_length is not None
     r1 = rr.lower()
     assert r1 is not None
     r2 = rr.interpolate((length / max_length).magnitude)
     sin_a1, cos_a1 = math.sin(a1), math.cos(a1)
     sin_a2, cos_a2 = math.sin(a2), math.cos(a2)
     path = dr.path(
         d=("M", center.x + r1 * sin_a1, center.y - r1 * cos_a1),
         fill=color,
         stroke="none",
     )
     path.push("l", (r2 - r1) * sin_a1, (r1 - r2) * cos_a1)
     path.push(
         f"a{r2},{r2} 0 0,0 {r2 * (sin_a2 - sin_a1)},{r2 * (cos_a1 - cos_a2)}"
     )
     path.push("l", (r1 - r2) * sin_a2, (r2 - r1) * cos_a2)
     date_title = str(tracks[0].start_time().date())
     str_length = utils.format_float(self.poster.m2u(length))
     path.set_desc(title=f"{date_title} {str_length} {self.poster.u()}")
     g.add(path)
示例#10
0
def draw_paths(fn, bbox, paths, pad=(0, 0), sw=0.1):
    w, h = bbox
    sx, sy = pad
    dwg = Drawing(str(fn),
                  size=(w + 2 * sx, h + 2 * sy),
                  profile='tiny',
                  debug=False)

    bbox_path = tosvgpath(_box(bbox), pad, closed=True)
    dwg.add(dwg.path(d=bbox_path, stroke=accent, stroke_width=sw, fill='none'))

    for path in paths:
        dwg.add(
            dwg.path(d=tosvgpath(path, pad),
                     stroke=black,
                     fill='none',
                     stroke_width=sw))
    dwg.save(pretty=True, indent=2)
示例#11
0
    def render(self, dwg: Drawing) -> Group:
        g = dwg.g()

        p = dwg.path(stroke=self.color, fill_opacity=0, stroke_width=3)
        p.push("M %f %f" % (-self.width / 2, 0))
        p.push("Q %f %f %f %f" %
               (0, self.width * self.intensity, self.width / 2, 0))

        g.add(p)

        return g
示例#12
0
    def get_path(self, svg: Drawing, point: np.array):
        """
        Draw icon into SVG file.

        :param svg: SVG file to draw to
        :param point: icon position
        """
        shift: np.array = self.offset + point

        return svg.path(d=self.path,
                        transform=f"translate({shift[0]},{shift[1]})")
示例#13
0
    def render(self, dwg: Drawing) -> Group:
        g = dwg.g()

        p = dwg.path(fill="black", stroke_width=0)
        p.push("M %f %f" % (-self.width / 2, 0))
        p.push("Q %f %f %f %f" %
               (0, self.width * self.intensity, self.width / 2, 0))
        p.push("Z")

        g.add(p)

        return g
示例#14
0
 def _draw_circle_segment(self, d: svgwrite.Drawing, tracks: List[Track], a1: float, a2: float,
                          rr: ValueRange, center: XY):
     length = sum([t.length for t in tracks])
     color = self.color(self.poster.length_range_by_date, length, [t for t in tracks if t.special])
     r1 = rr.lower()
     r2 = rr.lower() + rr.diameter() * length / self.poster.length_range_by_date.upper()
     sin_a1, cos_a1 = math.sin(a1), math.cos(a1)
     sin_a2, cos_a2 = math.sin(a2), math.cos(a2)
     path = d.path(d=('M', center.x + r1 * sin_a1, center.y - r1 * cos_a1), fill=color, stroke='none')
     path.push('l', (r2 - r1) * sin_a1, (r1 - r2) * cos_a1)
     path.push('a{},{} 0 0,0 {},{}'.format(r2, r2, r2 * (sin_a2 - sin_a1), r2 * (cos_a1 - cos_a2)))
     path.push('l', (r1 - r2) * sin_a2, (r2 - r1) * cos_a2)
     d.add(path)
示例#15
0
    def draw_path_clip(self):
        path_filename = "{}/path_clip_{}.svg".format(
            self.output_folder,
            basename(self.filename).replace(".svg", ""))
        dwg = Drawing(path_filename)
        image_bbox = calc_overall_bbox(self.tile_paths)

        dx = self.pent_x - min(image_bbox[0], image_bbox[1])
        dy = self.pent_y - min(image_bbox[2], image_bbox[3])
        dwg.add(
            dwg.path(
                **{
                    "d": self.new_pentagon().d(),
                    "fill": "none",
                    'stroke-width': 4,
                    'stroke': rgb(0, 0, 0)
                }))
        neg_transform = "translate({}, {})".format(-dx, -dy)
        transform = "translate({}, {})".format(dx, dy)
        clip_path = dwg.defs.add(
            dwg.clipPath(id="pent_path", transform=neg_transform))
        clip_path.add(dwg.path(d=self.new_pentagon().d()))
        group = dwg.add(
            dwg.g(clip_path="url(#pent_path)",
                  transform=transform,
                  id="clippedpath"))
        for i, path in enumerate(self.tile_paths):
            group.add(
                dwg.path(d=path.d(),
                         style=self.tile_attributes[i].get('style'),
                         id=self.tile_attributes[i]['id']))
        dwg.add(dwg.use("#clippedpath", transform="transform(100, 100)"))

        dwg.viewbox(self.pent_x, self.pent_y, self.pent_width,
                    self.pent_height)
        dwg.save()
        xml = xml.dom.minidom.parse(path_filename)
        open(path_filename, "w").write(xml.toprettyxml())
示例#16
0
    def add(self, svg: Drawing) -> None:
        point: np.array = to_grid(self.point)
        radius: float = 7.5
        a1 = self.angle + math.pi / 9.0
        a2 = self.angle - math.pi / 9.0
        n1 = np.array((math.cos(a1), math.sin(a1)))
        n2 = np.array((math.cos(a2), math.sin(a2)))
        p1 = point + n1 * radius
        p2 = point + n1 * 20
        p3 = point + n2 * 20
        p4 = point + n2 * radius
        svg.add(
            svg.path(d=["M", p1, "C", p2, p3, p4],
                     fill="none",
                     stroke="black",
                     stroke_width=0.5))

        n = (p4 - p3) / np.linalg.norm(p4 - p3)
        svg.add(
            svg.path(d=create_v(p4, n,
                                np.dot(rotation_matrix(-math.pi / 2.0), n)),
                     stroke_width=0.5,
                     fill="none",
                     stroke="black"))
示例#17
0
 def draw_single_pentagon(self):
     pentagon = self.new_pentagon()
     dwg = Drawing("{}/single_pentagon.svg".format(self.output_folder),
                   profile='tiny')
     dwg.add(
         dwg.path(
             **{
                 'd': pentagon.d(),
                 'fill': "none",
                 'stroke-width': 4,
                 'stroke': rgb(0, 0, 0)
             }))
     dwg.viewbox(self.pent_x, self.pent_y, self.pent_width,
                 self.pent_height)
     dwg.save()
示例#18
0
    def _draw_year(self, d: svgwrite.Drawing, size: XY, offset: XY, year: int):
        min_size = min(size.x, size.y)
        outer_radius = 0.5 * min_size - 6
        radius_range = ValueRange.from_pair(outer_radius / 4, outer_radius)
        center = offset + 0.5 * size

        if self._rings:
            self._draw_rings(d, center, radius_range)

        year_style = 'dominant-baseline: central; font-size:{}px; font-family:Arial;'.format(min_size * 4.0 / 80.0)
        month_style = 'font-size:{}px; font-family:Arial;'.format(min_size * 3.0 / 80.0)

        d.add(d.text('{}'.format(year), insert=center.tuple(), fill=self.poster.colors['text'], text_anchor="middle",
                     alignment_baseline="middle", style=year_style))
        df = 360.0 / (366 if calendar.isleap(year) else 365)
        day = 0
        date = datetime.date(year, 1, 1)
        while date.year == year:
            text_date = date.strftime("%Y-%m-%d")
            a1 = math.radians(day * df)
            a2 = math.radians((day + 1) * df)
            if date.day == 1:
                (_, last_day) = calendar.monthrange(date.year, date.month)
                a3 = math.radians((day + last_day - 1) * df)
                sin_a1, cos_a1 = math.sin(a1), math.cos(a1)
                sin_a3, cos_a3 = math.sin(a3), math.cos(a3)
                r1 = outer_radius + 1
                r2 = outer_radius + 6
                r3 = outer_radius + 2
                d.add(d.line(
                    start=(center + r1 * XY(sin_a1, -cos_a1)).tuple(),
                    end=(center + r2 * XY(sin_a1, -cos_a1)).tuple(),
                    stroke=self.poster.colors['text'],
                    stroke_width=0.3))
                path = d.path(d=('M', center.x + r3 * sin_a1, center.y - r3 * cos_a1), fill='none', stroke='none')
                path.push('a{},{} 0 0,1 {},{}'.format(r3, r3, r3 * (sin_a3 - sin_a1), r3 * (cos_a1 - cos_a3)))
                d.add(path)
                tpath = svgwrite.text.TextPath(path, date.strftime("%B"), startOffset=(0.5 * r3 * (a3 - a1)))
                text = d.text("", fill=self.poster.colors['text'], text_anchor="middle", style=month_style)
                text.add(tpath)
                d.add(text)
            if text_date in self.poster.tracks_by_date:
                self._draw_circle_segment(d, self.poster.tracks_by_date[text_date], a1, a2, radius_range, center)

            day += 1
            date += datetime.timedelta(1)
示例#19
0
class Writer():
  def __init__(self, fontfn, outfn, size, pad=0, sw=0.2, nl=10, xdst=1):
    self.pos = (pad, pad)
    self.sw = sw
    self.pad = pad
    self.nl = nl
    self.xdst = xdst
    self.dwg = Drawing(str(outfn), size=size, profile='tiny', debug=False)
    with open(str(fontfn), 'r') as f:
      self.symbols = load(f)['symbols']

  def newline(self):
    self.pos = (self.pad, self.pos[1] + self.nl)

  def scale(self, s):
    for o in self.symbols.values():
      w = o['w']
      h = o['h']
      new_paths = []
      for path in o['paths']:
        new_path = []
        for x, y in path:
          new_path.append((s*(x-w*0.5)+w*0.5*s, s*(y-h*0.5)+h*0.5*s))
        new_paths.append(new_path)
      o.update({'w': w*s, 'h': h*s, 'paths': new_paths})
    return self

  def write(self, phrase):
    for s in phrase:
      if s in self.symbols:
        o = self.symbols[s]
        gw = o['w']
        paths = o['paths']
        for path in paths:
          self.dwg.add(
              self.dwg.path(
                  d=tosvgpath(list(shift_path(path, self.pos))),
                  stroke=black,
                  fill='none',
                  stroke_width=self.sw))
        self.pos = _rel_move(self.pos, (gw + self.xdst, 0))
      else:
        print('symbol not found: {:s}'.format(s))
    self.dwg.save(pretty=True, indent=2)
示例#20
0
def export_svg_svgwrite(fn, paths, w, h, line_width=0.1):

  from svgwrite import Drawing
  w_str = "{}pt".format(w)
  h_str = "{}pt".format(h)

  dwg = Drawing(filename = fn,
                size = (w_str, h_str),
                viewBox=("0 0 {} {}".format(w,h)))

  for path in paths:
    if(len(path) > 1):
      str_list = []
      str_list.append("M {},{}".format(path[0,0],path[0,1]))
      for e in path[1:]:
        str_list.append(" L {},{}".format(e[0],e[1]))
      s = ''.join(str_list)
      dwg.add(dwg.path(s).stroke(color="rgb(0%,0%,0%)",width=line_width).fill("none"))

  dwg.save()
示例#21
0
    def add(self, svg: Drawing) -> None:

        a: np.array = to_grid(self.point1)
        b: np.array = to_grid(self.point2)
        n = (b - a) / np.linalg.norm((b - a))
        na = a + (n * self.radius)
        nb = b - (n * self.radius)
        line = svg.line(na, nb, stroke_width=0.5, fill="none", stroke="black")
        if not self.is_feasible:
            line.update({"stroke-dasharray": "1,1"})
        svg.add(line)

        v = svg.path(d=create_v(nb, n,
                                np.dot(rotation_matrix(-math.pi / 2.0), n)),
                     stroke_width=0.5,
                     fill="none",
                     stroke="black")
        if not self.is_feasible:
            v.update({"stroke-dasharray": "1,1"})
        svg.add(v)
示例#22
0
    def generate_tiling(self):
        dwg = Drawing("{}/tiling2.svg".format(self.output_folder),
                      profile="tiny")

        current_color = 0
        row_spacing = self.pent_height * 2 + self.bottom_length

        for y in range(self.num_down):
            transform = "translate({}, {})".format(0, self.rep_spacing * y)
            dgroup = dwg.add(dwg.g(transform=transform))
            for x in range(self.num_across):
                # if x is odd, point 1 of pent 1 needs to be attached to point 3 of pent 2
                if x % 2 == 1:
                    dx = int(
                        x / 2
                    ) * self.rep_spacing + self.pent_width * 2 + self.column_offset.real
                    transform = "translate({}, {})".format(
                        dx, self.column_offset.imag)
                else:
                    transform = "translate({}, {})".format(
                        int(x / 2) * self.rep_spacing, 0)
                group = dgroup.add(dwg.g(transform=transform))
                for pent in self.cairo_group:
                    group.add(
                        dwg.path(
                            **{
                                'd':
                                pent.d(),
                                'fill':
                                self._colors[current_color %
                                             len(self._colors)],
                                'stroke-width':
                                4,
                                'stroke':
                                rgb(0, 0, 0)
                            }))
                    current_color += 1

        dwg.viewbox(*self.pattern_viewbox)
        dwg.save(pretty=True)
示例#23
0
def export_svg_svgwrite(fn, paths, w, h, line_width=0.1):

    from svgwrite import Drawing
    w_str = "{}pt".format(w)
    h_str = "{}pt".format(h)

    dwg = Drawing(filename=fn,
                  size=(w_str, h_str),
                  viewBox=("0 0 {} {}".format(w, h)))

    for path in paths:
        if (len(path) > 1):
            str_list = []
            str_list.append("M {},{}".format(path[0, 0], path[0, 1]))
            for e in path[1:]:
                str_list.append(" L {},{}".format(e[0], e[1]))
            s = ''.join(str_list)
            dwg.add(
                dwg.path(s).stroke(color="rgb(0%,0%,0%)",
                                   width=line_width).fill("none"))

    dwg.save()
示例#24
0
def save_pie_chart(filename, root_list, step_size):

    #  create the drawing surface
    svg_drawing = Drawing(filename=filename,
                          size=(SVG_SIZE, SVG_SIZE),
                          debug=True)

    start_x = SVG_SIZE // 2
    start_y = SVG_SIZE // 2
    radius = SVG_SIZE // 2

    all_angles = []
    for node in root_list:
        all_angles += node.pie_angle
    all_angles = sorted(all_angles)

    radians0 = all_angles[-1]
    for i in range(len(all_angles)):
        radians1 = all_angles[i]
        dx0 = radius * (math.sin(radians0))
        dy0 = radius * (math.cos(radians0))
        dx1 = radius * (math.sin(radians1))
        dy1 = radius * (math.cos(radians1))

        m0 = dy0
        n0 = -dx0
        m1 = -dy0 + dy1
        n1 = dx0 - dx1

        w = svg_drawing.path(
            d="M {0},{1} l {2},{3} a {4},{4} 0 0,0 {5},{6} z".format(
                start_x, start_y, m0, n0, radius, m1, n1),
            fill=colors[i],
            stroke="none",
        )
        svg_drawing.add(w)
        radians0 = radians1

    svg_drawing.save()
示例#25
0
def disvg(paths=None,
          colors=None,
          filename=None,
          stroke_widths=None,
          nodes=None,
          node_colors=None,
          node_radii=None,
          openinbrowser=True,
          timestamp=None,
          margin_size=0.1,
          mindim=600,
          dimensions=None,
          viewbox=None,
          text=None,
          text_path=None,
          font_size=None,
          attributes=None,
          svg_attributes=None,
          svgwrite_debug=False,
          paths2Drawing=False,
          baseunit='px'):
    """Creates (and optionally displays) an SVG file.

    REQUIRED INPUTS:
        :param paths - a list of paths

    OPTIONAL INPUT:
        :param colors - specifies the path stroke color.  By default all paths
        will be black (#000000).  This paramater can be input in a few ways
        1) a list of strings that will be input into the path elements stroke
            attribute (so anything that is understood by the svg viewer).
        2) a string of single character colors -- e.g. setting colors='rrr' is
            equivalent to setting colors=['red', 'red', 'red'] (see the
            'color_dict' dictionary above for a list of possibilities).
        3) a list of rgb 3-tuples -- e.g. colors = [(255, 0, 0), ...].

        :param filename - the desired location/filename of the SVG file
        created (by default the SVG will be named 'disvg_output.svg' or
        'disvg_output_<timestamp>.svg' and stored in the temporary
        directory returned by `tempfile.gettempdir()`.  See `timestamp`
        for information on the timestamp.

        :param stroke_widths - a list of stroke_widths to use for paths
        (default is 0.5% of the SVG's width or length)

        :param nodes - a list of points to draw as filled-in circles

        :param node_colors - a list of colors to use for the nodes (by default
        nodes will be red)

        :param node_radii - a list of radii to use for the nodes (by default
        nodes will be radius will be 1 percent of the svg's width/length)

        :param text - string or list of strings to be displayed

        :param text_path - if text is a list, then this should be a list of
        path (or path segments of the same length.  Note: the path must be
        long enough to display the text or the text will be cropped by the svg
        viewer.

        :param font_size - a single float of list of floats.

        :param openinbrowser -  Set to True to automatically open the created
        SVG in the user's default web browser.

        :param timestamp - if true, then the a timestamp will be
        appended to the output SVG's filename.  This is meant as a
        workaround for issues related to rapidly opening multiple
        SVGs in your browser using `disvg`. This defaults to true if
        `filename is None` and false otherwise.

        :param margin_size - The min margin (empty area framing the collection
        of paths) size used for creating the canvas and background of the SVG.

        :param mindim - The minimum dimension (height or width) of the output
        SVG (default is 600).

        :param dimensions - The (x,y) display dimensions of the output SVG.
        I.e. this specifies the `width` and `height` SVG attributes. Note that 
        these also can be used to specify units other than pixels. Using this 
        will override the `mindim` parameter.

        :param viewbox - This specifies the coordinated system used in the svg.
        The SVG `viewBox` attribute works together with the the `height` and 
        `width` attrinutes.  Using these three attributes allows for shifting 
        and scaling of the SVG canvas without changing the any values other 
        than those in `viewBox`, `height`, and `width`.  `viewbox` should be 
        input as a 4-tuple, (min_x, min_y, width, height), or a string 
        "min_x min_y width height".  Using this will override the `mindim` 
        parameter.

        :param attributes - a list of dictionaries of attributes for the input
        paths.  Note: This will override any other conflicting settings.

        :param svg_attributes - a dictionary of attributes for output svg.
        
        :param svgwrite_debug - This parameter turns on/off `svgwrite`'s 
        debugging mode.  By default svgwrite_debug=False.  This increases 
        speed and also prevents `svgwrite` from raising of an error when not 
        all `svg_attributes` key-value pairs are understood.
        
        :param paths2Drawing - If true, an `svgwrite.Drawing` object is 
        returned and no file is written.  This `Drawing` can later be saved 
        using the `svgwrite.Drawing.save()` method.

    NOTES:
        * The `svg_attributes` parameter will override any other conflicting 
        settings.

        * Any `extra` parameters that `svgwrite.Drawing()` accepts can be 
        controlled by passing them in through `svg_attributes`.

        * The unit of length here is assumed to be pixels in all variables.

        * If this function is used multiple times in quick succession to
        display multiple SVGs (all using the default filename), the
        svgviewer/browser will likely fail to load some of the SVGs in time.
        To fix this, use the timestamp attribute, or give the files unique
        names, or use a pause command (e.g. time.sleep(1)) between uses.

    SEE ALSO:
        * document.py
    """

    _default_relative_node_radius = 5e-3
    _default_relative_stroke_width = 1e-3
    _default_path_color = '#000000'  # black
    _default_node_color = '#ff0000'  # red
    _default_font_size = 12

    if filename is None:
        timestamp = True if timestamp is None else timestamp
        filename = os_path.join(gettempdir(), 'disvg_output.svg')

    dirname = os_path.abspath(os_path.dirname(filename))
    if not os_path.exists(dirname):
        makedirs(dirname)

    # append time stamp to filename
    if timestamp:
        fbname, fext = os_path.splitext(filename)
        tstamp = str(time()).replace('.', '')
        stfilename = os_path.split(fbname)[1] + '_' + tstamp + fext
        filename = os_path.join(dirname, stfilename)

    # check paths and colors are set
    if isinstance(paths, Path) or is_path_segment(paths):
        paths = [paths]
    if paths:
        if not colors:
            colors = [_default_path_color] * len(paths)
        else:
            assert len(colors) == len(paths)
            if isinstance(colors, str):
                colors = str2colorlist(colors,
                                       default_color=_default_path_color)
            elif isinstance(colors, list):
                for idx, c in enumerate(colors):
                    if is3tuple(c):
                        colors[idx] = "rgb" + str(c)

    # check nodes and nodes_colors are set (node_radii are set later)
    if nodes:
        if not node_colors:
            node_colors = [_default_node_color] * len(nodes)
        else:
            assert len(node_colors) == len(nodes)
            if isinstance(node_colors, str):
                node_colors = str2colorlist(node_colors,
                                            default_color=_default_node_color)
            elif isinstance(node_colors, list):
                for idx, c in enumerate(node_colors):
                    if is3tuple(c):
                        node_colors[idx] = "rgb" + str(c)

    # set up the viewBox and display dimensions of the output SVG
    # along the way, set stroke_widths and node_radii if not provided
    assert paths or nodes
    stuff2bound = []
    if viewbox:
        if not isinstance(viewbox, str):
            viewbox = '%s %s %s %s' % viewbox
        if dimensions is None:
            dimensions = viewbox.split(' ')[2:4]
    elif dimensions:
        dimensions = tuple(map(str, dimensions))

        def strip_units(s):
            return re.search(r'\d*\.?\d*', s.strip()).group()

        viewbox = '0 0 %s %s' % tuple(map(strip_units, dimensions))
    else:
        if paths:
            stuff2bound += paths
        if nodes:
            stuff2bound += nodes
        if text_path:
            stuff2bound += text_path
        xmin, xmax, ymin, ymax = big_bounding_box(stuff2bound)
        dx = xmax - xmin
        dy = ymax - ymin

        if dx == 0:
            dx = 1
        if dy == 0:
            dy = 1

        # determine stroke_widths to use (if not provided) and max_stroke_width
        if paths:
            if not stroke_widths:
                sw = max(dx, dy) * _default_relative_stroke_width
                stroke_widths = [sw] * len(paths)
                max_stroke_width = sw
            else:
                assert len(paths) == len(stroke_widths)
                max_stroke_width = max(stroke_widths)
        else:
            max_stroke_width = 0

        # determine node_radii to use (if not provided) and max_node_diameter
        if nodes:
            if not node_radii:
                r = max(dx, dy) * _default_relative_node_radius
                node_radii = [r] * len(nodes)
                max_node_diameter = 2 * r
            else:
                assert len(nodes) == len(node_radii)
                max_node_diameter = 2 * max(node_radii)
        else:
            max_node_diameter = 0

        extra_space_for_style = max(max_stroke_width, max_node_diameter)
        xmin -= margin_size * dx + extra_space_for_style / 2
        ymin -= margin_size * dy + extra_space_for_style / 2
        dx += 2 * margin_size * dx + extra_space_for_style
        dy += 2 * margin_size * dy + extra_space_for_style
        viewbox = "%s %s %s %s" % (xmin, ymin, dx, dy)

        if mindim is None:
            szx = "{}{}".format(dx, baseunit)
            szy = "{}{}".format(dy, baseunit)
        else:
            if dx > dy:
                szx = str(mindim) + baseunit
                szy = str(int(ceil(mindim * dy / dx))) + baseunit
            else:
                szx = str(int(ceil(mindim * dx / dy))) + baseunit
                szy = str(mindim) + baseunit
        dimensions = szx, szy

    # Create an SVG file
    if svg_attributes is not None:
        dimensions = (svg_attributes.get("width", dimensions[0]),
                      svg_attributes.get("height", dimensions[1]))
        debug = svg_attributes.get("debug", svgwrite_debug)
        dwg = Drawing(filename=filename,
                      size=dimensions,
                      debug=debug,
                      **svg_attributes)
    else:
        dwg = Drawing(filename=filename,
                      size=dimensions,
                      debug=svgwrite_debug,
                      viewBox=viewbox)

    # add paths
    if paths:
        for i, p in enumerate(paths):
            if isinstance(p, Path):
                ps = p.d()
            elif is_path_segment(p):
                ps = Path(p).d()
            else:  # assume this path, p, was input as a Path d-string
                ps = p

            if attributes:
                good_attribs = {'d': ps}
                for key in attributes[i]:
                    val = attributes[i][key]
                    if key != 'd':
                        try:
                            dwg.path(ps, **{key: val})
                            good_attribs.update({key: val})
                        except Exception as e:
                            warn(str(e))

                dwg.add(dwg.path(**good_attribs))
            else:
                dwg.add(
                    dwg.path(ps,
                             stroke=colors[i],
                             stroke_width=str(stroke_widths[i]),
                             fill='none'))

    # add nodes (filled in circles)
    if nodes:
        for i_pt, pt in enumerate([(z.real, z.imag) for z in nodes]):
            dwg.add(dwg.circle(pt, node_radii[i_pt], fill=node_colors[i_pt]))

    # add texts
    if text:
        assert isinstance(text, str) or (isinstance(text, list) and isinstance(
            text_path, list) and len(text_path) == len(text))
        if isinstance(text, str):
            text = [text]
            if not font_size:
                font_size = [_default_font_size]
            if not text_path:
                pos = complex(xmin + margin_size * dx, ymin + margin_size * dy)
                text_path = [Line(pos, pos + 1).d()]
        else:
            if font_size:
                if isinstance(font_size, list):
                    assert len(font_size) == len(text)
                else:
                    font_size = [font_size] * len(text)
            else:
                font_size = [_default_font_size] * len(text)
        for idx, s in enumerate(text):
            p = text_path[idx]
            if isinstance(p, Path):
                ps = p.d()
            elif is_path_segment(p):
                ps = Path(p).d()
            else:  # assume this path, p, was input as a Path d-string
                ps = p

            # paragraph = dwg.add(dwg.g(font_size=font_size[idx]))
            # paragraph.add(dwg.textPath(ps, s))
            pathid = 'tp' + str(idx)
            dwg.defs.add(dwg.path(d=ps, id=pathid))
            txter = dwg.add(dwg.text('', font_size=font_size[idx]))
            txter.add(txt.TextPath('#' + pathid, s))

    if paths2Drawing:
        return dwg

    dwg.save()

    # re-open the svg, make the xml pretty, and save it again
    xmlstring = md_xml_parse(filename).toprettyxml()
    with open(filename, 'w') as f:
        f.write(xmlstring)

    # try to open in web browser
    if openinbrowser:
        try:
            open_in_browser(filename)
        except:
            print("Failed to open output SVG in browser.  SVG saved to:")
            print(filename)
示例#26
0
    def _draw_year(self, dr: svgwrite.Drawing, g: svgwrite.container.Group,
                   size: XY, offset: XY, year: int) -> None:
        min_size = min(size.x, size.y)
        outer_radius = 0.5 * min_size - 6
        radius_range = ValueRange.from_pair(outer_radius / 4, outer_radius)
        center = offset + 0.5 * size

        if self._rings:
            self._draw_rings(dr, g, center, radius_range)

        year_style = f"dominant-baseline: central; font-size:{min_size * 4.0 / 80.0}px; font-family:Arial;"
        month_style = f"font-size:{min_size * 3.0 / 80.0}px; font-family:Arial;"

        g.add(
            dr.text(
                f"{year}",
                insert=center.tuple(),
                fill=self.poster.colors["text"],
                text_anchor="middle",
                alignment_baseline="middle",
                style=year_style,
            ))
        df = 360.0 / (366 if calendar.isleap(year) else 365)
        day = 0
        date = datetime.date(year, 1, 1)
        while date.year == year:
            text_date = date.strftime("%Y-%m-%d")
            a1 = math.radians(day * df)
            a2 = math.radians((day + 1) * df)
            if date.day == 1:
                (_, last_day) = calendar.monthrange(date.year, date.month)
                a3 = math.radians((day + last_day - 1) * df)
                sin_a1, cos_a1 = math.sin(a1), math.cos(a1)
                sin_a3, cos_a3 = math.sin(a3), math.cos(a3)
                r1 = outer_radius + 1
                r2 = outer_radius + 6
                r3 = outer_radius + 2
                g.add(
                    dr.line(
                        start=(center + r1 * XY(sin_a1, -cos_a1)).tuple(),
                        end=(center + r2 * XY(sin_a1, -cos_a1)).tuple(),
                        stroke=self.poster.colors["text"],
                        stroke_width=0.3,
                    ))
                path = dr.path(
                    d=("M", center.x + r3 * sin_a1, center.y - r3 * cos_a1),
                    fill="none",
                    stroke="none",
                )
                path.push(
                    f"a{r3},{r3} 0 0,1 {r3 * (sin_a3 - sin_a1)},{r3 * (cos_a1 - cos_a3)}"
                )
                g.add(path)
                tpath = svgwrite.text.TextPath(
                    path,
                    self.poster.month_name(date.month),
                    startOffset=(0.5 * r3 * (a3 - a1)))
                text = dr.text(
                    "",
                    fill=self.poster.colors["text"],
                    text_anchor="middle",
                    style=month_style,
                )
                text.add(tpath)
                g.add(text)
            if text_date in self.poster.tracks_by_date:
                self._draw_circle_segment(
                    dr,
                    g,
                    self.poster.tracks_by_date[text_date],
                    a1,
                    a2,
                    radius_range,
                    center,
                )

            day += 1
            date += datetime.timedelta(1)
def flatten_scene(pScene):
    lNode = pScene.GetRootNode()

    if not lNode:
        return

    for i in range(lNode.GetChildCount()):

        lChildNode = lNode.GetChild(i)
        if lChildNode.GetNodeAttribute() is None:
            continue
        lAttributeType = (lChildNode.GetNodeAttribute().GetAttributeType())
        if lAttributeType != FbxNodeAttribute.eMesh:
            continue
        lMesh = lChildNode.GetNodeAttribute()
        projected_points = {}
        control_points = lMesh.GetControlPoints()
        start_point = 0
        poly_paths = []
        for polygon_num in range(lMesh.GetPolygonCount()):
            corners = []
            for corner in range(3):
                corners.append(lMesh.GetPolygonVertex(polygon_num, corner))
            # first, check if any of the control points are already projected
            flattened = []
            for j, corner in enumerate(corners):
                if corner in projected_points:
                    flattened.append(projected_points[corner])
                    continue
                target_corner = corners[j - 1]
                current_vec = control_points[corner]
                target_vec = control_points[target_corner]
                angle = acos(
                    current_vec.DotProduct(target_vec) /
                    (current_vec.Length() * target_vec.Length()))
                length = current_vec.Distance(target_vec)
                # find where the last point was. If it doesn't exist, use the start point
                start_corner = projected_points[target_corner] \
                    if target_corner in projected_points else start_point
                flattened_corner = start_corner + length * (cos(angle) +
                                                            1j * sin(angle))
                projected_points[corner] = flattened_corner
                start_point = flattened_corner
                flattened.append(flattened_corner)
            poly_paths.append(
                Path(*[
                    Line(start=flattened[j], end=flattened[j - 1])
                    for j in range(3)
                ]))

        dwg = Drawing("mesh{}.svg".format(i), profile='tiny')
        for poly_path in poly_paths:
            dwg.add(
                dwg.path(
                    **{
                        'd': poly_path.d(),
                        'fill': "none",
                        'stroke-width': 4,
                        'stroke': rgb(0, 0, 0)
                    }))
        bbox = calc_overall_bbox(poly_paths)
        width, height = abs(bbox[1] - bbox[0]), abs(bbox[3] - bbox[2])
        dwg.viewbox(min(bbox[0], bbox[1]), min(bbox[2], bbox[3]), width,
                    height)
        dwg.save()
示例#28
0
def add_graphelement_to_svg_drawing(element: GraphElement,
                                    drawing: svgwrite.Drawing,
                                    filters: Dict[str, Filter]) -> None:
    args = {}
    for attr, value in element.attr.items():
        if attr.startswith('.svg_tag'):
            continue
        if attr.startswith('.svg_'):
            name = attr[5:]
            if name == 'filter':
                args[name] = filters[value].get_funciri()
            else:
                args[name] = value
    if '.svg_tag' in element.attr:
        tag = element.attr['.svg_tag']
        if tag == 'rect':
            x = float(element.attr['x'])
            y = -float(element.attr['y'])
            width = float(element.attr.get('.svg_width', 0.1))
            height = float(element.attr.get('.svg_height', 0.1))
            x = x - width / 2
            y = y - height / 2
            drawing.add(drawing.rect((x*mult, y*mult), (width*mult, height*mult),
                                     **args))
        elif tag == 'path':
            drawing.add(drawing.path(**args))
        elif tag == 'circle':
            x = float(element.attr['x'])
            y = -float(element.attr['y'])
            args.setdefault('r', '1cm')
            args.setdefault('stroke_width', '0.1mm')
            args.setdefault('stroke', 'black')
            args.setdefault('fill', 'none')
            drawing.add(drawing.circle(center=(x * mult, y * mult), **args))
        elif tag == 'image':
            x = float(element.attr['x'])
            y = -float(element.attr['y'])
            width = float(element.attr.pop('.svg_width', 5))
            height = float(element.attr.pop('.svg_height', 5))
            x = x - width / 2
            y = y - height / 2
            center = ((x + width / 2), (y + height / 2))
            args.setdefault('insert', (x * mult, y * mult))
            args.setdefault('size', (width * mult, height * mult))
            if '.svgx_rotate' in element.attr:
                rotation = float(element.attr['.svgx_rotate'])
                args.setdefault('transform',
                                f'translate({center[0]*mult}, {center[1]*mult}) '
                                f'rotate({-rotation}) '
                                f'translate({-center[0]*mult}, {-center[1]*mult})'
                                )
            drawing.add(getattr(drawing, element.attr['.svg_tag'])(**args))
        elif tag != 'None' and tag is not None:
            drawing.add(getattr(drawing, element.attr['.svg_tag'])(**args))
    elif isinstance(element, Vertex):
        if '.helper_node' in element.attr and element.attr['.helper_node']:
            return
        x = float(element.attr['x'])
        y = -float(element.attr['y'])
        args.setdefault('r', '0.4cm')
        args.setdefault('stroke_width', '1mm')
        args.setdefault('stroke', 'black')
        args.setdefault('fill', 'none')
        drawing.add(drawing.circle(center=(x*mult, y*mult), **args))
    elif isinstance(element, Edge):
        v1 = element.vertex1
        v2 = element.vertex2
        x1 = float(v1.attr['x'])
        y1 = -float(v1.attr['y'])
        x2 = float(v2.attr['x'])
        y2 = -float(v2.attr['y'])
        args.setdefault('stroke_width', '1mm')
        args.setdefault('stroke', 'black')
        drawing.add(drawing.line(start=(x1*mult, y1*mult), end=(x2*mult, y2*mult),
                                 **args))
    else:
        raise ValueError
示例#29
0
def disvg(paths=None,
          colors=None,
          filename=os_path.join(getcwd(), 'disvg_output.svg'),
          stroke_widths=None,
          nodes=None,
          node_colors=None,
          node_radii=None,
          openinbrowser=True,
          timestamp=False,
          margin_size=0.1,
          mindim=600,
          dimensions=None,
          viewbox=None,
          text=None,
          text_path=None,
          font_size=None,
          attributes=None,
          svg_attributes=None):
    """Takes in a list of paths and creates an SVG file containing said paths.
    REQUIRED INPUTS:
        :param paths - a list of paths

    OPTIONAL INPUT:
        :param colors - specifies the path stroke color.  By default all paths
        will be black (#000000).  This paramater can be input in a few ways
        1) a list of strings that will be input into the path elements stroke
            attribute (so anything that is understood by the svg viewer).
        2) a string of single character colors -- e.g. setting colors='rrr' is
            equivalent to setting colors=['red', 'red', 'red'] (see the
            'color_dict' dictionary above for a list of possibilities).
        3) a list of rgb 3-tuples -- e.g. colors = [(255, 0, 0), ...].

        :param filename - the desired location/filename of the SVG file
        created (by default the SVG will be stored in the current working
        directory and named 'disvg_output.svg').

        :param stroke_widths - a list of stroke_widths to use for paths
        (default is 0.5% of the SVG's width or length)

        :param nodes - a list of points to draw as filled-in circles

        :param node_colors - a list of colors to use for the nodes (by default
        nodes will be red)

        :param node_radii - a list of radii to use for the nodes (by default
        nodes will be radius will be 1 percent of the svg's width/length)

        :param text - string or list of strings to be displayed

        :param text_path - if text is a list, then this should be a list of
        path (or path segments of the same length.  Note: the path must be
        long enough to display the text or the text will be cropped by the svg
        viewer.

        :param font_size - a single float of list of floats.

        :param openinbrowser -  Set to True to automatically open the created
        SVG in the user's default web browser.

        :param timestamp - if True, then the a timestamp will be appended to
        the output SVG's filename.  This will fix issues with rapidly opening
        multiple SVGs in your browser.

        :param margin_size - The min margin (empty area framing the collection
        of paths) size used for creating the canvas and background of the SVG.

        :param mindim - The minimum dimension (height or width) of the output
        SVG (default is 600).

        :param dimensions - The display dimensions of the output SVG.  Using
        this will override the mindim parameter.

        :param viewbox - This specifies what rectangular patch of R^2 will be
        viewable through the outputSVG.  It should be input in the form
        (min_x, min_y, width, height).  This is different from the display
        dimension of the svg, which can be set through mindim or dimensions.

        :param attributes - a list of dictionaries of attributes for the input
        paths.  Note: This will override any other conflicting settings.

        :param svg_attributes - a dictionary of attributes for output svg.
        Note 1: This will override any other conflicting settings.
        Note 2: Setting `svg_attributes={'debug': False}` may result in a 
        significant increase in speed.

    NOTES:
        -The unit of length here is assumed to be pixels in all variables.

        -If this function is used multiple times in quick succession to
        display multiple SVGs (all using the default filename), the
        svgviewer/browser will likely fail to load some of the SVGs in time.
        To fix this, use the timestamp attribute, or give the files unique
        names, or use a pause command (e.g. time.sleep(1)) between uses.
    """

    _default_relative_node_radius = 5e-3
    _default_relative_stroke_width = 1e-3
    _default_path_color = '#000000'  # black
    _default_node_color = '#ff0000'  # red
    _default_font_size = 12

    # append directory to filename (if not included)
    if os_path.dirname(filename) == '':
        filename = os_path.join(getcwd(), filename)

    # append time stamp to filename
    if timestamp:
        fbname, fext = os_path.splitext(filename)
        dirname = os_path.dirname(filename)
        tstamp = str(time()).replace('.', '')
        stfilename = os_path.split(fbname)[1] + '_' + tstamp + fext
        filename = os_path.join(dirname, stfilename)

    # check paths and colors are set
    if isinstance(paths, Path) or is_path_segment(paths):
        paths = [paths]
    if paths:
        if not colors:
            colors = [_default_path_color] * len(paths)
        else:
            assert len(colors) == len(paths)
            if isinstance(colors, str):
                colors = str2colorlist(colors,
                                       default_color=_default_path_color)
            elif isinstance(colors, list):
                for idx, c in enumerate(colors):
                    if is3tuple(c):
                        colors[idx] = "rgb" + str(c)

    # check nodes and nodes_colors are set (node_radii are set later)
    if nodes:
        if not node_colors:
            node_colors = [_default_node_color] * len(nodes)
        else:
            assert len(node_colors) == len(nodes)
            if isinstance(node_colors, str):
                node_colors = str2colorlist(node_colors,
                                            default_color=_default_node_color)
            elif isinstance(node_colors, list):
                for idx, c in enumerate(node_colors):
                    if is3tuple(c):
                        node_colors[idx] = "rgb" + str(c)

    # set up the viewBox and display dimensions of the output SVG
    # along the way, set stroke_widths and node_radii if not provided
    assert paths or nodes
    stuff2bound = []
    if viewbox:
        szx, szy = viewbox[2:4]
    else:
        if paths:
            stuff2bound += paths
        if nodes:
            stuff2bound += nodes
        if text_path:
            stuff2bound += text_path
        xmin, xmax, ymin, ymax = big_bounding_box(stuff2bound)
        dx = xmax - xmin
        dy = ymax - ymin

        if dx == 0:
            dx = 1
        if dy == 0:
            dy = 1

        # determine stroke_widths to use (if not provided) and max_stroke_width
        if paths:
            if not stroke_widths:
                sw = max(dx, dy) * _default_relative_stroke_width
                stroke_widths = [sw] * len(paths)
                max_stroke_width = sw
            else:
                assert len(paths) == len(stroke_widths)
                max_stroke_width = max(stroke_widths)
        else:
            max_stroke_width = 0

        # determine node_radii to use (if not provided) and max_node_diameter
        if nodes:
            if not node_radii:
                r = max(dx, dy) * _default_relative_node_radius
                node_radii = [r] * len(nodes)
                max_node_diameter = 2 * r
            else:
                assert len(nodes) == len(node_radii)
                max_node_diameter = 2 * max(node_radii)
        else:
            max_node_diameter = 0

        extra_space_for_style = max(max_stroke_width, max_node_diameter)
        xmin -= margin_size * dx + extra_space_for_style / 2
        ymin -= margin_size * dy + extra_space_for_style / 2
        dx += 2 * margin_size * dx + extra_space_for_style
        dy += 2 * margin_size * dy + extra_space_for_style
        viewbox = "%s %s %s %s" % (xmin, ymin, dx, dy)
        if dimensions:
            szx, szy = dimensions
        else:
            if dx > dy:
                szx = str(mindim) + 'px'
                szy = str(int(ceil(mindim * dy / dx))) + 'px'
            else:
                szx = str(int(ceil(mindim * dx / dy))) + 'px'
                szy = str(mindim) + 'px'

    # Create an SVG file
    if svg_attributes:
        szx = svg_attributes.get("width", szx)
        szy = svg_attributes.get("height", szy)
        dwg = Drawing(filename=filename, size=(szx, szy), **svg_attributes)
    else:
        dwg = Drawing(filename=filename, size=(szx, szy), viewBox=viewbox)

    # add paths
    if paths:
        for i, p in enumerate(paths):
            if isinstance(p, Path):
                ps = p.d()
            elif is_path_segment(p):
                ps = Path(p).d()
            else:  # assume this path, p, was input as a Path d-string
                ps = p

            if attributes:
                good_attribs = {'d': ps}
                for key in attributes[i]:
                    val = attributes[i][key]
                    if key != 'd':
                        try:
                            dwg.path(ps, **{key: val})
                            good_attribs.update({key: val})
                        except Exception as e:
                            warn(str(e))

                dwg.add(dwg.path(**good_attribs))
            else:
                dwg.add(
                    dwg.path(ps,
                             stroke=colors[i],
                             stroke_width=str(stroke_widths[i]),
                             fill='none'))

    # add nodes (filled in circles)
    if nodes:
        for i_pt, pt in enumerate([(z.real, z.imag) for z in nodes]):
            dwg.add(dwg.circle(pt, node_radii[i_pt], fill=node_colors[i_pt]))

    # add texts
    if text:
        assert isinstance(text, str) or (isinstance(text, list) and isinstance(
            text_path, list) and len(text_path) == len(text))
        if isinstance(text, str):
            text = [text]
            if not font_size:
                font_size = [_default_font_size]
            if not text_path:
                pos = complex(xmin + margin_size * dx, ymin + margin_size * dy)
                text_path = [Line(pos, pos + 1).d()]
        else:
            if font_size:
                if isinstance(font_size, list):
                    assert len(font_size) == len(text)
                else:
                    font_size = [font_size] * len(text)
            else:
                font_size = [_default_font_size] * len(text)
        for idx, s in enumerate(text):
            p = text_path[idx]
            if isinstance(p, Path):
                ps = p.d()
            elif is_path_segment(p):
                ps = Path(p).d()
            else:  # assume this path, p, was input as a Path d-string
                ps = p

            # paragraph = dwg.add(dwg.g(font_size=font_size[idx]))
            # paragraph.add(dwg.textPath(ps, s))
            pathid = 'tp' + str(idx)
            dwg.defs.add(dwg.path(d=ps, id=pathid))
            txter = dwg.add(dwg.text('', font_size=font_size[idx]))
            txter.add(txt.TextPath('#' + pathid, s))

    # save svg
    if not os_path.exists(os_path.dirname(filename)):
        makedirs(os_path.dirname(filename))
    dwg.save()

    # re-open the svg, make the xml pretty, and save it again
    xmlstring = md_xml_parse(filename).toprettyxml()
    with open(filename, 'w') as f:
        f.write(xmlstring)

    # try to open in web browser
    if openinbrowser:
        try:
            open_in_browser(filename)
        except:
            print("Failed to open output SVG in browser.  SVG saved to:")
            print(filename)
示例#30
0
def disvg(paths=None, colors=None,
          filename=os_path.join(getcwd(), 'disvg_output.svg'),
          stroke_widths=None, nodes=None, node_colors=None, node_radii=None,
          openinbrowser=True, timestamp=False,
          margin_size=0.1, mindim=600, dimensions=None,
          viewbox=None, text=None, text_path=None, font_size=None,
          attributes=None, svg_attributes=None, svgwrite_debug=False, paths2Drawing=False):
    """Takes in a list of paths and creates an SVG file containing said paths.
    REQUIRED INPUTS:
        :param paths - a list of paths

    OPTIONAL INPUT:
        :param colors - specifies the path stroke color.  By default all paths
        will be black (#000000).  This paramater can be input in a few ways
        1) a list of strings that will be input into the path elements stroke
            attribute (so anything that is understood by the svg viewer).
        2) a string of single character colors -- e.g. setting colors='rrr' is
            equivalent to setting colors=['red', 'red', 'red'] (see the
            'color_dict' dictionary above for a list of possibilities).
        3) a list of rgb 3-tuples -- e.g. colors = [(255, 0, 0), ...].

        :param filename - the desired location/filename of the SVG file
        created (by default the SVG will be stored in the current working
        directory and named 'disvg_output.svg').

        :param stroke_widths - a list of stroke_widths to use for paths
        (default is 0.5% of the SVG's width or length)

        :param nodes - a list of points to draw as filled-in circles

        :param node_colors - a list of colors to use for the nodes (by default
        nodes will be red)

        :param node_radii - a list of radii to use for the nodes (by default
        nodes will be radius will be 1 percent of the svg's width/length)

        :param text - string or list of strings to be displayed

        :param text_path - if text is a list, then this should be a list of
        path (or path segments of the same length.  Note: the path must be
        long enough to display the text or the text will be cropped by the svg
        viewer.

        :param font_size - a single float of list of floats.

        :param openinbrowser -  Set to True to automatically open the created
        SVG in the user's default web browser.

        :param timestamp - if True, then the a timestamp will be appended to
        the output SVG's filename.  This will fix issues with rapidly opening
        multiple SVGs in your browser.

        :param margin_size - The min margin (empty area framing the collection
        of paths) size used for creating the canvas and background of the SVG.

        :param mindim - The minimum dimension (height or width) of the output
        SVG (default is 600).

        :param dimensions - The (x,y) display dimensions of the output SVG.
        I.e. this specifies the `width` and `height` SVG attributes. Note that 
        these also can be used to specify units other than pixels. Using this 
        will override the `mindim` parameter.

        :param viewbox - This specifies the coordinated system used in the svg.
        The SVG `viewBox` attribute works together with the the `height` and 
        `width` attrinutes.  Using these three attributes allows for shifting 
        and scaling of the SVG canvas without changing the any values other 
        than those in `viewBox`, `height`, and `width`.  `viewbox` should be 
        input as a 4-tuple, (min_x, min_y, width, height), or a string 
        "min_x min_y width height".  Using this will override the `mindim` 
        parameter.

        :param attributes - a list of dictionaries of attributes for the input
        paths.  Note: This will override any other conflicting settings.

        :param svg_attributes - a dictionary of attributes for output svg.
        
        :param svgwrite_debug - This parameter turns on/off `svgwrite`'s 
        debugging mode.  By default svgwrite_debug=False.  This increases 
        speed and also prevents `svgwrite` from raising of an error when not 
        all `svg_attributes` key-value pairs are understood.
        
        :param paths2Drawing - If true, an `svgwrite.Drawing` object is 
        returned and no file is written.  This `Drawing` can later be saved 
        using the `svgwrite.Drawing.save()` method.

    NOTES:
        * The `svg_attributes` parameter will override any other conflicting 
        settings.

        * Any `extra` parameters that `svgwrite.Drawing()` accepts can be 
        controlled by passing them in through `svg_attributes`.

        * The unit of length here is assumed to be pixels in all variables.

        * If this function is used multiple times in quick succession to
        display multiple SVGs (all using the default filename), the
        svgviewer/browser will likely fail to load some of the SVGs in time.
        To fix this, use the timestamp attribute, or give the files unique
        names, or use a pause command (e.g. time.sleep(1)) between uses.
    """


    _default_relative_node_radius = 5e-3
    _default_relative_stroke_width = 1e-3
    _default_path_color = '#000000'  # black
    _default_node_color = '#ff0000'  # red
    _default_font_size = 12


    # append directory to filename (if not included)
    if os_path.dirname(filename) == '':
        filename = os_path.join(getcwd(), filename)

    # append time stamp to filename
    if timestamp:
        fbname, fext = os_path.splitext(filename)
        dirname = os_path.dirname(filename)
        tstamp = str(time()).replace('.', '')
        stfilename = os_path.split(fbname)[1] + '_' + tstamp + fext
        filename = os_path.join(dirname, stfilename)

    # check paths and colors are set
    if isinstance(paths, Path) or is_path_segment(paths):
        paths = [paths]
    if paths:
        if not colors:
            colors = [_default_path_color] * len(paths)
        else:
            assert len(colors) == len(paths)
            if isinstance(colors, str):
                colors = str2colorlist(colors,
                                       default_color=_default_path_color)
            elif isinstance(colors, list):
                for idx, c in enumerate(colors):
                    if is3tuple(c):
                        colors[idx] = "rgb" + str(c)

    # check nodes and nodes_colors are set (node_radii are set later)
    if nodes:
        if not node_colors:
            node_colors = [_default_node_color] * len(nodes)
        else:
            assert len(node_colors) == len(nodes)
            if isinstance(node_colors, str):
                node_colors = str2colorlist(node_colors,
                                            default_color=_default_node_color)
            elif isinstance(node_colors, list):
                for idx, c in enumerate(node_colors):
                    if is3tuple(c):
                        node_colors[idx] = "rgb" + str(c)

    # set up the viewBox and display dimensions of the output SVG
    # along the way, set stroke_widths and node_radii if not provided
    assert paths or nodes
    stuff2bound = []
    if viewbox:
        if not isinstance(viewbox, str):
            viewbox = '%s %s %s %s' % viewbox
        if dimensions is None:
            dimensions = viewbox.split(' ')[2:4]
    elif dimensions:
        dimensions = tuple(map(str, dimensions))
        def strip_units(s):
            return re.search(r'\d*\.?\d*', s.strip()).group()
        viewbox = '0 0 %s %s' % tuple(map(strip_units, dimensions))
    else:
        if paths:
            stuff2bound += paths
        if nodes:
            stuff2bound += nodes
        if text_path:
            stuff2bound += text_path
        xmin, xmax, ymin, ymax = big_bounding_box(stuff2bound)
        dx = xmax - xmin
        dy = ymax - ymin

        if dx == 0:
            dx = 1
        if dy == 0:
            dy = 1

        # determine stroke_widths to use (if not provided) and max_stroke_width
        if paths:
            if not stroke_widths:
                sw = max(dx, dy) * _default_relative_stroke_width
                stroke_widths = [sw]*len(paths)
                max_stroke_width = sw
            else:
                assert len(paths) == len(stroke_widths)
                max_stroke_width = max(stroke_widths)
        else:
            max_stroke_width = 0

        # determine node_radii to use (if not provided) and max_node_diameter
        if nodes:
            if not node_radii:
                r = max(dx, dy) * _default_relative_node_radius
                node_radii = [r]*len(nodes)
                max_node_diameter = 2*r
            else:
                assert len(nodes) == len(node_radii)
                max_node_diameter = 2*max(node_radii)
        else:
            max_node_diameter = 0

        extra_space_for_style = max(max_stroke_width, max_node_diameter)
        xmin -= margin_size*dx + extra_space_for_style/2
        ymin -= margin_size*dy + extra_space_for_style/2
        dx += 2*margin_size*dx + extra_space_for_style
        dy += 2*margin_size*dy + extra_space_for_style
        viewbox = "%s %s %s %s" % (xmin, ymin, dx, dy)

        if dx > dy:
            szx = str(mindim) + 'px'
            szy = str(int(ceil(mindim * dy / dx))) + 'px'
        else:
            szx = str(int(ceil(mindim * dx / dy))) + 'px'
            szy = str(mindim) + 'px'
        dimensions = szx, szy

    # Create an SVG file
    if svg_attributes is not None:
        dimensions = (svg_attributes.get("width", dimensions[0]),
                      svg_attributes.get("height", dimensions[1]))
        debug = svg_attributes.get("debug", svgwrite_debug)
        dwg = Drawing(filename=filename, size=dimensions, debug=debug,
                      **svg_attributes)
    else:
        dwg = Drawing(filename=filename, size=dimensions, debug=svgwrite_debug,
                      viewBox=viewbox)

    # add paths
    if paths:
        for i, p in enumerate(paths):
            if isinstance(p, Path):
                ps = p.d()
            elif is_path_segment(p):
                ps = Path(p).d()
            else:  # assume this path, p, was input as a Path d-string
                ps = p

            if attributes:
                good_attribs = {'d': ps}
                for key in attributes[i]:
                    val = attributes[i][key]
                    if key != 'd':
                        try:
                            dwg.path(ps, **{key: val})
                            good_attribs.update({key: val})
                        except Exception as e:
                            warn(str(e))

                dwg.add(dwg.path(**good_attribs))
            else:
                dwg.add(dwg.path(ps, stroke=colors[i],
                                 stroke_width=str(stroke_widths[i]),
                                 fill='none'))

    # add nodes (filled in circles)
    if nodes:
        for i_pt, pt in enumerate([(z.real, z.imag) for z in nodes]):
            dwg.add(dwg.circle(pt, node_radii[i_pt], fill=node_colors[i_pt]))

    # add texts
    if text:
        assert isinstance(text, str) or (isinstance(text, list) and
                                         isinstance(text_path, list) and
                                         len(text_path) == len(text))
        if isinstance(text, str):
            text = [text]
            if not font_size:
                font_size = [_default_font_size]
            if not text_path:
                pos = complex(xmin + margin_size*dx, ymin + margin_size*dy)
                text_path = [Line(pos, pos + 1).d()]
        else:
            if font_size:
                if isinstance(font_size, list):
                    assert len(font_size) == len(text)
                else:
                    font_size = [font_size] * len(text)
            else:
                font_size = [_default_font_size] * len(text)
        for idx, s in enumerate(text):
            p = text_path[idx]
            if isinstance(p, Path):
                ps = p.d()
            elif is_path_segment(p):
                ps = Path(p).d()
            else:  # assume this path, p, was input as a Path d-string
                ps = p

            # paragraph = dwg.add(dwg.g(font_size=font_size[idx]))
            # paragraph.add(dwg.textPath(ps, s))
            pathid = 'tp' + str(idx)
            dwg.defs.add(dwg.path(d=ps, id=pathid))
            txter = dwg.add(dwg.text('', font_size=font_size[idx]))
            txter.add(txt.TextPath('#'+pathid, s))

    if paths2Drawing:
        return dwg
      
    # save svg
    if not os_path.exists(os_path.dirname(filename)):
        makedirs(os_path.dirname(filename))
    dwg.save()

    # re-open the svg, make the xml pretty, and save it again
    xmlstring = md_xml_parse(filename).toprettyxml()
    with open(filename, 'w') as f:
        f.write(xmlstring)

    # try to open in web browser
    if openinbrowser:
        try:
            open_in_browser(filename)
        except:
            print("Failed to open output SVG in browser.  SVG saved to:")
            print(filename)
示例#31
0
    def render(self, dwg: Drawing) -> Group:
        g = dwg.g()

        head = self.head.render(dwg)

        socket_relative_width = 1.2

        socket_radius = (self.head_size * socket_relative_width, self.head_size*0.3)
        socket_relative_height = .3

        socket_left = (-self.head_size * socket_relative_width, self.head_size * .5)
        socket_left_bottom = (socket_left[0], socket_left[1] + self.head_size * socket_relative_height)
        socket_right: Tuple[float, float] = (self.head_size * socket_relative_width, self.head_size * .5)
        socket_right_bottom: Tuple[float, float] = (socket_right[0], socket_right[1] + self.head_size * socket_relative_height)

        size_factor = self.head_size / 50.0

        arm_length = 50 * size_factor
        arm_params = {
            "arm_length": arm_length,
            "arm_color": self.body_color,
            "hand_color": helper.colors.lighten_hex(self.body_color, 2),
            "thickness_shoulder": 30 * size_factor
        }
        arm_params.update(self.arm_params)

        for i in range(self.arm_count):
            left_arm = ArmWithHand(**arm_params) # type: ignore
            left_arm_g = left_arm.render(dwg)

            left_arm_x = socket_right_bottom[0] - left_arm.thickness_shoulder / 2 - (socket_right_bottom[0] - self.head_size * self.body_fatness) / (self.head_size * self.body_height) * i * left_arm.thickness_shoulder * 1.2

            left_arm_g.translate(left_arm_x, socket_right_bottom[1] + left_arm.thickness_shoulder / 2 + i * left_arm.thickness_shoulder * .8)
            left_arm_g.rotate(self.body_left_arm_angle / (math.pi) * 180 + (i * 20))

            g.add(left_arm_g)

            right_arm = ArmWithHand(reverse_shadow=True, **arm_params) # type: ignore
            right_arm_g = right_arm.render(dwg)

            right_arm_x = socket_left_bottom[0] + right_arm.thickness_shoulder / 2 + (-self.head_size * self.body_fatness - socket_left_bottom[0]) / (self.head_size * self.body_height) * i * right_arm.thickness_shoulder * 1.2

            right_arm_g.translate(right_arm_x, socket_left_bottom[1] + right_arm.thickness_shoulder / 2 + i * right_arm.thickness_shoulder * .8)
            right_arm_g.rotate(-self.body_right_arm_angle / (math.pi) * 180 - (i * 20))
            right_arm_g.scale(-1, 1)

            g.add(right_arm_g)

        leg_thickness_thigh = self.body_fatness * self.head_size
        leg_thickness_foot = leg_thickness_thigh * .7

        leg_length = self.head_size * 1

        boot_height = leg_length * .5
        foot_length = leg_length

        left_leg = LegWithFoot(leg_length=leg_length, # type: ignore
                               leg_color=self.body_color,
                               thickness_thigh=leg_thickness_thigh,
                               thickness_foot=leg_thickness_foot,
                               foot_color=helper.colors.lighten_hex(self.body_color, 2),
                               boot_height=boot_height,
                               foot_length=foot_length,
                               **self.leg_params)
        left_leg_g = left_leg.render(dwg)
        left_leg_g.translate(0, self.head_size * self.body_height)
        left_leg_g.rotate(-20)

        g.add(left_leg_g)

        right_leg = LegWithFoot(leg_length=leg_length, # type: ignore
                                leg_color=self.body_color,
                                thickness_thigh=leg_thickness_thigh,
                                thickness_foot=leg_thickness_foot,
                                foot_color=helper.colors.lighten_hex(self.body_color, 2),
                                boot_height=boot_height,
                                foot_length=foot_length,
                                **self.leg_params)
        right_leg_g = right_leg.render(dwg)
        right_leg_g.translate(0, self.head_size * self.body_height)
        right_leg_g.rotate(20)
        right_leg_g.scale(-1, 1)

        g.add(right_leg_g)

        body = dwg.path(fill=self.body_color)
        body.push("M %f %f" % (socket_right_bottom[0], socket_right_bottom[1]))
        body.push("L %f %f" % (self.head_size * self.body_fatness, self.head_size * self.body_height))
        body.push("L %f %f" % (self.head_size * (self.body_fatness - .2), self.head_size * (self.body_height + .2)))
        body.push("L %f %f" % (-self.head_size * (self.body_fatness - .2), self.head_size * (self.body_height + .2)))
        body.push("L %f %f" % (-self.head_size * self.body_fatness, self.head_size * self.body_height))
        body.push("L %f %f" % (socket_left_bottom[0], socket_left_bottom[1]))

        g.add(body)

        socket_background_color = helper.colors.darken_hex(self.socket_color)
        socket_background = dwg.ellipse(fill=socket_background_color, center=(0, self.head_size * .5), r=socket_radius)
        
        socket_foreground = dwg.path(fill=self.socket_color)
        socket_foreground.push("M %f %f" % socket_left)
        socket_foreground.push("A %f %f 0 0 0 %f %f" % (socket_radius[0], socket_radius[1], socket_right[0], socket_right[1]))
        socket_foreground.push("l 0 %f" % (self.head_size * .3))
        socket_foreground.push("A %f %f 0 0 1 %f %f" % (socket_radius[0], socket_radius[1], - self.head_size * socket_relative_width, self.head_size * .8))

        g.add(socket_background)
        g.add(head)
        g.add(socket_foreground)

        dome = dwg.path(fill="white", fill_opacity=.3)
        dome.push("M %f %f" % socket_left)
        dome.push("C %f %f %f %f %f %f" % (-self.head_size * (socket_relative_width + 1),
                                           -self.head_size * 3,
                                           self.head_size * (socket_relative_width + 1),
                                           -self.head_size * 3,
                                           socket_right[0],
                                           socket_right[1]))
        dome.push("A %f %f 0 0 1 %f %f" % (socket_radius[0], socket_radius[1], socket_left[0], socket_left[1]))

        refl_mask = dwg.defs.add(dwg.mask((self.head_size * -1.5, self.head_size * -2.5),
                                          (self.head_size * 3, self.head_size * 5),
                                          id="%s-dome-refl-mask" % self.id))
        refl_mask.add(dwg.rect((self.head_size * -1.5, self.head_size * -2.5),
                                          (self.head_size * 3, self.head_size * 5), fill="white"))
        refl_mask.add(dwg.circle((self.head_size * .3, -self.head_size * .25), r=self.head_size * 1.75, fill="black"))

        dome_reflection = dwg.path(fill="white", fill_opacity=.3, mask="url(#%s-dome-refl-mask)" % self.id)
        dome_reflection.push("M %f %f" % socket_left)
        dome_reflection.push("C %f %f %f %f %f %f" % (-self.head_size * (socket_relative_width + 1),
                                           -self.head_size * 3,
                                           self.head_size * (socket_relative_width + 1),
                                           -self.head_size * 3,
                                           socket_right[0],
                                           socket_right[1]))
        dome_reflection.push("A %f %f 0 0 1 %f %f" % (socket_radius[0], socket_radius[1], socket_left[0], socket_left[1]))
        dome_reflection.scale(.9)

        g.add(dome)
        g.add(dome_reflection)

        return g
示例#32
0
def flatten_shape(i, all_paths, merge_paths):
    dwg = Drawing("merge_output%s.svg" % i, profile='tiny')

    def draw_line(start, end, offset=0.0):
        start += offset
        end += offset
        dwg.add(
            dwg.line(start=(start.real, start.imag),
                     end=(end.real, end.imag),
                     stroke_width=4,
                     stroke=rgb(255, 0, 0)))

    dwg.add(
        dwg.path(
            **{
                'd': all_paths[i].d(),
                'fill': "none",
                'stroke-width': 4,
                'stroke': rgb(0, 0, 0)
            }))
    dwg.add(
        dwg.path(
            **{
                'd': merge_paths[i].d(),
                'fill': "none",
                'stroke-width': 4,
                'stroke': rgb(255, 0, 0)
            }))
    bbox = calc_overall_bbox(all_paths[i])
    width, height = abs(bbox[1] - bbox[0]), abs(bbox[3] - bbox[2])
    margin = 40
    lower = min(bbox[2], bbox[3]) + height + margin
    left = min(bbox[0], bbox[1]) + margin

    def draw_marker(loc, col=rgb(255, 0, 0), offset=(left, lower)):
        dwg.add(
            dwg.circle(center=(loc.real + offset[0], loc.imag + offset[1]),
                       r=4,
                       fill=col))

    max_axis = max(width, height)
    num_lines = 10
    points = [merge_paths[i].point(j / num_lines)
              for j in range(num_lines)] + [merge_paths[i].point(1.0)]
    angles = [
        asin((points[j + 1].imag - points[j].imag) /
             abs(points[j + 1] - points[j])) for j in range(num_lines)
    ]

    ends = [max_axis * (sin(angle) + cos(angle) * 1j) for angle in angles]
    intersection_clips = []
    for j, end in enumerate(ends):
        end_point = end + points[j]
        intersections = other_paths[i].intersect(
            Line(start=points[j], end=end_point))

        for intersection in intersections[0]:
            intersection_point = intersection[1].point(intersection[2])
            target = merge_paths[i].length() * (
                1 - j / num_lines) + abs(intersection_point - points[j]) * 1j
            intersection_clips.append(
                PathClip(index=other_paths[i].index(intersection[1]),
                         t=intersection[2],
                         target=target))
            if j % 10 == 0:
                draw_line(points[j], intersection_point)
                draw_marker(intersection_point, rgb(0, 255, 0), (0, 0))
            break

    # make the flexed points by chopping the chunks of the other paths out, then
    # translating and rotating them such that their end points line up with the diff lines
    def transform_side(sides, targets, angle_offset=0):
        def angle(point1, point2):
            diff = point1 - point2
            if diff.real == 0:
                return 90.0
            return atan(diff.imag / diff.real) * 180.0 / pi

        # change this so that it has two targets
        transformed_side = Path(*sides)
        source_angle = angle(transformed_side.end, transformed_side.start) - \
                       angle(targets[0], targets[1])
        transformed_side = transformed_side.rotated(-source_angle +
                                                    angle_offset)
        source = transformed_side.end if angle_offset == 0 else transformed_side.start
        diff = targets[1] - source
        transformed_side = transformed_side.translated(diff)
        draw_marker(targets[0], rgb(0, 200, 200))
        draw_marker(targets[1], rgb(0, 255, 255))
        transformed_diff = abs(transformed_side.start - transformed_side.end)
        targets_diff = abs(targets[0] - targets[1])
        if transformed_diff < targets_diff:
            transformed_side.insert(
                0, Line(start=targets[0], end=transformed_side.start))
        elif transformed_diff > targets_diff:
            # pop elements off until the transformed diff is smaller
            while transformed_diff > targets_diff:
                transformed_side.pop(0)
                transformed_diff = abs(transformed_side.start -
                                       transformed_side.end)
            print("path", transformed_side)
            print("path is longer", transformed_diff - targets_diff)
        return transformed_side

    start_index = 0
    curr_t = 0
    flexed_path = []
    t_resolution = 0.01
    if intersection_clips[0].index > intersection_clips[-1].index or \
        (intersection_clips[0].index == intersection_clips[-1].index and
         intersection_clips[0].t > intersection_clips[-1].t):
        intersection_clips.reverse()
    # add the end of the shape to the intersection clips
    intersection_clips.append(
        PathClip(index=len(other_paths[i]) - 1,
                 t=1.0,
                 target=merge_paths[i].length()))
    last_target = 0
    for clip in intersection_clips:
        sides = []
        print("boundaries", start_index, clip.index, curr_t, clip.t)
        upper_t = clip.t if start_index == clip.index else 1.0
        while start_index <= clip.index and curr_t < upper_t:
            curr_seg = other_paths[i][start_index]
            while curr_t < upper_t:
                max_t = curr_t + t_resolution if curr_t + t_resolution < clip.t else clip.t
                sides.append(
                    Line(start=curr_seg.point(curr_t),
                         end=curr_seg.point(max_t)))
                curr_t += t_resolution
            curr_t = upper_t
            if start_index != clip.index:
                curr_t = 0.0
            if upper_t == 1.0:
                start_index += 1
                upper_t = clip.t if start_index == clip.index else 1.0
        if len(sides) != 0:
            flexed_path.append(
                transform_side(sides, [last_target, clip.target]))
        last_target = clip.target

    straight_path = [Line(start=0, end=merge_paths[i].length())]
    for p in flexed_path:
        p = p.translated(left + lower * 1j)
        dwg.add(
            dwg.path(d=p.d(),
                     fill="none",
                     stroke_width=4,
                     stroke=rgb(255, 0, 0)))

    transformed_path = flexed_path + straight_path
    transformed_path = Path(*transformed_path).translated(left + lower * 1j)
    dwg.add(
        dwg.path(d=transformed_path.d(),
                 fill="none",
                 stroke_width=4,
                 stroke=rgb(0, 0, 0)))
    bbox = calc_overall_bbox(list(all_paths[i]) + list(transformed_path))

    width, height = abs(bbox[1] - bbox[0]), abs(bbox[3] - bbox[2])
    dwg.viewbox(min(bbox[0], bbox[1]), min(bbox[2], bbox[3]), width, height)
    dwg.save()
    return flexed_path