def plot(*auts, filename='plot.svg', diagonal=True, endpoints=False, display_scale=1): assert len({aut.signature for aut in auts}) == 1 from svgwrite.path import Path from svgwrite.shapes import Line, Polyline from svgwrite.container import Group dwg, canvas = new_drawing(filename, display_scale) include_markers(dwg, endpoints) draw_grid(canvas, auts[0].signature) x_axis = Polyline([(0, 0), (SCALE, 0)], class_="axis") y_axis = Polyline([(0, 0), (0, SCALE)], class_="axis") canvas.add(x_axis) canvas.add(y_axis) if diagonal: diag = Line((0,0), (SCALE, SCALE), class_="grid depth0") canvas.add(diag) for i, aut in enumerate(auts): group = Group(class_="graph", id='graph_' + str(i)) canvas.add(group) last = (None, None) for (x0, y0, x1, y1) in graph_segments(aut): if last != (x0, y0): graph = Path(class_='graph_segment') group.add(graph) graph.push('M', SCALE * x0, SCALE * y0) graph.push('L', SCALE * x1, SCALE * y1) last = (x1, y1) dwg.save()
def load_death_symbol(drawing): # DEATH SYMBOL death_symbol = Group() death_symbol.add( Path(id="death", d="M 10 0 L 10 40 M 0 12 L 20 12", stroke_width="5")) drawing.defs.add(death_symbol) return death_symbol
def get_parallax_lines(): groups = [Group() for i in range(3)] duplicated_groups = [Group() for i in range(3)] longest = [0 for i in range(3)] current_y = anim['verticalInterval'] while current_y < anim['height']: line_quantity = randint(1, 9) lengths = [randint(5, 30) for l in range(line_quantity)] position = sample(range(anim['width'] * 2), line_quantity) for i, (length, pos) in enumerate(zip(lengths, position)): if i % 3 is 0 or i is 0: n = 2 elif i % 2 is 0: n = 1 else: n = 0 if pos + length > longest[n]: longest[n] = pos + length line = Line((pos, current_y), (pos + length, current_y)) if pos < anim['height']: duplicated_groups[n].add(line) groups[n].add(line) current_y += randint(1,5) * anim['verticalInterval'] dur = 2 containers = [Group(**anim['style']) for i in range(3)] for c, g, dg, l in zip(containers, groups, duplicated_groups, longest): c.add(g) dg['transform'] = 'translate({}, 0)'.format(l) c.add(dg) c.add(animate(l, dur)) dur += 0.5 return containers
def draw_numbers(*args): numbers = Group() for i, num in enumerate(args): assert isinstance(i, int) num_drawing = number(num) num_drawing.translate((35 * i, 0)) numbers.add(num_drawing) return numbers
def save(cls, image, filename, mosaic=False): # Use debug=False everywhere to turn off SVG validation, # which turns out to be obscenely expensive in this # library. DEBUG = False svg = svgwrite.Drawing(filename=filename, style='background-color: black;', size=(("%dpx" % (2 * image.r_outer), "%dpx" % (2 * image.r_outer))), debug=DEBUG) group = Group(debug=DEBUG) group.translate(image.r_outer, image.r_outer) for y, row in enumerate(image.pixels): ring = image.rings[y] theta = 2 * math.pi / len(row) r1 = ring.center + image.r_ring / 2 r2 = ring.center - image.r_ring / 2 for x, c in enumerate(row): if mosaic: path = Path(stroke='black', stroke_width=1, fill=cls.color_hex(c), debug=DEBUG) path.push((('M', 0, r2), ('L', 0, r1), ('A', r1, r1, 0, '0,0', (r1 * sin(theta), r1 * cos(theta))), ('L', r2 * sin(theta), r2 * cos(theta)), ('A', r2, r2, 0, '0,1', (0, r2)))) else: path = Path(stroke=cls.color_hex(c), stroke_width=image.r_pixel, fill='none', debug=DEBUG) path.push((('M', 0, ring.center), ('A', ring.center, ring.center, 0, '0,0', (ring.center * sin(theta), ring.center * cos(theta))))) path.rotate(180 - degrees(theta * (x + 1)), center=(0, 0)) group.add(path) svg.add(group) svg.save()
def get_runner_end(self, line, is_out): x, y = self.get_line_end(line) if is_out: g = Group() g['class'] = 'out' g.add(Line((x - X_SIZE, y - X_SIZE), (x + X_SIZE, y + X_SIZE))) g.add(Line((x - X_SIZE, y + X_SIZE), (x + X_SIZE, y - X_SIZE))) return g else: return Circle((x, y), CIRCLE_R)
def test_simple_defs(self): dwg = Drawing() g = dwg.defs.add(Group(id='test')) inner_g = g.add(Group(id='innerTest')) result = dwg.tostring() self.assertEqual(result, '<svg baseProfile="full" height="100%" version="1.1" ' 'width="100%" xmlns="http://www.w3.org/2000/svg" ' 'xmlns:ev="http://www.w3.org/2001/xml-events" ' 'xmlns:xlink="http://www.w3.org/1999/xlink">' '<defs><g id="test"><g id="innerTest" /></g></defs></svg>')
def add_y_label(self): y_coord = self.margin_top + (self.plottable_y / 2) text_group = Group( transform=f"rotate(270, {self.font_size}, {y_coord})") text_group.add( Text(self.y_label, insert=(0, y_coord), fill=self.graph_colour, font_size="15", stroke_width=0)) self.plot.add(text_group)
def number(num: int) -> Line: if 0 > num > 9999: raise RuntimeError( 'Cistercian numbers can only represent numbers from 0 to 9999') num_drawing = Group() num_drawing.add(zero()) if num != 0: for i, digit in enumerate(reversed(str(num))): digit_drawing = draw_functions[int(digit)]() offset = offsets[i] try: isnt_flat = bool(digit_drawing['y1'] - digit_drawing['y2']) except KeyError: isnt_flat = True if offset[1] and isnt_flat: offset = (offset[0], offset[1] - 10) digit_drawing.translate(offset) if i % 2 != 0: digit_drawing.scale((-1, 1)) digit_drawing.translate((-10, 0)) if i > 1: if isnt_flat: digit_drawing.scale((1, -1)) digit_drawing.translate((0, -10)) elif digit == '2': digit_drawing.translate((0, -20)) num_drawing.add(digit_drawing) num_drawing.translate((20, 20)) return num_drawing
def _draw_attachment(self): """ Return an SVG group containing a line element and "Attachment" label. """ group = Group(class_='connector') # Draw attachment (line) start = (OFFSET + self.center, OFFSET + self.cursor) height = PADDING * 2 + LINE_HEIGHT + PADDING * 2 end = (start[0], start[1] + height) line = Line(start=start, end=end, class_='attachment') group.add(line) self.cursor += PADDING * 4 return group
def draw_atbat(self, atbat, is_home_team_batting): atbat_group = Group() atbat_group.set_desc(atbat.get_description()) atbat_group.add(self.get_batter_name_text(atbat)) atbat_group.add(self.get_scoring_text(atbat)) self.runner_drawer.execute(self.y, atbat, atbat_group, is_home_team_batting) self.dwg.add(atbat_group)
def test_object_link_change_id(self): g = Group(id=999) self.assertEqual(g['id'], 999) use = Use(g) # change 'id' after assigning to <Use> object g['id'] = 'newid' self.assertEqual(use.tostring(), '<use xlink:href="#newid" />')
def test_add_subelement_with_autoid(self): marker = Marker(debug=True, profile='full') marker.add(Group()) self.assertTrue( re.match(r'^<marker id="id\d+"><g /></marker>$', marker.tostring()), "getting an autoid for class Marker failed.")
def get_lines(): group = Group(**img['style']) current_y = 6 current_x = randint(-10 , 150) for y in range(0, img['verticalLines']): while current_x < img['width']: rand_x = randint(2, 6) + current_x line = Line((current_x, current_y), (rand_x, current_y)) group.add(line) current_x = rand_x + randint(50, 150) current_x = randint(-10, 150) current_y = round(current_y + img['verticalInterval'], 1) return group
def add_x_column_labels(self, column_positions, column_labels, rotate=None): for gene in column_labels: text_group = Group( transform= f"rotate({rotate if rotate else 0},{column_positions[gene]}," f"{self.margin_top + self.plottable_y + 17})") text_group.add( Text(gene, insert=(column_positions[gene], self.margin_top + self.plottable_y + 17), fill=self.graph_colour, font_size=self.font_size, stroke_width=0)) self.plot.add(text_group)
def get_team_box(self, id, ht): box = Group() box['id'] = id box['class'] = 'team-box' box.add(Rect((ORIGIN_X, ORIGIN_Y), (ATBAT_W, ht))) box.add( Line((ORIGIN_X + NAME_W, ORIGIN_Y), (ORIGIN_X + NAME_W, ORIGIN_Y + ht))) box.add( Line((ORIGIN_X + NAME_W + SCORE_W, ORIGIN_Y), (ORIGIN_X + NAME_W + SCORE_W, ORIGIN_Y + ht))) return box
def text_to_paths(self, text, path_type, style, translate_x=0): glyphs = self.glyphs[path_type] glyphs_paths = Group(**style) text_len = len(text) - 1 for i, glyph in enumerate(text): if glyph is not ' ': glyphs_paths.add( self.get_path(glyphs[glyph], round(translate_x, 2))) if glyph is '/' or (i + 1 <= text_len and text[i + 1] is '/'): translate_x += self.glyph_width + self.slash_space elif glyph is 'l': translate_x += 1.6 + self.inner_space else: translate_x += self.glyph_width + self.inner_space else: translate_x += self.space return glyphs_paths
def _draw_wirelesslink(self, url, labels): """ Draw a line with labels representing a WirelessLink. :param url: Hyperlink URL :param labels: Iterable of text labels """ group = Group(class_='connector') # Draw the wireless link start = (OFFSET + self.center, self.cursor) height = PADDING * 2 + LINE_HEIGHT * len(labels) + PADDING * 2 end = (start[0], start[1] + height) line = Line(start=start, end=end, class_='wireless-link') group.add(line) self.cursor += PADDING * 2 # Add link link = Hyperlink(href=f'{self.base_url}{url}', target='_blank') # Add text label(s) for i, label in enumerate(labels): self.cursor += LINE_HEIGHT text_coords = (self.center + PADDING * 2, self.cursor - LINE_HEIGHT / 2) text = Text(label, insert=text_coords, class_='bold' if not i else []) link.add(text) group.add(link) self.cursor += PADDING * 2 return group
def group_runner(self, line, line_end, to_score): runner_group = Group() runner_group.add(line) runner_group.add(line_end) if to_score: runner_group['class'] = 'runner to-score' else: runner_group['class'] = 'runner' return runner_group
def get_svg(self, **kwargs): """Returns the svg code of this shape with annotation. Args: kwargs: The options for the group. Returns: svgwrite.container.Group: The group of the shape and annotations. """ group = Group(**kwargs) group.add(self.shape.get_svg(fill=self.color)) if len(self.annot_txt) > 0 and self.show_annot: group.add(Annotation(self.annot_txt, self.annot_pos, self.shape)) if self.show_value_txt: group.add(Value(self.value, self.shape)) return group
def encode(self, bits_data: List[int]): tattoo_bit_height = int(len(bits_data) / 8) x, y = 0, 0 xmin, xmax = 0, 0 ymin, ymax = 0, 0 tattoo = Group(id="tattoo") for j in range(tattoo_bit_height): for i in range(8): if x > xmax: xmax = x if y > ymax: ymax = y bit = bits_data.pop() if bit == 0: tattoo.add(Use("#zero", (x, y))) elif bit == 1: tattoo.add(Use("#one", (x, y))) else: raise RuntimeError() x, y = self.shift_bit(x, y) x, y = self.shift_byte(j, y) xmax += self.poly.inner_circle_radius * self.dimens.bit_radius * 2 ymax += self.poly.outer_circle_radius * self.dimens.bit_radius * 2 tattoo_width = xmax - xmin tattoo_height = ymax - ymin scaled_width_mm = 65 scale_factor = scaled_width_mm * 3.78 / tattoo_width scaled_height_mm = tattoo_height * scale_factor / 3.78 tattoo.scale(scale_factor) tattoo.translate( 3.78 * (210 - scaled_width_mm) / 2, 3.78 * (297 - scaled_height_mm) / 2 ) self.dwg.add(tattoo) self.dwg.save(pretty=True)
def save(cls, image, filename, mosaic=False): # Use debug=False everywhere to turn off SVG validation, # which turns out to be obscenely expensive in this # library. DEBUG = False svg = svgwrite.Drawing(filename=filename, style='background-color: black;', size=(("%dpx" % (2 * image.r_outer), "%dpx" % (2 * image.r_outer))), debug=DEBUG) group = Group(debug=DEBUG) group.translate(image.r_outer, image.r_outer) for y, row in enumerate(image.pixels): ring = image.rings[y] theta = 2 * math.pi / len(row) r1 = ring.center + image.r_ring / 2 r2 = ring.center - image.r_ring / 2 for x, c in enumerate(row): if mosaic: path = Path(stroke='black', stroke_width=1, fill=cls.color_hex(c), debug=DEBUG) path.push( (('M', 0, r2), ('L', 0, r1), ('A', r1, r1, 0, '0,0', (r1 * sin(theta), r1 * cos(theta))), ('L', r2 * sin(theta), r2 * cos(theta)), ('A', r2, r2, 0, '0,1', (0, r2)))) else: path = Path(stroke=cls.color_hex(c), stroke_width=image.r_pixel, fill='none', debug=DEBUG) path.push((('M', 0, ring.center), ('A', ring.center, ring.center, 0, '0,0', (ring.center * sin(theta), ring.center * cos(theta))))) path.rotate(180 - degrees(theta * (x + 1)), center=(0, 0)) group.add(path) svg.add(group) svg.save()
def convert_font(): cwd = pathlib.Path.cwd() for root, dirs, files in os.walk('{}/exports'.format(cwd)): for name in files: source = os.path.join(root, name) target = 'fonts/{}.min.svg'.format(name.split('.')[0]) print(source) try: paths, attr = svg2paths(source) except Exception as e: print(e) continue try: xmin, xmax, ymin, ymax = bounding_box(paths) except Exception as e: print(e) continue dx = xmax - xmin dy = ymax - ymin viewbox = '{} {} {} {}'.format(xmin, ymin, dx, dy) attr = {'viewBox': viewbox, 'preserveAspectRatio': 'xMidYMid meet'} wsvg(paths=paths, svg_attributes=attr, filename=source) doc = SaxDocument(source) d = doc.get_pathd_and_matrix()[0] g = Group() dwg = svgwrite.Drawing(target) dwg.viewbox(minx=xmin, miny=ymin, width=dx, height=dy) dwg.add(g) g.scale(sx=1, sy=-1) g.translate(tx=0, ty=-dy - ymin * 2) g.add(dwg.path(d)) dwg.save() generate_pdf(target)
def new_drawing(filename='plot.svg', display_scale=1): from svgwrite.drawing import Drawing from svgwrite.container import Group size = 2 * PADDING + SCALE dwg = Drawing(filename, size=(size * display_scale, size * display_scale)) dwg.viewbox(minx=0, miny=0, width=size, height=size) add_stylesheet(dwg) canvas = Group(id='canvas') canvas.translate(PADDING, PADDING) canvas.scale(1, -1) canvas.translate(0, -SCALE) dwg.add(canvas) return dwg, canvas
def _draw_cable(self, color, url, labels): """ Return an SVG group containing a line element and text labels representing a Cable. :param color: Cable (line) color :param url: Hyperlink URL :param labels: Iterable of text labels """ group = Group(class_='connector') # Draw a "shadow" line to give the cable a border start = (OFFSET + self.center, self.cursor) height = PADDING * 2 + LINE_HEIGHT * len(labels) + PADDING * 2 end = (start[0], start[1] + height) cable_shadow = Line(start=start, end=end, class_='cable-shadow') group.add(cable_shadow) # Draw the cable cable = Line(start=start, end=end, style=f'stroke: #{color}') group.add(cable) self.cursor += PADDING * 2 # Add link link = Hyperlink(href=f'{self.base_url}{url}', target='_blank') # Add text label(s) for i, label in enumerate(labels): self.cursor += LINE_HEIGHT text_coords = (self.center + PADDING * 2, self.cursor - LINE_HEIGHT / 2) text = Text(label, insert=text_coords, class_='bold' if not i else []) link.add(text) group.add(link) self.cursor += PADDING * 2 return group
def translate( self, data: DataContainer, width: Width, height: Height, x_key: str = "x", y_key: str = "y", v_key: str = "value", is_1d: bool = False, **kwargs: Any, ) -> Group: """Base translator for SVG Heatmap renderer. :param data: point datum array. point datum represent by dict consists with x, y positions and value. :param width: drawing area width. :param height: drawing area height. :param x_key: key name of x for points. :param y_key: key name of y for points. :param v_key: key name of value for points. :param is_1d: render rect as full width (One-dimensional heatmap). :return: Rect nodes enclosed with Group node. """ return Group()
def create_svg_document_with_light(elements, size, viewbox=None, background_color="white", background_opacity=1.0): """Create the full SVG document, with a lighting filter. Resources: - https://www.w3.org/TR/SVG11/filters.html#LightSourceDefinitions - https://svgwrite.readthedocs.io/en/master/classes/filters.html - http://www.svgbasics.com/filters2.html - https://css-tricks.com/look-svg-light-source-filters/ :param viewbox: (minx, miny, width, height) """ # TODO work in progress # TODO have a look at how threejs is converted to SVG: # https://github.com/mrdoob/three.js/blob/master/examples/jsm/renderers/SVGRenderer.js dwg = Drawing("ase.svg", profile="full", size=size) light_filter = dwg.defs.add(Filter(size=("100%", "100%"))) diffuse_lighting = light_filter.feDiffuseLighting(size=size, surfaceScale=10, diffuseConstant=1, kernelUnitLength=1, color="white") diffuse_lighting.fePointLight(source=(size[0], 0, 1000)) light_filter.feComposite(operator="arithmetic", k1=1) root = Group(id="root", filter=light_filter.get_funciri()) dwg.add(root) # if Color(background_color).web != "white": # apparently the best way, see: https://stackoverflow.com/a/11293812/5033292 root.add( shapes.Rect(size=size, fill=background_color, fill_opacity=background_opacity)) for element in elements: root.add(element) if viewbox: dwg.viewbox(*viewbox) return dwg
def create_svg_document(elements, size, viewbox=None, background_color="white", background_opacity=1.0): """Create the full SVG document. :param viewbox: (minx, miny, width, height) """ dwg = Drawing("ase.svg", profile="tiny", size=size) root = Group(id="root") dwg.add(root) # if Color(background_color).web != "white": # apparently the best way, see: https://stackoverflow.com/a/11293812/5033292 root.add( shapes.Rect(size=size, fill=background_color, fill_opacity=background_opacity)) for element in elements: root.add(element) if viewbox: dwg.viewbox(*viewbox) return dwg
def generate_object(self, specimen_number, doc_type): style = self.styles[doc_type] width = round(self.width + style['margin'] * 2, 7) height = round(self.height + style['margin'] * 2, 7) document = self.get_SVG_document(specimen_number, width, height) elements = Group() elements['transform'] = 'translate({}, {})'.format( style['margin'], style['margin']) document.add(elements) outline = self.get_path(self.ruler_outline, 0, style=style['outlineStyle']) elements.add(outline) text = number_to_string(specimen_number) + self.ruler_total text = self.text_to_paths(text, style['numbersPathType'], style['numbersStyle']) text['transform'] = 'translate(12.5, 20.5)' elements.add(text) return document
def test_constructor(self): g = Group() self.assertEqual(g.tostring(), "<g />")
def test_object_link_auto_id(self): AutoID(999) # set next id to 999 g = Group() use = Use(g) self.assertEqual(use.tostring(), '<use xlink:href="#id999" />')
def test_object_link(self): g = Group(id='test') use = Use(g) self.assertEqual(use.tostring(), '<use xlink:href="#test" />')
#!/usr/bin/env python3 from svgwrite import inch, Drawing from svgwrite.container import Group, Defs from utmtool.rulers import corner_ruler from utmtool.utils import write_to_pdf, UseInch, translate_inch from utmtool.template import tool_template #------------------------------------------------------------------------------- # Build UTM Tool #------------------------------------------------------------------------------- tool_size = 2.48 utm_tool = Group(id='utm_tool', stroke="black", stroke_width=0.5) utm_tool.add(tool_template("ВТ", tool_size)) # Large map ruler scale = 2500 diag_shift = 0.40 r = corner_ruler(scale=scale, length=100, major_interval=10, minor_per_major=10) translate_inch(r, tool_size - diag_shift, diag_shift) utm_tool.add(r) # Small map ruler scale = 8000 diag_shift = 0.85 r = corner_ruler(scale=scale, length=200, major_interval=50, minor_per_major=5)
def generate_pcb(self, footprint_name, specs): total_pincount = eval(specs['total_pincount']) pad_pitch = eval(specs['pad_pitch']) pad_width = eval(specs['pad_width']) pad_length = eval(specs['pad_length']) lead_to_lead_length= eval(specs['lead_to_lead_length']) if specs.has_key('sides'): sides = eval(specs['sides']) else: sides = 2 if specs.has_key('top_margin'): top_margin = eval(specs['top_margin']) else: top_margin = 1 if specs.has_key('left_margin'): left_margin = eval(specs['left_margin']) else: left_margin = 1 if specs.has_key('ic_label'): ic_label = eval(specs['ic_label']) else: ic_label = 'IC' footprint_width = str((2 * left_margin) + (pad_pitch * (total_pincount / sides))) footprint_height = str((2 * top_margin) + (pad_length * 2) + lead_to_lead_length) # Create the document svg_document = svgwrite.Drawing(filename = footprint_name + ".svg",size=(footprint_width+'mm', footprint_height+'mm'), viewBox=('0 0 ' + str(footprint_width) + ' ' + str(footprint_height))) # Create the silkscreen silkscreen = Group(id='Silkscreen') if sides==2: # x line_1 = left_margin - pad_width line_2 = left_margin + ((total_pincount / sides) * pad_pitch) # y line_start = top_margin + (pad_length / 2) line_end = top_margin + lead_to_lead_length + + (pad_length / 2) silkscreen.add(svg_document.line(start=(line_1, line_start), end=(line_1,line_end), stroke_width = pad_width, stroke = "white", fill = "rgb(255,255,255)")) silkscreen.add(svg_document.line(start=(line_2, line_start), end=(line_2,line_end), stroke_width = pad_width, stroke = "white", fill = "rgb(255,255,255)")) # silkscreen.add(svg_document.text(ic_label,insert = (30, 30))) svg_document.add(silkscreen) copper_layer = Group(id='copper1') if sides >= 1: for s in range(0,sides): if s==0: side_start = top_margin elif s==1: side_start = top_margin + lead_to_lead_length for p in range(0,total_pincount / sides): # svg_document.add(svg_document.translate((s, 40))) copper_layer.add(svg_document.rect(insert = (left_margin + (p * pad_pitch), side_start), size = (pad_width, pad_length), stroke_width = "0", # connectorname = '\"' + str(((s+1) * (total_pincount/sides))+p) + '\"', fill = "#ffbf00")) svg_document.add(copper_layer) # Write the file svg_document.save() # Rewrite the file adding in the connectors import xml.dom.minidom xmldoc = xml.dom.minidom.parse(footprint_name + ".svg") pretty_xml_as_string = xmldoc.toprettyxml() doclist = pretty_xml_as_string.split('\n') newdoc = "" cnum = 1 for l in doclist: if "#ffbf00" in l: print "Found" l = l.replace("<rect fill=\"#ffbf00\"","<rect fill=\"#ffbf00\" connector=\"%d\" id=\"connector%dpin\"" % (cnum,cnum)) cnum += 1 newdoc = newdoc + l + '\n' f = open(footprint_name + ".svg", 'w') f.write(newdoc)
def test_add_subelement(self): group = Group(id='group') subgroup = Group(id='subgroup') group.add(subgroup) self.assertEqual(group.tostring(), '<g id="group"><g id="subgroup" /></g>')
def test_add_group(self): group = Group(id='group') subgroup = group.add(Group(id='subgroup')) self.assertEqual(group.tostring(), '<g id="group"><g id="subgroup" /></g>')
def nest(output, files, wbin, hbin, enclosing_rectangle=False): packer = newPacker() def float2dec(x): return _float2dec(x, 4) def bbox_paths(paths): bbox = None for p in paths: p_bbox = p.bbox() if bbox is None: bbox = p_bbox else: bbox = (min(p_bbox[0], bbox[0]), max(p_bbox[1], bbox[1]), min(p_bbox[2], bbox[2]), max(p_bbox[3], bbox[3])) return tuple(float2dec(x) for x in bbox) all_paths = {} for svg\ in files: paths, attributes = svg2paths(svg) bbox = bbox_paths(paths) for i in range(files[svg]): rid = svg + str(i) all_paths[rid] = {'paths': paths, 'bbox': bbox} print(rid) packer.add_rect(bbox[1] - bbox[0], bbox[3] - bbox[2], rid=rid) print('Rectangle packing...') while True: packer.add_bin(wbin, hbin) packer.pack() rectangles = {r[5]: r for r in packer.rect_list()} if len(rectangles) == len(all_paths): break else: print('not enough space in the bin, adding ') combineds = {} print('packing into SVGs...') for rid, obj in all_paths.items(): paths = obj['paths'] bbox = obj['bbox'] group = Group() width, height = (float2dec(bbox[1] - bbox[0]), float2dec(bbox[3] - bbox[2])) bin, x, y, w, h, _ = rectangles[rid] if bin not in combineds: svg_file = output if bin != 0: splitext = os.path.splitext(svg_file) svg_file = splitext[0] + '.%s' % bin + splitext[1] dwg = Drawing(svg_file, profile='tiny', size=('%smm' % wbin, '%smm' % hbin), viewBox="0 0 %s %s" % (wbin, hbin)) combineds[bin] = dwg combined = combineds[bin] if (width > height and w > h) or \ (width < height and w < h) or \ (width == height and w == h): rotate = 0 dx = -bbox[0] dy = -bbox[2] else: rotate = 90 dx = -bbox[2] dy = -bbox[0] for p in paths: path = Path(d=p.d()) path.stroke(color='red', width='1') path.fill(opacity=0) group.add(path) group.translate(x + dx, y + dy) group.rotate(rotate) combined.add(group) for combined in combineds.values(): if enclosing_rectangle: r = Rect(size=(wbin, hbin)) r.fill(opacity=0) r.stroke(color='lightgray') combined.add(r) print('SVG saving...') combined.save(pretty=True)