def effect(self): xforms = [] for i in range(self.NXFORM): if getattr(self.options, 'xform%d' % i): t = [ getattr(self.options, "%s%d" % (p, i)) for p in self.XFORM_PARAMS ] xforms.append(inkex.Transform(t)) if not xforms: inkex.errormsg(_('There are no transforms to apply')) return False if not self.svg.selected: inkex.errormsg(_('There is no selection to duplicate')) return False nodes = self.svg.selected.values() grp = inkex.Group('IFS') layer = self.svg.get_current_layer().add(grp) for i in range(self.options.iter): n = [] for node in nodes: for x in xforms: d = node.copy() d.transform = x * d.transform n.append(d) g = inkex.Group('IFS iter %d' % i) g.add(*n) grp.add(g) nodes = n return True
def update_lettering(self, raise_error=False): del self.group[:] if self.settings.scale == 100: destination_group = self.group else: destination_group = inkex.Group( attrib={ # L10N The user has chosen to scale the text by some percentage # (50%, 200%, etc). If you need to use the percentage symbol, # make sure to double it (%%). INKSCAPE_LABEL: _("Text scale %s%%") % self.settings.scale }) self.group.append(destination_group) font = self.fonts.get(self.font_chooser.GetValue(), self.default_font) try: font.render_text(self.settings.text, destination_group, back_and_forth=self.settings.back_and_forth, trim=self.settings.trim) except FontError as e: if raise_error: inkex.errormsg( "Error: Text cannot be applied to the document.\n%s" % e) return else: pass if self.settings.scale != 100: destination_group.attrib['transform'] = 'scale(%s)' % ( self.settings.scale / 100.0)
def write_month_header(self, g, m): txt_atts = {'style': str(inkex.Style(self.style_month)), 'x': str(-self.day_w / 3), 'y': str(self.day_h / 5)} try: text = unicode(self.options.month_names[m - 1], self.options.input_encode) if self.options.primary_calendar == "hijri": text = unicode(self.options.hijri_month_names[m - 1], self.options.input_encode) g.add(TextElement(**txt_atts)).text = text except: raise ValueError('You must select a correct system encoding.') gw = g.add(inkex.Group()) week_x = 0 if self.options.start_day == 'sun': day_names = self.options.day_names[:] else: day_names = self.options.day_names[1:] day_names.append(self.options.day_names[0]) if self.options.show_weeknr: day_names.insert(0, self.options.weeknr_name) for wday in day_names: txt_atts = {'style': str(inkex.Style(self.style_day_name)), 'x': str(self.day_w * week_x), 'y': str(self.day_h)} try: gw.add(TextElement(**txt_atts)).text = unicode( wday, self.options.input_encode) except: raise ValueError('You must select a correct system encoding.') week_x += 1
def get_or_create_group(self): if self.svg.selected: groups = set() for node in self.svg.selected.values(): if node.tag == SVG_GROUP_TAG and INKSTITCH_LETTERING in node.attrib: groups.add(node) for group in node.iterancestors(SVG_GROUP_TAG): if INKSTITCH_LETTERING in group.attrib: groups.add(group) if len(groups) > 1: inkex.errormsg(_("Please select only one block of text.")) sys.exit(1) elif len(groups) == 0: inkex.errormsg( _("You've selected objects that were not created by the Lettering extension. " "Please clear your selection or select different objects before running Lettering again." )) sys.exit(1) else: return list(groups)[0] else: group = inkex.Group( attrib={ INKSCAPE_LABEL: _("Ink/Stitch Lettering"), "transform": get_correction_transform(self.get_current_layer(), child=True) }) self.get_current_layer().append(group) return group
def draw_coluor_bars(self, cx, cy, rotate, name, parent, bbox): group = parent.add(inkex.Group(id=name)) group.transform = inkex.Transform(translate=(cx, cy)) * inkex.Transform(rotate=rotate) loc = 0 if bbox: loc = min(self.mark_size / 3, max(bbox.width, bbox.height) / 45) for bar in [{'c': '*', 'stroke': '#000', 'x': 0, 'y': -(loc + 1)}, {'c': 'r', 'stroke': '#0FF', 'x': 0, 'y': 0}, {'c': 'g', 'stroke': '#F0F', 'x': (loc * 11) + 1, 'y': -(loc + 1)}, {'c': 'b', 'stroke': '#FF0', 'x': (loc * 11) + 1, 'y': 0} ]: i = 0 while i <= 1: color = inkex.Color('white') if bar['c'] == 'r' or bar['c'] == '*': color.red = 255 * i if bar['c'] == 'g' or bar['c'] == '*': color.green = 255 * i if bar['c'] == 'b' or bar['c'] == '*': color.blue = 255 * i r_att = {'fill': str(color), 'stroke': bar['stroke'], 'stroke-width': loc/8, 'x': str((loc * i * 10) + bar['x']), 'y': str(bar['y']), 'width': loc, 'height': loc} rect = Rectangle() for att, value in r_att.items(): rect.set(att, value) group.add(rect) i += 0.1
def _expand_defs(root): from inkex import Transform, ShapeElement from copy import deepcopy for el in root: if isinstance(el, inkex.Use): # <group> element will replace <use> node group = inkex.Group() # add all objects from symbol node for obj in el.href: group.append(deepcopy(obj)) # translate group group.transform = Transform(translate=(float(el.attrib["x"]), float(el.attrib["y"]))) # replace use node with group node parent = el.getparent() parent.remove(el) parent.add(group) el = group # required for recursive defs # expand children defs TexTextElement._expand_defs(el)
def csp_to_path(self, node, csp_list, transform=None): """Create new paths based on csp data, return group with paths.""" # set up stroke width, group stroke_width = self.svg.unittouu('1px') stroke_color = '#000000' style = { 'fill': 'none', 'stroke': stroke_color, 'stroke-width': str(stroke_width), } group = inkex.Group() # apply gradientTransform and node's preserved transform to group group.transform = transform * node.transform # convert each csp to path, append to group for csp in csp_list: elem = group.add(inkex.PathElement()) elem.style = style elem.path = inkex.CubicSuperPath(csp) if self.options.mode == 'outline': elem.path.close() elif self.options.mode == 'faces': if len(csp) == 1 and len(csp[0]) == 5: elem.path.close() return group
def effect(self): if len(self.svg.selected) == 0: inkex.errormsg("Please select some paths first.") exit() path_attribute = self.options.path_attribute is_text_attribute = path_attribute in ['id', 'label'] for id, node in self.svg.selection.filter(inkex.PathElement).items(): to_show = self.extract_path_attribute(path_attribute, node) node.path.transform(node.composed_transform()).to_superpath() bbox = node.bounding_box() tx, ty = bbox.center if self.options.group: group_element = node.getparent().add(inkex.Group()) group_element.add(node) group_element.set('id', node.get('id') + "_group") text_element = group_element.add(inkex.TextElement()) else: text_element = node.getparent().add(inkex.TextElement()) tspan_element = text_element.add(inkex.Tspan()) tspan_element.set('sodipodi:role', 'line') styles = { 'text-align': 'center', 'vertical-align': 'bottom', 'text-anchor': 'middle', 'font-size': str(self.options.fontsize) + 'px', 'font-weight': self.options.fontweight, 'font-style': 'normal', 'font-family': self.options.font, 'fill': str(self.options.color) } tspan_element.set('style', str(inkex.Style(styles))) tspan_element.set('dy', '0') if is_text_attribute: if self.options.capitals: to_show = to_show.upper() if self.options.matchre != '': matches = re.findall(self.options.matchre, to_show) if len(matches) > 0: to_show = matches[0] if self.options.replaced != '': to_show = to_show.replace(self.options.replaced, self.options.replacewith) tspan_element.text = to_show tspan_element.set('id', node.get('id') + "_tspan") text_element.set('id', node.get('id') + "_text") text_element.set('x', str(tx)) text_element.set('y', str(ty)) text_element.set( 'transform', 'rotate(%s, %s, %s)' % (-int(self.options.angle), tx, ty))
def effect(self): delx = math.cos(math.radians( self.options.angle)) * self.options.magnitude dely = math.sin(math.radians( self.options.angle)) * self.options.magnitude for node in self.svg.selection.filter(inkex.PathElement): group = node.getparent().add(inkex.Group()) facegroup = group.add(inkex.Group()) group.append(node) if node.transform: group.transform = node.transform node.transform = None facegroup.style = node.style for cmd_proxy in node.path.to_absolute().proxy_iterator(): self.process_segment(cmd_proxy, facegroup, delx, dely)
def create_new_group(parent, insert_index): group = inkex.Group( attrib={ INKSCAPE_LABEL: _("Auto-Satin"), "transform": get_correction_transform(parent, child=True) }) parent.insert(insert_index, group) return group
def get_group(self, node): """ make a clipped group, clip with clone of original, clipped group include original and group of paths. """ defs = self.svg.defs clip = defs.add(ClipPath()) new_node = clip.add(node.copy()) clip_group = node.getparent().add(inkex.Group()) group = clip_group.add(inkex.Group()) clip_group.set('clip-path', clip.get_id(as_url=2)) # make a blur filter reference by the style of each path filt = defs.add(Filter(x='-0.5', y='-0.5',\ height=str(self.options.blurheight),\ width=str(self.options.blurwidth))) filt.add_primitive('feGaussianBlur', stdDeviation=self.options.stddev) return group, inkex.Style(filter=filt.get_id(as_url=2))
def add_dot(self, node): """Add a dot label for this path element""" group = node.getparent().add(inkex.Group()) dot_group = group.add(inkex.Group()) num_group = group.add(inkex.Group()) group.transform = node.transform style = inkex.Style({'stroke': 'none', 'fill': '#000'}) for step, (x, y) in enumerate(node.path.end_points): circle = dot_group.add(Circle(cx=str(x), cy=str(y),\ r=str(self.svg.unittouu(self.options.dotsize) / 2))) circle.style = style num_group.append( self.add_text( x + (self.svg.unittouu(self.options.dotsize) / 2), y - (self.svg.unittouu(self.options.dotsize) / 2), self.options.start + (self.options.step * step))) node.delete()
def create_troubleshoot_layer(self): svg = self.document.getroot() layer = svg.find(".//*[@id='__validation_layer__']") if layer is None: layer = inkex.Group(attrib={ 'id': '__validation_layer__', INKSCAPE_LABEL: _('Troubleshoot'), INKSCAPE_GROUPMODE: 'layer', }) svg.append(layer) else: # Clear out everything from the last run del layer[:] add_layer_commands(layer, ["ignore_layer"]) error_group = inkex.Group(attrib={ "id": '__validation_errors__', INKSCAPE_LABEL: _("Errors"), }) layer.append(error_group) warning_group = inkex.Group(attrib={ "id": '__validation_warnings__', INKSCAPE_LABEL: _("Warnings"), }) layer.append(warning_group) type_warning_group = inkex.Group(attrib={ "id": '__validation_ignored__', INKSCAPE_LABEL: _("Type Warnings"), }) layer.append(type_warning_group) self.troubleshoot_layer = layer self.error_group = error_group self.warning_group = warning_group self.type_warning_group = type_warning_group
def effect(self): mesh = om.read_trimesh(self.options.inputfile) fullUnfolded, unfoldedComponents = unfold(mesh) # Compute maxSize of the components # All components must be scaled to the same size as the largest component maxSize = 0 for unfolding in unfoldedComponents: [xmin, ymin, boxSize] = findBoundingBox(unfolding[0]) if boxSize > maxSize: maxSize = boxSize # Create a new container group to attach all paperfolds paperfoldMainGroup = self.document.getroot().add( inkex.Group(id=self.svg.get_unique_id( "paperfold-"))) #make a new group at root level for i in range(len(unfoldedComponents)): paperfoldPageGroup = writeSVG(self, unfoldedComponents[i], maxSize, self.options.printNumbers) #translate the groups next to each other to remove overlappings if i != 0: previous_bbox = paperfoldMainGroup[i - 1].bounding_box() this_bbox = paperfoldPageGroup.bounding_box() paperfoldPageGroup.set( "transform", "translate(" + str(previous_bbox.left + previous_bbox.width - this_bbox.left) + ", 0.0)") paperfoldMainGroup.append(paperfoldPageGroup) #apply scale factor translation_matrix = [[self.options.scalefactor, 0.0, 0.0], [0.0, self.options.scalefactor, 0.0]] paperfoldMainGroup.transform = Transform( translation_matrix) * paperfoldMainGroup.transform #paperfoldMainGroup.set('transform', 'scale(%f,%f)' % (self.options.scalefactor, self.options.scalefactor)) #adjust canvas to the inserted unfolding if self.options.resizetoimport: bbox = paperfoldMainGroup.bounding_box() namedView = self.document.getroot().find( inkex.addNS('namedview', 'sodipodi')) doc_units = namedView.get(inkex.addNS('document-units', 'inkscape')) root = self.svg.getElement('//svg:svg') offset = self.svg.unittouu( str(self.options.extraborder) + self.options.extraborder_units) root.set( 'viewBox', '%f %f %f %f' % (bbox.left - offset, bbox.top - offset, bbox.width + 2 * offset, bbox.height + 2 * offset)) root.set('width', str(bbox.width + 2 * offset) + doc_units) root.set('height', str(bbox.height + 2 * offset) + doc_units)
def add_group(document, node, command): parent = node.getparent() group = inkex.Group( attrib={ "id": generate_unique_id(document, "command_group"), INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command), "transform": get_correction_transform(node) }) parent.insert(parent.index(node) + 1, group) return group
def render_stitch_plan(svg, stitch_plan, realistic=False, visual_commands=True): layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']") if layer is None: layer = inkex.Group( attrib={ 'id': '__inkstitch_stitch_plan__', INKSCAPE_LABEL: _('Stitch Plan'), INKSCAPE_GROUPMODE: 'layer' }) else: # delete old stitch plan del layer[:] # make sure the layer is visible layer.set('style', 'display:inline') svg.append(layer) for i, color_block in enumerate(stitch_plan): group = inkex.Group( attrib={ 'id': '__color_block_%d__' % i, INKSCAPE_LABEL: "color block %d" % (i + 1) }) layer.append(group) if realistic: color_block_to_realistic_stitches(color_block, svg, group) else: color_block_to_paths(color_block, svg, group, visual_commands) if realistic: filter_document = inkex.load_svg(realistic_filter) svg.defs.append(filter_document.getroot())
def effect(self): font_dir = self.options.font_dir file_format = self.options.file_format if not os.path.isdir(font_dir): errormsg( _("Font directory not found. Please specify an existing directory." )) glyphs = list(Path(font_dir).rglob(file_format)) if not glyphs: glyphs = list(Path(font_dir).rglob(file_format.lower())) document = self.document.getroot() for glyph in glyphs: letter = self.get_glyph_element(glyph) label = "GlyphLayer-%s" % letter.get(INKSCAPE_LABEL, ' ').split('.')[0][-1] group = inkex.Group( attrib={ INKSCAPE_LABEL: label, INKSCAPE_GROUPMODE: "layer", "transform": get_correction_transform(document, child=True) }) # remove color block groups if we import without commands # there will only be one object per color block anyway if not self.options.import_commands: for element in letter.iter(SVG_PATH_TAG): group.insert(0, element) else: group.insert(0, letter) document.insert(0, group) group.set('style', 'display:none') # users may be confused if they get an empty document # make last letter visible again group.set('style', None) # In most cases trims are inserted with the imported letters. # Let's make sure the trim symbol exists in the defs section ensure_symbol(document, 'trim') self.insert_baseline(document)
def effect(self): self.validate_options() self.calculate_size_and_positions() parent = self.document.getroot() txt_atts = {'id': 'year_' + str(self.options.year)} self.year_g = parent.add(inkex.Group(**txt_atts)) txt_atts = {'style': str(inkex.Style(self.style_year)), 'x': str(self.doc_w / 2), 'y': str(self.day_w * 1.5)} self.year_g.add(TextElement(**txt_atts)).text = str(self.options.year) try: if self.options.month == 0: for m in range(1, 13): self.create_month(m) else: self.create_month(self.options.month) except ValueError as err: return inkex.errormsg(str(err))
def effect(self): dot_group = self.svg.add(inkex.Group()) for node in self.svg.get_selected( inkex.PathElement ): #works for InkScape (1:1.0+devel+202008292235+eff2292935) @ Linux and for Windows (but with deprecation) #for node in self.svg.selection.filter(inkex.PathElement).values(): #works for InkScape 1.1dev (9b1fc87, 2020-08-27)) @ Windows points = list(node.path.end_points) start = points[0] end = points[len(points) - 1] if start[0] == end[0] and start[1] == end[1]: self.drawCircle(dot_group, '#00FF00', start) self.drawCircle( dot_group, '#FFFF00', points[1] ) #draw one point which gives direction of the path else: #open contour with start and end point self.drawCircle(dot_group, '#FF0000', start) self.drawCircle(dot_group, '#0000FF', end)
def _render_line(self, line, position, glyph_set): """Render a line of text. An SVG XML node tree will be returned, with an svg:g at its root. If the font metadata requests it, Auto-Satin will be applied. Parameters: line -- the line of text to render. position -- Current position. Will be updated to point to the spot immediately after the last character. glyph_set -- a FontVariant instance. Returns: An svg:g element containing the rendered text. """ group = inkex.Group(attrib={INKSCAPE_LABEL: line}) last_character = None for character in line: if self.letter_case == "upper": character = character.upper() elif self.letter_case == "lower": character = character.lower() glyph = glyph_set[character] if character == " " or (glyph is None and self.default_glyph == " "): position.x += self.word_spacing last_character = None else: if glyph is None: glyph = glyph_set[self.default_glyph] if glyph is not None: node = self._render_glyph(glyph, position, character, last_character) group.append(node) last_character = character return group
def effect(self): if len(self.svg.selected) != 2: inkex.errormsg( "Please select exact two objects:\n1. object representing path,\n2. object representing dots." ) return nodes = list(self.svg.selected.items()) iddot = nodes[0][0] idpath = nodes[1][0] dot = self.svg.selected[iddot] path = self.svg.selected[idpath] self.svg.selected.popitem() self.svg.selected.popitem() bb = dot.bounding_box() parent = path.find('..') group = inkex.Group() parent.add(group) end_points = list(path.path.end_points) control_points = [] for cp in path.path.control_points: is_endpoint = False for ep in end_points: if cp.x == ep.x and cp.y == ep.y: is_endpoint = True break if not is_endpoint: control_points.append(cp) pointlist = [] if self.options.endpoints: pointlist += end_points if self.options.controlpoints: pointlist += control_points for point in pointlist: clone = inkex.Use() clone.set('xlink:href', '#' + iddot) clone.set('x', point.x - bb.center.x) clone.set('y', point.y - bb.center.y) group.add(clone)
def longitude_lines(self, number, tilt, radius, rotate): """Add lines of latitude as a group""" # GROUP FOR THE LINES OF LONGITUDE grp_long = inkex.Group() grp_long.set('inkscape:label', 'Lines of Longitude') # angle between neighbouring lines of longitude in degrees #delta_long = 360.0 / number for i in range(0, number // 2): # The longitude of this particular line in radians long_angle = rotate + (i * (360.0 / number)) * (pi / 180.0) if long_angle > pi: long_angle -= 2 * pi # the rise is scaled by the sine of the tilt # length = sqrt(width*width+height*height) #by pythagorean theorem # inverse = sin(acos(length/so.RADIUS)) inverse = abs(sin(long_angle)) * cos(tilt) rads = (radius * inverse + EPSILON, radius) # The rotation of the ellipse to get it to pass through the pole (degs) rotation = atan((radius * sin(long_angle) * sin(tilt)) / (radius * cos(long_angle))) * (180.0 / pi) # remove the hidden side of the ellipses if required # this is always exactly half the ellipse, but we need to find out which half start_end = (0, 2 * pi ) # Default start and end angles -> full ellipse if self.options.HIDE_BACK: if long_angle <= pi / 2: # cut out the half ellispse that is hidden start_end = (pi / 2, 3 * pi / 2) else: start_end = (3 * pi / 2, pi / 2) # finally, draw the line of longitude # the centre is always at the centre of the sphere elem = grp_long.add(self.draw_ellipse(rads, (0, 0), start_end)) # the rotation will be applied about the group centre (the centre of the sphere) elem.transform = inkex.Transform(rotate=(rotation, )) return grp_long
def effect(self): if not self.svg.selected: raise inkex.AbortExtension( _('You must to select some "Slicer rectangles" ' 'or other "Layout groups".')) base_elements = self.get_slicer_layer().descendants() for key, node in self.svg.selected.id_dict().items(): if node not in base_elements: raise inkex.AbortExtension( _(f'The element "{key}" is not in the Web Slicer layer')) g_parent = node.getparent() group = g_parent.add(inkex.Group()) desc = group.add(inkex.Desc()) desc.text = self.get_conf_text_from_list([ 'html_id', 'html_class', 'width_unity', 'height_unity', 'bg_color' ]) for node in self.svg.selected.values(): group.insert(1, node)
def draw_reg_marks(self, cx, cy, rotate, name, parent): colours = ['#000000', '#00ffff', '#ff00ff', '#ffff00', '#000000'] g = parent.add(inkex.Group(id=name)) for i in range(len(colours)): style = {'fill': colours[i], 'fill-opacity': '1', 'stroke': 'none'} r = (self.mark_size / 2) step = r stroke = r / len(colours) regoffset = stroke * i regmark_attribs = {'style': str(inkex.Style(style)), 'd': 'm' + ' ' + str(-regoffset) + ',' + str(r) + ' ' + str(-stroke) + ',0' + ' ' + str(step) + ',' + str(-r) + ' ' + str(-step) + ',' + str(-r) + ' ' + str(stroke) + ',0' + ' ' + str(step) + ',' + str(r) + ' ' + str(-step) + ',' + str(r) + ' z', 'transform': 'translate(' + str(cx) + ',' + str(cy) + ') rotate(' + str(rotate) + ')'} g.add(inkex.PathElement(**regmark_attribs))
def latitude_lines(self, number, tilt, radius): """Add lines of latitude as a group""" # GROUP FOR THE LINES OF LATITUDE grp_lat = inkex.Group() grp_lat.set('inkscape:label', 'Lines of Latitude') # Angle between the line of latitude (subtended at the centre) delta_lat = 180.0 / number for i in range(1, number): # The angle of this line of latitude (from a pole) lat_angle = ((delta_lat * i) * (pi / 180)) # The width of the LoLat (no change due to projection) # The projected height of the line of latitude rads = ( radius * sin(lat_angle), # major (radius * sin(lat_angle) * sin(tilt)) + EPSILON, # minor ) # The x position is the sphere center, The projected y position of the LoLat pos = (0, radius * cos(lat_angle) * cos(tilt)) if self.options.HIDE_BACK: if lat_angle > tilt: # this LoLat is partially or fully visible if lat_angle > pi - tilt: # this LoLat is fully visible grp_lat.add(self.draw_ellipse(rads, pos)) else: # this LoLat is partially visible proportion = -(acos(tan(lat_angle - pi / 2) \ / tan(pi / 2 - tilt))) / pi + 1 # make the start and end angles (mirror image around pi/2) start_end = (pi / 2 - proportion * pi, pi / 2 + proportion * pi) grp_lat.add(self.draw_ellipse(rads, pos, start_end)) else: # just draw the full lines of latitude grp_lat.add(self.draw_ellipse(rads, pos)) return grp_lat
def effect(self): centro_ancho_documento = self.svg.unittouu(self.document.getroot().get('width'))/2 centro_alto_documento = self.svg.unittouu(self.document.getroot().get('height'))/2 ancho_caja = self.svg.unittouu(str(self.options.width) + self.options.unit) alto_caja = self.svg.unittouu(str(self.options.height) + self.options.unit) largo_caja = self.svg.unittouu(str(self.options.depth) + self.options.unit) ancho_pestana_cola = self.svg.unittouu(str(self.options.glue_tab) + self.options.unit) alto_pestana_cierre = self.svg.unittouu(str(self.options.close_tab) + self.options.unit) alto_pestana = self.svg.unittouu(str(self.options.side_tabs) + self.options.unit) if self.options.unit=="mm": medida_pestana1=5 medida_pestana2=1 medida_pestana3=4 medida_pestana4=3 if self.options.unit=="cm": medida_pestana1=0.5 medida_pestana2=0.1 medida_pestana3=0.4 medida_pestana4=0.3 if self.options.unit=='in': medida_pestana1=0.196 medida_pestana2=0.039 medida_pestana3=0.157 medida_pestana4=0.118 medida1_pestanas_laterales=self.svg.unittouu(str(medida_pestana1) + self.options.unit) medida2_pestanas_laterales=self.svg.unittouu(str(medida_pestana2) + self.options.unit) medida3_pestanas_laterales=self.svg.unittouu(str(medida_pestana3) + self.options.unit) medida4_pestanas_laterales=self.svg.unittouu(str(medida_pestana4) + self.options.unit) id_caja = self.svg.get_unique_id('estuche-lineal') group = self.svg.get_current_layer().add(inkex.Group(id=id_caja)) estilo_linea_cortes = {'stroke': '#FF0000', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))} estilo_linea_hendidos = {'stroke': '#0000FF', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))} estilo_linea_medioscortes = {'stroke': '#00FFFF', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))} # line.path --> M = coordenadas absolutas # line.path --> l = dibuja una linea desde el punto actual a las coordenadas especificadas # line.path --> c = dibuja una curva beizer desde el punto actual a las coordenadas especificadas # line.path --> q = dibuja un arco desde el punto actual a las coordenadas especificadas usando un punto como referencia # line.path --> Z = cierra path #Perfil Exterior de la caja line = group.add(inkex.PathElement(id=id_caja + '-perfil-exterior')) line.path = [ ['M', [0, 0]], ['l', [ancho_caja, 0]], ['l', [0,0]], ['l', [0, 0]], ['l', [0, 0-medida1_pestanas_laterales]], ['l', [medida2_pestanas_laterales, 0-medida2_pestanas_laterales]], ['l', [medida3_pestanas_laterales, 0-(alto_pestana-medida2_pestanas_laterales-medida1_pestanas_laterales)]], ['l', [(largo_caja-medida2_pestanas_laterales-medida3_pestanas_laterales-medida4_pestanas_laterales), 0]], ['l', [0,alto_pestana-medida4_pestanas_laterales]], ['l', [medida4_pestanas_laterales, medida4_pestanas_laterales]], ['l', [0, 0-largo_caja]], ['l', [0, 0]], ['q', [0,0-alto_pestana_cierre,alto_pestana_cierre, 0-alto_pestana_cierre]], ['l', [ancho_caja-(alto_pestana_cierre*2), 0]], ['q', [alto_pestana_cierre,0,alto_pestana_cierre,alto_pestana_cierre]], ['l', [0, 0]], ['l', [0, (largo_caja)]], ['l', [medida4_pestanas_laterales, 0-medida4_pestanas_laterales]], ['l', [0,0-(alto_pestana-medida4_pestanas_laterales)]], ['l', [(largo_caja-medida2_pestanas_laterales-medida3_pestanas_laterales-medida4_pestanas_laterales), 0]], ['l', [medida3_pestanas_laterales, (alto_pestana-medida2_pestanas_laterales-medida1_pestanas_laterales)]], ['l', [medida2_pestanas_laterales, medida2_pestanas_laterales]], ['l', [0, medida1_pestanas_laterales]], ['l', [0,0]], ['l', [0, alto_caja]], ['l', [0-medida4_pestanas_laterales, medida4_pestanas_laterales]], ['l', [0,(alto_pestana-medida4_pestanas_laterales)]], ['l', [0-(largo_caja-medida2_pestanas_laterales-medida3_pestanas_laterales-medida4_pestanas_laterales), 0]], ['l', [0-(medida3_pestanas_laterales), 0-(alto_pestana-medida2_pestanas_laterales-medida1_pestanas_laterales)]], ['l', [0-(medida2_pestanas_laterales), 0-(medida2_pestanas_laterales)]], ['l', [0, 0-medida1_pestanas_laterales]], ['l', [0, 0]], ['l', [0,0]], ['l', [0-ancho_caja, 0]], ['l', [0,0]], ['l', [0, 0]], ['l', [0, medida1_pestanas_laterales]], ['l', [0-medida2_pestanas_laterales, medida2_pestanas_laterales]], ['l', [0-medida3_pestanas_laterales, (alto_pestana-medida2_pestanas_laterales-medida1_pestanas_laterales)]], ['l', [0-(largo_caja-medida2_pestanas_laterales-medida3_pestanas_laterales-medida4_pestanas_laterales), 0]], ['l', [0,0-(alto_pestana-medida4_pestanas_laterales)]], ['l', [0-medida4_pestanas_laterales, 0-medida4_pestanas_laterales]], ['l', [0,0]], ['l', [0, largo_caja]], ['l', [0, 0]], ['q', [0,alto_pestana_cierre,0-alto_pestana_cierre, alto_pestana_cierre]],# ['l', [0-(ancho_caja-(alto_pestana_cierre*2)), 0]], ['q', [0-alto_pestana_cierre,0,0-alto_pestana_cierre,0-alto_pestana_cierre]], ['l', [0, 0]], ['l', [0, 0-largo_caja]], ['l', [0, 0-medida2_pestanas_laterales]], ['l', [0-ancho_pestana_cola, 0-(ancho_pestana_cola/2)]], ['l', [0, 0-(alto_caja-ancho_pestana_cola-(medida2_pestanas_laterales*2))]], ['l', [ancho_pestana_cola, 0-(ancho_pestana_cola/2)]], ['Z', []] ] line.style = estilo_linea_cortes #Hendidos line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-1')) line.path = [ ['M', [0,0]], ['l', [0,alto_caja]] ] line.style = estilo_linea_hendidos line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-2')) line.path = [ ['M', [ancho_caja,0]], ['l', [0,alto_caja]] ] line.style = estilo_linea_hendidos line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-3')) line.path = [ ['M', [ancho_caja+largo_caja,0]], ['l', [0,alto_caja]] ] line.style = estilo_linea_hendidos line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-4')) line.path = [ ['M', [ancho_caja+ancho_caja+largo_caja,0]], ['l', [0,alto_caja]] ] line.style = estilo_linea_hendidos line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-5')) line.path = [ ['M', [ancho_caja,0]], ['l', [largo_caja,0]] ] line.style = estilo_linea_hendidos line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-6')) line.path = [ ['M', [ancho_caja+largo_caja,0]], ['l', [ancho_caja,0]] ] line.style = estilo_linea_hendidos line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-7')) line.path = [ ['M', [(ancho_caja*2)+largo_caja,0]], ['l', [largo_caja,0]] ] line.style = estilo_linea_hendidos line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-8')) line.path = [ ['M', [0,alto_caja]], ['l', [ancho_caja,0]] ] line.style = estilo_linea_hendidos line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-9')) line.path = [ ['M', [ancho_caja,alto_caja]], ['l', [largo_caja,0]] ] line.style = estilo_linea_hendidos line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-10')) line.path = [ ['M', [(ancho_caja*2)+largo_caja,alto_caja]], ['l', [largo_caja,0]] ] line.style = estilo_linea_hendidos line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-11')) line.path = [ ['M', [ancho_caja+largo_caja,0-(largo_caja)]], ['l', [ancho_caja,0]] ] line.style = estilo_linea_hendidos line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-12')) line.path = [ ['M', [0,alto_caja+largo_caja]], ['l', [ancho_caja,0]] ] line.style = estilo_linea_hendidos
def create_month(self, m): txt_atts = { 'transform': 'translate(' + str(self.year_margin + (self.month_w + self.month_margin) * self.month_x_pos) + ',' + str((self.day_h * 4) + (self.month_h * self.month_y_pos)) + ')', 'id': 'month_' + str(m) + '_' + str(self.options.year) } g = self.year_g.add(inkex.Group(**txt_atts)) self.write_month_header(g, m) gdays = g.add(inkex.Group()) cal = calendar.monthcalendar(self.options.year, m) if m == 1: if self.options.year > 1: before_month = \ self.in_line_month(calendar.monthcalendar(self.options.year - 1, 12)) else: before_month = \ self.in_line_month(calendar.monthcalendar(self.options.year, m - 1)) if m == 12: next_month = \ self.in_line_month(calendar.monthcalendar(self.options.year + 1, 1)) else: next_month = \ self.in_line_month(calendar.monthcalendar(self.options.year, m + 1)) if len(cal) < 6: # add a line after the last week cal.append([0, 0, 0, 0, 0, 0, 0]) if len(cal) < 6: # add a line before the first week (Feb 2009) cal.reverse() cal.append([0, 0, 0, 0, 0, 0, 0]) cal.reverse() # How mutch before month days will be showed: bmd = cal[0].count(0) + cal[1].count(0) before = True week_y = 0 for week in cal: if (self.weeknr != 0 and ((self.options.start_day == 'mon' and week[0] != 0) or (self.options.start_day == 'sun' and week[1] != 0))) or \ (self.weeknr == 0 and ((self.options.start_day == 'mon' and week[3] > 0) or (self.options.start_day == 'sun' and week[4] > 0))): self.weeknr += 1 week_x = 0 if self.options.show_weeknr: # Remove leap week (starting previous year) and empty weeks if self.weeknr != 0 and not (week[0] == 0 and week[6] == 0): style = self.style_weeknr txt_atts = { 'style': str(inkex.Style(style)), 'x': str(self.day_w * week_x), 'y': str(self.day_h * (week_y + 2)) } gdays.add(TextElement(**txt_atts)).text = str(self.weeknr) week_x += 1 else: week_x += 1 for day in week: style = self.style_day if self.is_weekend(week_x - self.cols_before): style = self.style_weekend if day == 0: style = self.style_nmd txt_atts = { 'style': str(inkex.Style(style)), 'x': str(self.day_w * week_x), 'y': str(self.day_h * (week_y + 2)) } text = None if day == 0 and not self.options.fill_edb: pass # draw nothing elif day == 0: if before: text = str(before_month[-bmd]) bmd -= 1 else: text = str(next_month[bmd]) bmd += 1 else: text = str(day) before = False if text: gdays.add(TextElement(**txt_atts)).text = text week_x += 1 week_y += 1 self.month_x_pos += 1 if self.month_x_pos >= self.months_per_line: self.month_x_pos = 0 self.month_y_pos += 1
def createLinks(node): nodeParent = node.getparent() pathIsClosed = False path = node.path.to_arrays() #to_arrays() is deprecated. How to make more modern? if path[-1][0] == 'Z' or path[0][1] == path[-1][1]: #if first is last point the path is also closed. The "Z" command is not required pathIsClosed = True if self.options.path_types == 'open_paths' and pathIsClosed is True: return #skip this loop iteration elif self.options.path_types == 'closed_paths' and pathIsClosed is False: return #skip this loop iteration elif self.options.path_types == 'both': pass # if keeping is enabled we make of copy of the current node and insert it while modifying the original ones. We could also delete the original and modify a copy... if self.options.keep_selected is True: parent = node.getparent() idx = parent.index(node) copynode = copy.copy(node) parent.insert(idx, copynode) # we measure the length of the path to calculate the required dash configuration csp = node.path.transform(node.composed_transform()).to_superpath() slengths, stotal = csplength(csp) #get segment lengths and total length of path in document's internal unit if self.options.length_filter is True: if stotal < self.svg.unittouu(str(self.options.length_filter_value) + self.options.length_filter_unit): if self.options.show_info is True: self.msg("node " + node.get('id') + " is shorter than minimum allowed length of {:1.3f} {}. Path length is {:1.3f} {}".format(self.options.length_filter_value, self.options.length_filter_unit, stotal, self.options.creationunit)) return #skip this loop iteration if self.options.creationunit == "percent": length_link = (self.options.length_link / 100.0) * stotal else: length_link = self.svg.unittouu(str(self.options.length_link) + self.options.creationunit) dashes = [] #central dashes array if self.options.creationtype == "entered_values": dash_length = ((stotal - length_link * self.options.link_count) / self.options.link_count) - 2 * length_link * self.options.link_multiplicator dashes.append(dash_length) dashes.append(length_link) for i in range(0, self.options.link_multiplicator): dashes.append(length_link) #stroke (=gap) dashes.append(length_link) #gap if self.options.switch_pattern is True: dashes = dashes[::-1] #reverse the array #validate dashes. May not be negative (dash or gap cannot be longer than the path itself). Otherwise Inkscape will freeze forever. Reason: rendering issue if any(dash <= 0.0 for dash in dashes) == True: if self.options.show_info is True: self.msg("node " + node.get('id') + ": Error! Dash array may not contain negative numbers: " + ' '.join(format(dash, "1.3f") for dash in dashes) + ". Path skipped. Maybe it's too short. Adjust your link count, multiplicator and length accordingly, or set to unit '%'") return False if self.options.skip_errors is True else exit(1) if self.options.creationunit == "percent": stroke_dashoffset = self.options.link_offset / 100.0 else: stroke_dashoffset = self.svg.unittouu(str(self.options.link_offset) + self.options.creationunit) if self.options.switch_pattern is True: stroke_dashoffset = stroke_dashoffset + ((self.options.link_multiplicator * 2) + 1) * length_link if self.options.creationtype == "use_existing": if self.options.no_convert is True: if self.options.show_info is True: self.msg("node " + node.get('id') + ": Nothing to do. Please select another creation method or disable cosmetic style output paths.") return False if self.options.skip_errors is True else exit(1) stroke_dashoffset = 0 style = node.style if 'stroke-dashoffset' in style: stroke_dashoffset = style['stroke-dashoffset'] try: floats = [float(dash) for dash in re.findall(r"[+]?\d*\.\d+|\d+", style['stroke-dasharray'])] #allow only positive values if len(floats) > 0: dashes = floats #overwrite previously calculated values with custom input else: raise ValueError except: if self.options.show_info is True: self.msg("node " + node.get('id') + ": No dash style to continue with.") return False if self.options.skip_errors is True else exit(1) if self.options.creationtype == "custom_dashpattern": stroke_dashoffset = self.options.custom_dashoffset_value try: floats = [float(dash) for dash in re.findall(r"[+]?\d*\.\d+|\d+", self.options.custom_dasharray_value)] #allow only positive values if len(floats) > 0: dashes = floats #overwrite previously calculated values with custom input else: raise ValueError except: if self.options.show_info is True: self.msg("node " + node.get('id') + ": Error in custom dasharray string (might be empty or does not contain any numbers).") return False if self.options.skip_errors is True else exit(1) #assign stroke dasharray from entered values, existing style or custom dashpattern stroke_dasharray = ' '.join(format(dash, "1.3f") for dash in dashes) # check if the node has a style attribute. If not we create a blank one with a black stroke and without fill style = None default_fill = 'none' default_stroke_width = '1px' default_stroke = '#000000' if node.attrib.has_key('style'): style = node.get('style') if style.endswith(';') is False: style += ';' # if has style attribute and dasharray and/or dashoffset are present we modify it accordingly declarations = style.split(';') # parse the style content and check what we need to adjust for i, decl in enumerate(declarations): parts = decl.split(':', 2) if len(parts) == 2: (prop, val) = parts prop = prop.strip().lower() #if prop == 'fill': # declarations[i] = prop + ':{}'.format(default_fill) #if prop == 'stroke': # declarations[i] = prop + ':{}'.format(default_stroke) #if prop == 'stroke-width': # declarations[i] = prop + ':{}'.format(default_stroke_width) if prop == 'stroke-dasharray': #comma separated list of one or more float values declarations[i] = prop + ':{}'.format(stroke_dasharray) if prop == 'stroke-dashoffset': declarations[i] = prop + ':{}'.format(stroke_dashoffset) node.set('style', ';'.join(declarations)) #apply new style to node #if has style attribute but the style attribute does not contain fill, stroke, stroke-width, stroke-dasharray or stroke-dashoffset yet style = node.style if re.search('fill:(.*?)(;|$)', str(style)) is None: style += 'fill:{};'.format(default_fill) if re.search('(;|^)stroke:(.*?)(;|$)', str(style)) is None: #if "stroke" is None, add one. We need to distinguish because there's also attribute "-inkscape-stroke" that's why we check starting with ^ or ; style += 'stroke:{};'.format(default_stroke) if not 'stroke-width' in style: style += 'stroke-width:{};'.format(default_stroke_width) if not 'stroke-dasharray' in style: style += 'stroke-dasharray:{};'.format(stroke_dasharray) if not 'stroke-dashoffset' in style: style += 'stroke-dashoffset:{};'.format(stroke_dashoffset) node.set('style', style) else: style = 'fill:{};stroke:{};stroke-width:{};stroke-dasharray:{};stroke-dashoffset:{};'.format(default_fill, default_stroke, default_stroke_width, stroke_dasharray, stroke_dashoffset) node.set('style', style) # Print some info about values if self.options.show_info is True: self.msg("node " + node.get('id') + ":") if self.options.creationunit == "percent": self.msg(" * total path length = {:1.3f} {}".format(stotal, self.svg.unit)) #show length, converted in selected unit self.msg(" * (calculated) offset: {:1.3f} %".format(stroke_dashoffset)) if self.options.creationtype == "entered_values": self.msg(" * (calculated) gap length: {:1.3f} %".format(length_link)) else: self.msg(" * total path length = {:1.3f} {} ({:1.3f} {})".format(self.svg.uutounit(stotal, self.options.creationunit), self.options.creationunit, stotal, self.svg.unit)) #show length, converted in selected unit self.msg(" * (calculated) offset: {:1.3f} {}".format(self.svg.uutounit(stroke_dashoffset, self.options.creationunit), self.options.creationunit)) if self.options.creationtype == "entered_values": self.msg(" * (calculated) gap length: {:1.3f} {}".format(length_link, self.options.creationunit)) if self.options.creationtype == "entered_values": self.msg(" * total gaps = {}".format(self.options.link_count)) self.msg(" * (calculated) dash/gap pattern: {} ({})".format(stroke_dasharray, self.svg.unit)) # Conversion step (split cosmetic path into real segments) if self.options.no_convert is False: style = node.style #get the style again, but this time as style class new = [] for sub in node.path.to_superpath(): idash = 0 dash = dashes[0] length = float(stroke_dashoffset) while dash < length: length = length - dash idash = (idash + 1) % len(dashes) dash = dashes[idash] new.append([sub[0][:]]) i = 1 while i < len(sub): dash = dash - length length = bezier.cspseglength(new[-1][-1], sub[i]) while dash < length: new[-1][-1], nxt, sub[i] = bezier.cspbezsplitatlength(new[-1][-1], sub[i], dash/length) if idash % 2: # create a gap new.append([nxt[:]]) else: # splice the curve new[-1].append(nxt[:]) length = length - dash idash = (idash + 1) % len(dashes) dash = dashes[idash] if idash % 2: new.append([sub[i]]) else: new[-1].append(sub[i]) i += 1 style.pop('stroke-dasharray') node.pop('sodipodi:type') csp = CubicSuperPath(new) node.path = CubicSuperPath(new) node.style = style # break apart the combined path to have multiple elements if self.options.breakapart is True: breakOutputNodes = None breakOutputNodes = self.breakContours(node, breakOutputNodes) breakApartGroup = nodeParent.add(inkex.Group()) for breakOutputNode in breakOutputNodes: breakApartGroup.append(breakOutputNode) #self.msg(replacedNode.get('id')) #self.svg.selection.set(replacedNode.get('id')) #update selection to split paths segments (does not work, so commented out) #cleanup useless points p = breakOutputNode.path commandsCoords = p.to_arrays() # "m 45.250809,91.692739" - this path contains onyl one command - a single point if len(commandsCoords) == 1: breakOutputNode.delete() # "m 45.250809,91.692739 z" - this path contains two commands, but only one coordinate. # It's a single point, the path is closed by a Z command elif len(commandsCoords) == 2 and commandsCoords[0][1] == commandsCoords[1][1]: breakOutputNode.delete() # "m 45.250809,91.692739 l 45.250809,91.692739" - this path contains two commands, # but the first and second coordinate are the same. It will render als point elif len(commandsCoords) == 2 and commandsCoords[-1][0] == 'Z': breakOutputNode.delete() # "m 45.250809,91.692739 l 45.250809,91.692739 z" - this path contains three commands, # but the first and second coordinate are the same. It will render als point, the path is closed by a Z command elif len(commandsCoords) == 3 and commandsCoords[0][1] == commandsCoords[1][1] and commandsCoords[2][1] == 'Z': breakOutputNode.delete()
def effect(self): # internal overwrite for scale: self.options.scale = 1.0 if (self.options.ids): for node in self.svg.selected.values(): if node.tag == inkex.addNS('image', 'svg'): self.path = self.checkImagePath( node) # This also ensures the file exists if self.path is None: # check if image is embedded or linked image_string = node.get( '{http://www.w3.org/1999/xlink}href') # find comma position i = 0 while i < 40: if image_string[i] == ',': break i = i + 1 image = Image.open( BytesIO( base64.b64decode( image_string[i + 1:len(image_string)]))) else: image = Image.open(self.path) # Write the embedded or linked image to temporary directory if os.name == "nt": exportfile = "Primitive.png" else: exportfile = "/tmp/Primitive.png" image.save(exportfile, "png") ## Build up Primitive command according to your settings from extension GUI if os.name == "nt": command = "primitive" else: command = "./primitive" command += " -m " + str(self.options.m) command += " -rep " + str(self.options.rep) command += " -r " + str(self.options.r) command += " -s " + str(self.options.s) command += " -a " + str(self.options.a) if not self.options.bg_enabled: command += " -bg " + self.rgbToHex(self.options.bg) command += " -j " + str(self.options.j) command += " -i " + exportfile command += " -o " + exportfile + ".svg" command += " -n " + str(self.options.n) #inkex.utils.debug(command) # Create the vector new SVG file with os.popen(command, "r") as proc: result = proc.read() #inkex.utils.debug(result) # proceed if new SVG file was successfully created doc = None if os.path.exists(exportfile + ".svg"): # Delete the temporary png file again because we do not need it anymore if os.path.exists(exportfile): os.remove(exportfile) # new parse the SVG file and insert it as new group into the current document tree doc = etree.parse(exportfile + ".svg").getroot() newGroup = self.document.getroot().add(inkex.Group()) newGroup.attrib['transform'] = "matrix(" + \ str(float(node.get('width')) / float(doc.get('width'))) + \ ", 0, 0 , " + \ str(float(node.get('height')) / float(doc.get('height'))) + \ "," + node.get('x') + \ "," + node.get('y') + ")" newGroup.append(doc) # Delete the temporary svg file if os.path.exists(exportfile + ".svg"): try: os.remove(exportfile + ".svg") except: pass else: inkex.utils.debug( "Error while creating output file! :-( The \"primitive\" executable seems to be missing, has no exec permissions or platform is imcompatible." ) exit(1) #remove the old image or not if self.options.keeporiginal is not True: node.delete() # create clip path to remove the stuffy surroundings if self.options.cliprect: path = '//svg:defs' defslist = self.document.getroot().xpath( path, namespaces=inkex.NSS) if len(defslist) > 0: defs = defslist[0] clipPathData = { inkex.addNS('label', 'inkscape'): 'imagetracerClipPath', 'clipPathUnits': 'userSpaceOnUse', 'id': 'imagetracerClipPath' } clipPath = etree.SubElement(defs, 'clipPath', clipPathData) #inkex.utils.debug(image.width) clipBox = { 'x': str(0), 'y': str(0), 'width': str(doc.get('width')), 'height': str(doc.get('height')), 'style': 'fill:#000000; stroke:none; fill-opacity:1;' } etree.SubElement(clipPath, 'rect', clipBox) #etree.SubElement(newGroup, 'g', {inkex.addNS('label','inkscape'):'imagetracerjs', 'clip-path':"url(#imagetracerClipPath)"}) newGroup.getchildren()[0].set( 'clip-path', 'url(#imagetracerClipPath)') else: inkex.utils.debug( "No image found for tracing. Please select an image first.")
def generate(self): size = self.svg.unittouu(str(self.options.size) + 'px') head = self.svg.unittouu(str(self.options.head) + 'px') width = self.svg.unittouu(str(self.options.width) + 'pt') medium = self.svg.unittouu('10pt') # font sizes, ... large = self.svg.unittouu('12pt') # ... could be options tsector, survey_title = analyse3d(self.options.file, self.options.nsector) tmax = max(tsector) thoriz = 0.5 * sum(tsector) # each leg is counted twice ns = len(tsector) tmean = 2 * thoriz / ns # Inner group to contain all the rose diagram elements rose_diagram = inkex.Group() elements = [''] * (2*ns) # list of lines and arc elements for i, v in enumerate(tsector): alo = 2 * m.pi * (i - 0.5) / ns ahi = 2 * m.pi * (i + 0.5) / ns rad = size * tsector[i] / tmax xx0 = rad * m.sin(alo) yy0 = - rad * m.cos(alo) xx1 = rad * m.sin(ahi) yy1 = - rad * m.cos(ahi) elements[2*i] = ('L' if i else 'M') + '%7.2f, %7.2f ' % (xx0, yy0) elements[2*i+1] = 'A %6.2f, %6.2f 0 0,1 %7.2f, %7.2f' % (rad, rad, xx1, yy1) # Add the lines and arc elements - 'z' closes the path zigzag = inkex.PathElement(d=' '.join(elements) + ' z') zigzag.style = {'stroke': 'black', 'fill': 'none' if self.options.bw else 'yellow', 'stroke-width': width} rose_diagram.add(zigzag) # Adjust the scale circle radius, rounding down if necessary scale_rad = fancy_round(tmean) if scale_rad > tmax: scale_rad = fancy_round(tmean, offset=0.0) # Add the scale circle circle = inkex.Circle(r=str(size * scale_rad / tmax)) circle.style = {'stroke': 'black' if self.options.bw else 'blue', 'fill': 'none', 'stroke-width': width} if self.options.bw: circle.style['stroke-dasharray'] = '{step} {step}'.format(step=4*width) rose_diagram.add(circle) # Add N-S, E-W, NW-SE, NE-SW lines; the N has a half-arrow style = {'stroke': 'black', 'fill': 'none', 'stroke-width': width} style = str(inkex.Style(style)) length = 1.05 * size rose_diagram.add(inkex.PathElement(d=f'M0,{-length+2*head} L{-head},{-length+2*head} L0,-{length} L0,{length}', style=style)) rose_diagram.add(inkex.PathElement(d=f'M-{length},0 L{length},0', style=style)) length = m.sqrt(0.5) * 1.05 * size rose_diagram.add(inkex.PathElement(d=f'M-{length},-{length} L{length},{length}', style=style)) rose_diagram.add(inkex.PathElement(d=f'M{length},-{length} L-{length},{length}', style=style)) # Add a 'N' to the north arrow north = inkex.TextElement(x=str(head), y=str(-1.05*size+head)) north.style = {'font-family': 'Verdana', 'font-size': large, 'fill': 'black', 'text-anchor': 'middle', 'text-align': 'center'} north.text = 'N' rose_diagram.add(north) yield rose_diagram # Add annotation for cave length and circle radius annotation = inkex.TextElement(x=str(0), y=str(1.3*size)) annotation.style = {'font-family': 'Verdana', 'font-size': medium, 'fill': 'black', 'text-anchor': 'middle', 'text-align': 'center'} cave_length = f'{round(thoriz/1000, 1)} km' if thoriz > 10000 else f'{round(thoriz)} m' circle_radius = f'{scale_rad/1000} km' if scale_rad > 1000 else f'{scale_rad} m' annotation.text = f'length {cave_length}, circle radius is {circle_radius}' yield annotation # Add a title title = inkex.TextElement(x=str(0), y=str(-1.3*size)) title.style = {'font-family': 'Verdana', 'font-size': large, 'fill': 'black', 'text-anchor': 'middle', 'text-align': 'center'} title.text = self.options.title or survey_title yield title