def test_viewer_uninitialized(assert_image_similarity): """An uninitialized engine should not crash""" engine = Engine() engine.scale = 3.0 engine.origin = (10.0, 20.0) engine.view_mode = ViewMode.OUTLINE_COLORFUL engine.show_pen_up = True engine.show_points = True engine.pen_width = 0.5 engine.pen_opacity = 0.5 engine.debug = True engine.show_rulers = True engine.pixel_factor = 2.0 engine.unit_type = UnitType.IMPERIAL engine.zoom(2, 10, 10) engine.pan(50, 40) engine.render() engine.viewport_to_model(30, 30) engine.toggle_layer_visibility(10) engine.fit_to_viewport() # ensure 100% coverage doc = vp.Document() engine.document = doc engine.fit_to_viewport()
def test_viewer_engine_properties(assert_image_similarity): renderer = ImageRenderer((640, 480)) doc = vp.Document() renderer.engine.document = doc assert renderer.engine.document is doc renderer.engine.scale = 3.0 assert renderer.engine.scale == 3.0 renderer.engine.origin = (10.0, 20.0) assert renderer.engine.origin == (10.0, 20.0) renderer.engine.view_mode = ViewMode.OUTLINE_COLORFUL assert renderer.engine.view_mode == ViewMode.OUTLINE_COLORFUL renderer.engine.show_pen_up = True assert renderer.engine.show_pen_up renderer.engine.show_points = True assert renderer.engine.show_points renderer.engine.pen_width = 0.5 assert renderer.engine.pen_width == 0.5 renderer.engine.pen_opacity = 0.5 assert renderer.engine.pen_opacity == 0.5 renderer.engine.debug = True assert renderer.engine.debug renderer.engine.toggle_layer_visibility(10) assert not renderer.engine.layer_visible(10)
def run(args): saver = ReproSaver() saver.seed() R = 150 phi = 1.6180339 smaller = R / phi # golden ratio pts = [] pts.extend(circle_connections(1200, R, np.pi, -np.pi)) rays = draw_rays(0, 7 * R * phi, 0, np.pi, 30) rays.extend(draw_rays(2.5 * R, 7 * R * phi, np.pi / 60, np.pi - np.pi / 60, 29)) rays = shift(rays, np.array([1.7 * R, 0])) rays = mask_drawing(rays, circle((0, 0), 1.05 * R, N=50), invert=True) pts.extend(rays) # pts = mask_drawing(pts, circle((0, 0), R, N=50)) # pts.extend(circle_connections(230, R, np.pi * 0.3, 0)) # pts.extend(circle_connections(530, R, np.pi * 0.5, 0)) # pts.extend(circle_connections(650, R, np.pi * 0.65, np.pi * 0.50)) # pts.extend(circle_connections(700, R, np.pi * 0.95, np.pi * 0.72)) # for i in range(1, 18): # smooth_circle = circle_connections(360, R * 1.02**i, np.pi / 180, np.pi / 180) # pts.extend(drop_parts(smooth_circle, space=1, passes=4)) lines = to_vpype(pts) lines.crop(-2.3 * R - smaller, 2.3 * R + smaller, R + smaller, -R - smaller) # left, bottom, right, top pts = from_vpype_lines(lines) if not args.nosave: saver.add_svg(pts) document = vpype.Document(lines) # document.extend_page_size(None) with open('/tmp/signal_planet.svg', 'w') as fout: vpype.write_svg(fout, document) vpype_viewer.show(document) return 0
def run(args): saver = ReproSaver('star_results') saver.seed() if args.rnd: description = random_star() print(f"description: {description}") paths = construct_star(description) else: N_star = 12 R = 600 paths = [] start_fractions = np.linspace(0.4, 1, 25) end_fractions = reversed(start_fractions) for start_fraction, end_fraction in zip(start_fractions, end_fractions): start_r = R * start_fraction end_r = R * end_fraction paths.extend(star_line(N_star, start_r, end_r, 1)) # vlastovka start_fractions = np.geomspace(0.4, 0.65, 20) end_fractions = reversed(np.geomspace(0.3, 0.9, 20)) for start_fraction, end_fraction in zip(start_fractions, end_fractions): start_r = R * start_fraction end_r = R * end_fraction paths.extend(star_line(N_star, start_r, end_r, 1)) # stred start_fractions = rev_geomspace(0.1, 1, 30) end_fractions = start_fractions # end_fractions = np.linspace(0.1, 1, 30) for start_fraction, end_fraction in zip(start_fractions, end_fractions): start_r = R * start_fraction end_r = R * end_fraction paths.extend(star_line(N_star, start_r, end_r, 5, mirror=False)) # stred 2 start_fractions = rev_geomspace(0.13, 0.7, 20) end_fractions = start_fractions # end_fractions = np.linspace(0.1, 1, 30) for start_fraction, end_fraction in zip(start_fractions, end_fractions): start_r = R * start_fraction end_r = R * end_fraction paths.extend(star_line(N_star, start_r, end_r, 5, mirror=False)) paths.extend(star_line(N_star, R, R, 6, mirror=False)) lines = to_vpype(paths) if not args.nosave: saver.add_svg(paths) document = vpype.Document(lines) with open('/tmp/stars.svg', 'w') as fout: vpype.write_svg(fout, document) if not args.novis: vpype_viewer.show(document) return 0
def big_doc(): random.seed(0) doc = vp.Document() doc.add( vp.LineCollection([( random.uniform(0, 100) + random.uniform(0, 100) * 1j, random.uniform(0, 100) + random.uniform(0, 100) * 1j, ) for _ in range(1000)])) return doc
def execute_single_line(pipeline: str, line: vp.LineLike) -> vp.LineCollection: """Execute a pipeline on a single line. The pipeline is expected to remain single layer. Returns: the layer 1's LineCollection """ doc = execute(pipeline, vp.Document(vp.LineCollection([line]))) assert len(doc.layers) == 1 return doc.layers[1]
def execute(pipeline: str, document: Optional[vp.Document] = None) -> vp.Document: """Execute a vpype pipeline. This function serves as a Python API to vpype's pipeline. It can be used from a regular Python script (as opposed to the ``vpype`` CLI which must be used from a console or via :func:`os.system`. If a :class:`vpype.Document` instance is provided, it will be preloaded in the pipeline before the first command executes. The pipeline's content after the last command is returned as a :class:`vpype.Document` instance. Examples: Read a SVG file, optimize it and return the result as a :class:`vpype.Document` instance:: >>> doc = execute("read input.svg linemerge linesimplify linesort") Optimize and save a :class:`vpype.Document` instance:: >>> doc = vp.Document() >>> # populate `doc` with some graphics >>> execute("linemerge linesimplify linesort write output.svg", doc) Args: pipeline: vpype pipeline as would be used with ``vpype`` CLI document: if provided, is perloaded in the pipeline before the first command executes Returns: pipeline's content after the last command executes """ if document: @cli.command() @vp.global_processor def vsketchinput(doc): doc.extend(document) return doc out_doc = vp.Document() @cli.command() @vp.global_processor def vsketchoutput(doc): out_doc.extend(doc) return doc args = ("vsketchinput " if document else "") + pipeline + " vsketchoutput" cli.main(prog_name="vpype", args=shlex.split(args), standalone_mode=False) return out_doc
def test_hpgl_flex_no_pagesize(simple_printer_config): doc = vp.Document() doc.add(vp.LineCollection([(1 + 1j, 2 + 4j)])) vp.config_manager.load_config_file(simple_printer_config) with pytest.raises(ValueError): vp.write_hpgl( output=sys.stdout, document=doc, landscape=False, center=False, device="simple", page_size="simple_flex_portrait", velocity=None, )
def test_layout(runner, args, expected_bounds): document = vp.Document() @cli.command() @vp.global_processor def sample(doc: vp.Document): nonlocal document document = doc res = runner.invoke(cli, f"random -n 100 rect 0 0 1cm 1cm layout {args} sample") assert res.exit_code == 0 bounds = document.bounds() assert bounds is not None for act, exp in zip(bounds, expected_bounds): assert act == pytest.approx(exp * CM)
def test_hpgl_wrong_ref(simple_printer_config): doc = vp.Document() doc.add(vp.LineCollection([(1 + 1j, 2 + 4j)])) doc.page_size = 10, 15 vp.config_manager.load_config_file(simple_printer_config) with pytest.raises(ValueError): vp.write_hpgl( output=sys.stdout, document=doc, landscape=False, center=False, device="defective", page_size="wrong_ref", velocity=None, )
def test_viewer_engine_properties(assert_image_similarity): renderer = ImageRenderer((640, 480)) doc = vp.Document() renderer.engine.document = doc assert renderer.engine.document is doc renderer.engine.scale = 3.0 assert renderer.engine.scale == 3.0 renderer.engine.origin = (10.0, 20.0) assert renderer.engine.origin == (10.0, 20.0) renderer.engine.view_mode = ViewMode.OUTLINE_COLORFUL assert renderer.engine.view_mode == ViewMode.OUTLINE_COLORFUL renderer.engine.show_pen_up = True assert renderer.engine.show_pen_up renderer.engine.show_points = True assert renderer.engine.show_points renderer.engine.pen_width = 0.5 assert renderer.engine.pen_width == 0.5 renderer.engine.pen_opacity = 0.5 assert renderer.engine.pen_opacity == 0.5 renderer.engine.debug = True assert renderer.engine.debug renderer.engine.unit_type = UnitType.IMPERIAL assert renderer.engine.unit_type == UnitType.IMPERIAL renderer.engine.pixel_factor = 2.0 assert renderer.engine.pixel_factor == 2.0 renderer.engine.show_rulers = False assert not renderer.engine.show_rulers assert renderer.engine.scale_spec == DEFAULT_SCALE_SPEC renderer.engine.toggle_layer_visibility(10) assert not renderer.engine.layer_visible(10)
def run(args): R = 150 smaller = R / 1.6180339 # golden ratio pts = [] pts.extend(circle_connections(230, R, np.pi * 0.3, 0)) pts.extend(circle_connections(530, R, np.pi * 0.5, 0)) pts.extend(circle_connections(650, R, np.pi * 0.65, np.pi * 0.50)) pts.extend(circle_connections(700, R, np.pi * 0.95, np.pi * 0.72)) for i in range(1, 18): smooth_circle = circle_connections(360, R * 1.02**i, np.pi / 180, np.pi / 180) pts.extend(drop_parts(smooth_circle, space=1, passes=4)) lines = to_vpype(pts) lines.crop(R - smaller, 0, R, -R) document = vpype.Document(lines) # document.extend_page_size(None) with open('/tmp/circles.svg', 'w') as fout: vpype.write_svg(fout, document) vpype_viewer.show(document) return 0
def test_text_block_render(assert_image_similarity, font_name, align, line_spacing, justify): doc = vp.Document() doc.add( vp.text_block( LOREM, 500, font_name, size=18, align=align, line_spacing=line_spacing, justify=justify, )) doc[1].append(vp.line(500, -20, 500, 500)) renderer = ImageRenderer((1024, 1024)) renderer.engine.document = doc renderer.engine.show_rulers = True renderer.engine.unit_type = UnitType.PIXELS renderer.engine.origin = (-20, -20) renderer.engine.scale = 1.8 assert_image_similarity(renderer.render())
def run(args): saver = ReproSaver() saver.seed() if args.cfg is not None: saver.load_seeds(args.cfg) H, W = 210, 148 angle_range = 3 horizontal_lines = [] for i in range(1000): horizontal_lines.append( random_line(H, W, min_angle=np.radians(0 - angle_range / 2), max_angle=np.radians(0 + angle_range / 2))) vertical_lines = [] for i in range(1000): vertical_lines.append( random_line(H, W, min_angle=np.radians(90 - angle_range / 2), max_angle=np.radians(90 + angle_range / 2))) cross = draw_cross(H, W) horizontal, vertical = draw_cross_parts(H, W) margin = 10 (horizontal, vertical, cross) = resize_and_center([horizontal, vertical, cross], H, W, margin, margin, margin, margin) horizontal_lines = mask_drawing(horizontal_lines, horizontal) vertical_lines = mask_drawing(vertical_lines, vertical) horizontal_lines = jitter_length(horizontal_lines, 0, 5) vertical_lines = jitter_length(vertical_lines, 0, 5) vertical_lines = mask_drawing(vertical_lines, horizontal, invert=True) cross_lines = horizontal_lines cross_lines.extend(vertical_lines) cross_lines.extend(vertical_lines) # cross_lines.append(cross) bg_lines = [] for i in range(300): bg_lines.append(random_line(0.2 * H, W)) bg_lines = shift(bg_lines, (0, 0.8 * H)) bg_lines = mask_drawing(bg_lines, cross, invert=True) pts = [] pts.extend(cross_lines) pts = resize_and_center(pts, H, W, margin, margin, margin, margin) # pts.extend(bg_lines) lines = to_vpype(pts) document = vpype.Document(lines) vpype_viewer.show(document) if not args.nosave: saver.add_svg(pts) return 0
def effect(self): lc = vpype.LineCollection() # create a new array of LineStrings consisting of Points. We convert selected paths to polylines and grab their points elementsToWork = [] # we make an array of all collected nodes to get the boundingbox of that array. We need it to place the vpype converted stuff to the correct XY coordinates applyTransformAvailable = False # at first we apply external extension try: sys.path.append("..") # add parent directory to path to allow importing applytransform (vpype extension is encapsulated in sub directory) import applytransform applyTransformAvailable = True except Exception as e: # inkex.utils.debug(e) inkex.utils.debug("Calling 'Apply Transformations' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery. Skipping this step") def flatten(node): path = node.path.to_superpath() bezier.cspsubdiv(path, self.options.flatness) newpath = [] for subpath in path: first = True for csp in subpath: cmd = 'L' if first: cmd = 'M' first = False newpath.append([cmd, [csp[1][0], csp[1][1]]]) node.path = newpath # flatten the node's path to linearize, split up the path to it's subpaths (break apart) and add all points to the vpype lines collection def convertPath(node, nodes = None): if nodes is None: nodes = [] if node.tag == inkex.addNS('path','svg'): nodes.append(node) if self.options.flattenbezier is True: flatten(node) raw = node.path.to_arrays() subPaths, prev = [], 0 for i in range(len(raw)): # Breaks compound paths into simple paths if raw[i][0] == 'M' and i != 0: subPaths.append(raw[prev:i]) prev = i subPaths.append(raw[prev:]) for subPath in subPaths: points = [] for csp in subPath: if len(csp[1]) > 0: #we need exactly two points per straight line segment points.append(Point(round(csp[1][0], self.options.decimals), round(csp[1][1], self.options.decimals))) if subPath[-1][0] == 'Z' or subPath[0][1] == subPath[-1][1]: #check if path is closed by Z or first pont == last point points.append(Point(round(subPath[0][1][0], self.options.decimals), round(subPath[0][1][1], self.options.decimals))) #if closed, we add the first point again lc.append(LineString(points)) children = node.getchildren() if children is not None: for child in children: convertPath(child, nodes) return nodes doc = None #create a vpype document ''' if 'paths' we process paths only. Objects like rectangles or strokes like polygon have to be converted before accessing them if 'layers' we can process all layers in the complete document ''' if self.options.input_handling == "paths": # getting the bounding box of the current selection. We use to calculate the offset XY from top-left corner of the canvas. This helps us placing back the elements input_bbox = None if self.options.apply_transformations is True and applyTransformAvailable is True: ''' we need to apply transfoms to the complete document even if there are only some single paths selected. If we apply it to selected nodes only the parent groups still might contain transforms. This messes with the coordinates and creates hardly controllable behaviour ''' applytransform.ApplyTransform().recursiveFuseTransform(self.document.getroot()) if len(self.svg.selected) == 0: elementsToWork = convertPath(self.document.getroot()) for element in elementsToWork: input_bbox += element.bounding_box() else: elementsToWork = None for element in self.svg.selected.values(): elementsToWork = convertPath(element, elementsToWork) #input_bbox = inkex.elements._selected.ElementList.bounding_box(self.svg.selected) # get BoundingBox for selection input_bbox = self.svg.selection.bounding_box() # get BoundingBox for selection if len(lc) == 0: inkex.errormsg('Selection appears to be empty or does not contain any valid svg:path nodes. Try to cast your objects to paths using CTRL + SHIFT + C or strokes to paths using CTRL + ALT+ C') return # find the first object in selection which has a style attribute (skips groups and other things which have no style) firstElementStyle = None for element in elementsToWork: if element.attrib.has_key('style'): firstElementStyle = element.get('style') doc = vpype.Document(page_size=(input_bbox.width + input_bbox.left, input_bbox.height + input_bbox.top)) #create new vpype document doc.add(lc, layer_id=None) # we add the lineCollection (converted selection) to the vpype document elif self.options.input_handling == "layers": doc = vpype.read_multilayer_svg(self.options.input_file, quantization = self.options.flatness, crop = False, simplify = self.options.simplify, parallel = self.options.parallel, default_width = self.document.getroot().get('width'), default_height = self.document.getroot().get('height')) for element in self.document.getroot().xpath("//svg:g", namespaces=inkex.NSS): #all groups/layers elementsToWork.append(element) tooling_length_before = doc.length() traveling_length_before = doc.pen_up_length() # build and execute the conversion command # the following code block is not intended to sum up the commands to build a series (pipe) of commands! ########################################## # Line Sorting if self.options.linesort is True: command = "linesort " if self.options.linesort_no_flip is True: command += " --no-flip" # Line Merging if self.options.linemerge is True: command = "linemerge --tolerance " + str(self.options.linemerge_tolerance) if self.options.linemerge_no_flip is True: command += " --no-flip" # Trimming if self.options.trim is True: command = "trim " + str(self.options.trim_x_margin) + " " + str(self.options.trim_y_margin) # Relooping if self.options.reloop is True: command = "reloop --tolerance " + str(self.options.reloop_tolerance) # Multipass if self.options.multipass is True: command = "multipass --count " + str(self.options.multipass_count) # Filter if self.options.filter is True: command = "filter --tolerance " + str(self.options.filter_tolerance) if self.options.filter_min_length_enabled is True: command += " --min-length " + str(self.options.filter_min_length) if self.options.filter_max_length_enabled is True: command += " --max-length " + str(self.options.filter_max_length) if self.options.filter_closed is True and self.options.filter_not_closed is False: command += " --closed" if self.options.filter_not_closed is True and self.options.filter_closed is False: command += " --not-closed" if self.options.filter_closed is False and \ self.options.filter_not_closed is False and \ self.options.filter_min_length_enabled is False and \ self.options.filter_max_length_enabled is False: inkex.errormsg('No filters to apply. Please select at least one filter.') return # Plugin Occult if self.options.plugin_occult is True: command = "occult --tolerance " + str(self.options.plugin_occult_tolerance) if self.options.plugin_occult_keepseparatelayer is True: command += " --keep-occulted" # Split All if self.options.splitall is True: command = " splitall" # Free Mode if self.options.freemode is True: command = "" if self.options.freemode_cmd1_enabled is True: command += " " + self.options.freemode_cmd1.strip() if self.options.freemode_cmd2_enabled is True: command += " " + self.options.freemode_cmd2.strip() if self.options.freemode_cmd3_enabled is True: command += " " + self.options.freemode_cmd3.strip() if self.options.freemode_cmd4_enabled is True: command += " " + self.options.freemode_cmd4.strip() if self.options.freemode_cmd5_enabled is True: command += " " + self.options.freemode_cmd5.strip() if self.options.freemode_cmd1_enabled is False and \ self.options.freemode_cmd2_enabled is False and \ self.options.freemode_cmd3_enabled is False and \ self.options.freemode_cmd4_enabled is False and \ self.options.freemode_cmd5_enabled is False: inkex.utils.debug("Warning: empty vpype pipeline. With this you are just getting read-write layerset/lineset.") else: if self.options.freemode_show_cmd is True: inkex.utils.debug("Your command pipe will be the following:") inkex.utils.debug(command) # inkex.utils.debug(command) try: doc = execute(command, doc) except Exception as e: inkex.utils.debug("Error in vpype:" + str(e)) return ########################################## tooling_length_after = doc.length() traveling_length_after = doc.pen_up_length() if tooling_length_before > 0: tooling_length_saving = (1.0 - tooling_length_after / tooling_length_before) * 100.0 else: tooling_length_saving = 0.0 if traveling_length_before > 0: traveling_length_saving = (1.0 - traveling_length_after / traveling_length_before) * 100.0 else: traveling_length_saving = 0.0 if self.options.output_stats is True: inkex.utils.debug('Total tooling length before vpype conversion: ' + str('{:0.2f}'.format(tooling_length_before)) + ' mm') inkex.utils.debug('Total traveling length before vpype conversion: ' + str('{:0.2f}'.format(traveling_length_before)) + ' mm') inkex.utils.debug('Total tooling length after vpype conversion: ' + str('{:0.2f}'.format(tooling_length_after)) + ' mm') inkex.utils.debug('Total traveling length after vpype conversion: ' + str('{:0.2f}'.format(traveling_length_after)) + ' mm') inkex.utils.debug('Total tooling length optimized: ' + str('{:0.2f}'.format(tooling_length_saving)) + ' %') inkex.utils.debug('Total traveling length optimized: ' + str('{:0.2f}'.format(traveling_length_saving)) + ' %') if tooling_length_after == 0: inkex.errormsg('No lines left after vpype conversion. Conversion result is empty. Cannot continue') return # show the vpype document visually if self.options.output_show: warnings.filterwarnings("ignore") # workaround to suppress annoying DeprecationWarning # vpype_viewer.show(doc, view_mode=ViewMode.PREVIEW, show_pen_up=self.options.output_trajectories, show_points=self.options.output_show_points, pen_width=0.1, pen_opacity=1.0, argv=None) vpype_viewer.show(doc, view_mode=ViewMode.PREVIEW, show_pen_up=self.options.output_trajectories, show_points=self.options.output_show_points, argv=None) # https://vpype.readthedocs.io/en/stable/api/vpype_viewer.ViewMode.html warnings.filterwarnings("default") # reset warning filter exit(0) #we leave the code loop because we only want to preview. We don't want to import the geometry # save the vpype document to new svg file and close it afterwards output_file = self.options.input_file + ".vpype.svg" output_fileIO = open(output_file, "w", encoding="utf-8") #vpype.write_svg(output_fileIO, doc, page_size=None, center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer', single_path = True) vpype.write_svg(output_fileIO, doc, page_size=None, center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer') #vpype.write_svg(output_fileIO, doc, page_size=(self.svg.unittouu(self.document.getroot().get('width')), self.svg.unittouu(self.document.getroot().get('height'))), center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer') output_fileIO.close() # convert vpype polylines/lines/polygons to regular paths again. We need to use "--with-gui" to respond to "WARNING: ignoring verb FileSave - GUI required for this verb." if self.options.strokes_to_paths is True: cli_output = inkscape(output_file, "--with-gui", actions="EditSelectAllInAllLayers;EditUnlinkClone;ObjectToPath;FileSave;FileQuit") if len(cli_output) > 0: self.debug(_("Inkscape returned the following output when trying to run the vpype object to path back-conversion:")) self.debug(cli_output) # this does not work because line, polyline and polygon have no base class to execute replace_with #if self.options.strokes_to_paths is True: # for lineLayer in lineLayers: # for element in lineLayer: # element.replace_with(element.to_path_element()) # parse the SVG file try: stream = open(output_file, 'r') except FileNotFoundError as e: inkex.utils.debug("There was no SVG output generated by vpype. Cannot continue") exit(1) p = etree.XMLParser(huge_tree=True) import_doc = etree.parse(stream, parser=etree.XMLParser(huge_tree=True)) stream.close() # handle pen_up trajectories (travel lines) trajectoriesLayer = import_doc.getroot().xpath("//svg:g[@id='pen_up_trajectories']", namespaces=inkex.NSS) if self.options.output_trajectories is True: if len(trajectoriesLayer) > 0: trajectoriesLayer[0].set('style', 'stroke:#0000ff;stroke-width:{:0.2f}px;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill:none'.format(self.options.trajectories_stroke_width)) trajectoriesLayer[0].attrib.pop('stroke') # remove unneccesary stroke attribute trajectoriesLayer[0].attrib.pop('fill') # remove unneccesary fill attribute else: if len(trajectoriesLayer) > 0: trajectoriesLayer[0].delete() lineLayers = import_doc.getroot().xpath("//svg:g[not(@id='pen_up_trajectories')]", namespaces=inkex.NSS) #all layer except the pen_up trajectories layer if self.options.use_style_of_first_element is True and self.options.input_handling == "paths" and firstElementStyle is not None: # if we remove the fill property and use "Use style of first element in layer" the conversion will just crash with an unknown reason #declarations = firstElementStyle.split(';') #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 + ':none' for lineLayer in lineLayers: #lineLayer.set('style', ';'.join(declarations)) lineLayer.set('style', firstElementStyle) lineLayer.attrib.pop('stroke') # remove unneccesary stroke attribute lineLayer.attrib.pop('fill') # remove unneccesary fill attribute else: for lineLayer in lineLayers: if lineLayer.attrib.has_key('stroke'): color = lineLayer.get('stroke') lineLayer.set('style', 'stroke:' + color + ';stroke-width:{:0.2f}px;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill:none'.format(self.options.lines_stroke_width)) lineLayer.attrib.pop('stroke') # remove unneccesary stroke attribute lineLayer.attrib.pop('fill') # remove unneccesary fill attribute import_viewBox = import_doc.getroot().get('viewBox').split(" ") self_viewBox = self.document.getroot().get('viewBox').split(" ") scaleX = self.svg.unittouu(self_viewBox[2]) / self.svg.unittouu(import_viewBox[2]) scaleY = self.svg.unittouu(self_viewBox[3]) / self.svg.unittouu(import_viewBox[3]) for element in import_doc.getroot().iter("{http://www.w3.org/2000/svg}g"): self.document.getroot().append(element) if self.options.input_handling == "layers": element.set('transform', 'scale(' + str(scaleX) + ',' + str(scaleY) + ')') #imported groups need to be transformed. Or they have wrong size. Reason: different viewBox sizes/units in namedview definitions if self.options.apply_transformations is True and applyTransformAvailable is True: #we apply the transforms directly after adding them applytransform.ApplyTransform().recursiveFuseTransform(element) # Delete the temporary file again because we do not need it anymore if os.path.exists(output_file): os.remove(output_file) # Remove selection objects to do a real replace with new objects from vpype document if self.options.keep_objects is False: for element in elementsToWork: element.delete()
def to_vpype_document(layers): document = vpype.Document() for layer_name, layer in layers.items(): lines = to_vpype_lines(layer) document.add(lines, layer_name_to_id(layer_name)) return document
def effect(self): #get input file and copy it to some new temporary directory inputfile = self.options.inputfile if not os.path.exists(inputfile): inkex.utils.debug( "The input file does not exist. Please select a *.dxf or *.dwg file and try again." ) exit(1) temp_input_dir = os.path.join(tempfile.gettempdir(), "dxfdwg_input") shutil.rmtree(temp_input_dir, ignore_errors=True ) #remove the input directory before doing new job if not os.path.exists(temp_input_dir): os.mkdir(temp_input_dir) #recreate blank dir shutil.copy2( inputfile, os.path.join( temp_input_dir, Path(inputfile).name)) # complete target filename given #Prepapre output conversion outputfilebase = os.path.splitext(os.path.basename(inputfile))[0] inputfile_ending = os.path.splitext(os.path.basename(inputfile))[1] temp_output_dir = os.path.join(tempfile.gettempdir(), "dxfdwg_output") shutil.rmtree(temp_output_dir, ignore_errors=True ) #remove the output directory before doing new job if not os.path.exists(temp_output_dir): os.mkdir(temp_output_dir) #Prepare some more options for proceeding autocad_version = self.options.oda_outputformat.split("_")[0] autocad_format = self.options.oda_outputformat.split("_")[1] self.options.oda_audit_repair = "1" if self.options.oda_audit_repair else "0" #overwrite string bool with int value entityspace = [] if self.options.allentities or self.options.THREE_DFACE: entityspace.append("3DFACE") if self.options.allentities or self.options.ARC: entityspace.append("ARC") if self.options.allentities or self.options.BLOCK: entityspace.append("BLOCK") if self.options.allentities or self.options.CIRCLE: entityspace.append("CIRCLE") if self.options.allentities or self.options.ELLIPSE: entityspace.append("ELLIPSE") if self.options.allentities or self.options.LINE: entityspace.append("LINE") if self.options.allentities or self.options.LWPOLYLINE: entityspace.append("LWPOLYLINE") if self.options.allentities or self.options.POINT: entityspace.append("POINT") if self.options.allentities or self.options.POLYLINE: entityspace.append("POLYLINE") if self.options.allentities or self.options.POP_TRAFO: entityspace.append("POP_TRAFO") if self.options.allentities or self.options.SEQEND: entityspace.append("SEQEND") if self.options.allentities or self.options.SOLID: entityspace.append("SOLID") if self.options.allentities or self.options.SPLINE: entityspace.append("SPLINE") if self.options.allentities or self.options.TABLE: entityspace.append("TABLE") if self.options.allentities or self.options.VERTEX: entityspace.append("VERTEX") if self.options.allentities or self.options.VIEWPORT: entityspace.append("VIEWPORT") if self.options.allentities or self.options.THREE_DSOLID: entityspace.append("3DSOLID") if self.options.allentities or self.options.ATTRIB: entityspace.append("ATTRIB") if self.options.allentities or self.options.BODY: entityspace.append("BODY") if self.options.allentities or self.options.ARC_DIMENSION: entityspace.append("ARC_DIMENSION") if self.options.allentities or self.options.HATCH: entityspace.append("HATCH") if self.options.allentities or self.options.IMAGE: entityspace.append("IMAGE") if self.options.allentities or self.options.INSERT: entityspace.append("INSERT") if self.options.allentities or self.options.MESH: entityspace.append("MESH") if self.options.allentities or self.options.MTEXT: entityspace.append("MTEXT") if self.options.allentities or self.options.RAY: entityspace.append("RAY") if self.options.allentities or self.options.REGION: entityspace.append("REGION") if self.options.allentities or self.options.SHAPE: entityspace.append("SHAPE") if self.options.allentities or self.options.SURFACE: entityspace.append("SURFACE") if self.options.allentities or self.options.TRACE: entityspace.append("TRACE") if self.options.allentities or self.options.UNDERLAY: entityspace.append("UNDERLAY") if self.options.allentities or self.options.XLINE: entityspace.append("XLINE") #ODA to ezdxf mapping oda_ezdxf_mapping = [] oda_ezdxf_mapping.append( ["ACAD9", "R12", "AC1004"] ) #this mapping is not supported directly. so we use the lowest possible which is R12 oda_ezdxf_mapping.append( ["ACAD10", "R12", "AC1006"] ) #this mapping is not supported directly. so we use the lowest possible which is R12 oda_ezdxf_mapping.append(["ACAD12", "R12", "AC1009"]) oda_ezdxf_mapping.append( ["ACAD13", "R2000", "AC1012"] ) #R13 was overwritten by R2000 which points to AC1015 instead of AC1014 (see documentation) oda_ezdxf_mapping.append( ["ACAD14", "R2000", "AC1014"] ) #R14 was overwritten by R2000 which points to AC1015 instead of AC1014 (see documentation) oda_ezdxf_mapping.append(["ACAD2000", "R2000", "AC1015"]) oda_ezdxf_mapping.append(["ACAD2004", "R2004", "AC1018"]) oda_ezdxf_mapping.append(["ACAD2007", "R2007", "AC1021"]) oda_ezdxf_mapping.append(["ACAD2010", "R2010", "AC1024"]) oda_ezdxf_mapping.append(["ACAD2013", "R2013", "AC1027"]) oda_ezdxf_mapping.append(["ACAD2018", "R2018", "AC1032"]) ezdxf_autocad_format = None for oe in oda_ezdxf_mapping: if oe[0] == autocad_version: ezdxf_autocad_format = oe[1] break if ezdxf_autocad_format is None: inkex.errormsg("ezdxf conversion format version unknown") #Prepare DXF and SVG paths dxf_file = os.path.join(temp_output_dir, outputfilebase + ".dxf") svg_file = os.path.join(temp_output_dir, outputfilebase + ".svg") # Run ODA File Converter if self.options.oda_skip_dxf_to_dxf == False or inputfile_ending == ".dwg": # Executable test (check for proper configuration by checking mime type. Should return octet stream for a binary executable) if os.name == "nt" and "application/octet-stream" not in str( MimeTypes().guess_type( urllib.pathname2url(self.options.oda_fileconverter))): inkex.utils.debug( "You selected to use ODA File Converter but it is not configured properly. Check for installation and path location or select 'Skip conversion from DXF to DXF'. You can download ODA Converter from 'https://www.opendesign.com/guestfiles/oda_file_converter'. You need to install it in order to use it." ) exit(1) elif os.path.isfile(self.options.oda_fileconverter) == False: inkex.utils.debug( "You selected to use ODA File Converter but it is not configured properly. Check for installation and path location or select 'Skip conversion from DXF to DXF'. You can download ODA Converter from 'https://www.opendesign.com/guestfiles/oda_file_converter'. You need to install it in order to use it." ) exit(1) else: # Build and run ODA File Converter command oda_cmd = [ self.options.oda_fileconverter, temp_input_dir, temp_output_dir, autocad_version, autocad_format, "0", self.options.oda_audit_repair ] if os.name == 'nt' and self.options.oda_hidewindow: info = subprocess.STARTUPINFO( ) #hide the ODA File Converter window because it is annoying (does not work for Linux :-() info.dwFlags = 1 info.wShowWindow = 0 proc = subprocess.Popen(oda_cmd, startupinfo=info, shell=False, stdout=PIPE, stderr=PIPE) else: proc = subprocess.Popen(oda_cmd, shell=False, stdout=PIPE, stderr=PIPE) stdout, stderr = proc.communicate() if proc.returncode != 0 or (len(stderr) > 0 and stderr != b"Quit (core dumped)\n"): inkex.utils.debug("ODAFileConverter failed: %d %s %s" % (proc.returncode, stdout, stderr)) if os.name != 'nt': inkex.utils.debug( "If the error message above contains a warning about wrong/missing Qt version please install the required version. You can get the installer from 'https://download.qt.io/archive/qt/'. Sadly you will need to create a free account to install. After installation please configure the shell script '/usr/bin/ODAFileConverter' to add a preceding line with content similar to 'LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/Qt5.14.2/5.14.2/gcc_64/lib/'." ) exit(1) # check if ODA converted successfully. This is the case if no error file was created oda_errorfile = os.path.join(temp_output_dir, Path(inputfile).name + ".err") if os.path.exists(oda_errorfile): inkex.utils.debug( "ODA File Converter failed to process the file. Cannot continue DXF/DWG import. The error message is:" ) errormessage = open(oda_errorfile, 'r') errorlines = errormessage.readlines() for errorline in errorlines: inkex.utils.debug(errorline.strip()) errormessage.close() exit(1) # Do some movings/copies of skipped or processed DXF if self.options.oda_skip_dxf_to_dxf: #if true we need to move the file to simulate "processed" shutil.move(os.path.join(temp_input_dir, Path(inputfile).name), os.path.join(temp_output_dir, Path(inputfile).name)) if self.options.oda_keepconverted_dxf: shutil.copy2( dxf_file, os.path.join(os.path.dirname(inputfile), outputfilebase + "_oda.dxf")) # complete target filename given # Preprocessing DXF to DXF (entity filter) by using ezdxf the first time if self.options.ezdxf_preprocessing: # uniconverter does not handle all entities. we parse the file to exlude stuff which lets uniconverter fail dxf = ezdxf.readfile(dxf_file) modelspace = dxf.modelspace() allowed_entities = [] # supported entities by UniConverter- impossible: MTEXT TEXT INSERT and a lot of others query_string = str(entityspace)[1:-1].replace("'", "").replace(",", "") if query_string != "": for e in modelspace.query(query_string): allowed_entities.append(e) #inkex.utils.debug(ezdxf_autocad_format) #inkex.utils.debug(self.options.ezdxf_output_version) if self.options.ezdxf_output_version == "SAME": doc = ezdxf.new(ezdxf_autocad_format) else: doc = ezdxf.new( self.options.ezdxf_output_version ) #use the string values from inx file. Required to match the values from ezdxf library. See Python reference msp = doc.modelspace() for e in allowed_entities: msp.add_foreign_entity(e) doc.saveas(dxf_file) if self.options.ezdfx_keep_preprocessed: shutil.copy2( dxf_file, os.path.join( os.path.dirname(inputfile), outputfilebase + "_ezdxf.dxf")) # complete target filename given # Make SVG from DXF if self.options.dxf_to_svg_parser == "sk1": if os.name != "nt": inkex.utils.debug( "You selected sk1 UniConvertor but you are not running on a Windows platform. On Linux uniconverter 1.1.X can be installed using the now obsolete Python 2.7, but it will not run correctly because you finally will fail at installing liblcms1-dev library on newer systems. That leads to uncompilable sk1libs package. Unfortunately sk1 UniConvertor 2.X does not support dxf format. So please use another DXF to SVG converter." ) exit(1) sk1_command_ending = os.path.splitext( os.path.splitext( os.path.basename(self.options.sk1_uniconverter))[1])[0] if sk1_command_ending != ".cmd": inkex.utils.debug( "You selected sk1 UniConverter but it was not configured properly. Check the path to the executable." ) exit(1) uniconverter_cmd = [ self.options.sk1_uniconverter, dxf_file, svg_file ] #inkex.utils.debug(uniconverter_cmd) proc = subprocess.Popen(uniconverter_cmd, shell=False, stdout=PIPE, stderr=PIPE) stdout, stderr = proc.communicate() if proc.returncode != 0: inkex.errormsg("UniConverter failed: %d %s %s" % (proc.returncode, stdout, stderr)) if self.options.opendironerror: self.openExplorer(temp_output_dir) elif self.options.dxf_to_svg_parser == "bjnortier": if which("node") is None: inkex.utils.debug( "NodeJS executable not found on path. Please check your installation." ) exit(1) else: bjnortier_cmd = [ "node", os.path.join("node_modules", "dxf", "lib", "cli.js"), dxf_file, svg_file ] #inkex.utils.debug(bjnortier_cmd) proc = subprocess.Popen(bjnortier_cmd, shell=False, stdout=PIPE, stderr=PIPE) stdout, stderr = proc.communicate() if proc.returncode != 0: inkex.errormsg( "node.js DXF to SVG conversion failed: %d %s %s" % (proc.returncode, stdout, stderr)) if self.options.opendironerror: self.openExplorer(temp_output_dir) elif self.options.dxf_to_svg_parser == "kabeja": wd = os.path.join(os.getcwd(), "kabeja") #inkex.utils.debug(wd) proc = subprocess.Popen( "java -jar launcher.jar -nogui -pipeline svg " + dxf_file + " " + svg_file, cwd=wd, shell=True, stdout=PIPE, stderr=PIPE) stdout, stderr = proc.communicate() if proc.returncode != 0: inkex.errormsg("kabeja failed: %d %s %s" % (proc.returncode, stdout, stderr)) if self.options.opendironerror: self.openExplorer(temp_output_dir) elif self.options.dxf_to_svg_parser == "vpype_dxf": try: from inkex.command import inkscape import vpype from vpype_cli import execute except Exception as e: inkex.errormsg( "Error importing vpype. Did you properly install the vpype and vpype-dxf python modules?" ) exit(1) doc = vpype.Document() #create new vpype document command = "dread --quantization " + str( self.options.vpype_quantization) if self.options.vpype_simplify is True: command += " --simplify" if self.options.vpype_parallel is True: command += " --parallel" #command += " '" + inputfile + "'" command += " '" + dxf_file + "'" #inkex.errormsg(command) doc = execute(command, doc) if doc.length() == 0: inkex.errormsg( 'No lines left after vpype conversion. Conversion result is empty. Cannot continue' ) exit(1) # save the vpype document to new svg file and close it afterwards output_fileIO = open(svg_file, "w", encoding="utf-8") vpype.write_svg(output_fileIO, doc, page_size=None, center=False, source_string='', layer_label_format='%d', show_pen_up=False, color_mode='layer') output_fileIO.close() # convert vpype polylines/lines/polygons to regular paths again. We need to use "--with-gui" to respond to "WARNING: ignoring verb FileSave - GUI required for this verb." cli_output = inkscape( svg_file, "--with-gui", actions= "EditSelectAllInAllLayers;EditUnlinkClone;ObjectToPath;FileSave;FileQuit" ) if len(cli_output) > 0: self.debug( _("Inkscape returned the following output when trying to run the vpype object to path back-conversion:" )) self.debug(cli_output) elif self.options.dxf_to_svg_parser == "ezdxf": try: doc = ezdxf.readfile(dxf_file) msp = doc.modelspace() #for e in msp: #loop through entities # inkex.errormsg(e) #doc.header['$DIMSCALE'] = 0.2 does not apply to the plot :-( #inkex.utils.debug(doc.header['$DIMSCALE']) #inkex.utils.debug(doc.header['$MEASUREMENT']) auditor = doc.audit( ) #audit & repair DXF document before rendering # The auditor.errors attribute stores severe errors, which *may* raise exceptions when rendering. if len(auditor.errors) == 0: fig = plt.figure() ax = plt.axes([0., 0., 1., 1.], xticks=[], yticks=[]) #ax = plt.axes([0., 0., 1., 1.], frameon=False, xticks=[], yticks=[]) ax.patches = [] #plt.axis('off') plt.margins(0, 0) plt.gca().xaxis.set_major_locator(plt.NullLocator()) plt.gca().yaxis.set_major_locator(plt.NullLocator()) plt.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0) out = MatplotlibBackend(fig.add_axes(ax)) Frontend(RenderContext(doc), out).draw_layout(msp, finalize=True) #plt.show() #fig.savefig(os.path.join(temp_output_dir, outputfilebase + ".png"), dpi=300) fig.savefig( svg_file ) #see https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.savefig.html except IOError: inkex.errormsg("Not a DXF file or a generic I/O error.") exit(1) except ezdxf.DXFStructureError: inkex.errormsg("Invalid or corrupted DXF file.") exit(1) elif self.options.dxf_to_svg_parser == "legacy": inkex.utils.debug( "The selected legacy DXF to SVG parser is not supported by this extension yet. Use File > Import > *.dxf. This calls the \"dxf_input.inx\" extension." ) exit(1) else: inkex.utils.debug("undefined parser") exit(1) # Write the generated SVG into InkScape's canvas try: stream = open(svg_file, 'r') except FileNotFoundError as e: inkex.utils.debug( "There was no SVG output generated. Cannot continue") exit(1) p = etree.XMLParser(huge_tree=True) doc = etree.parse(stream, parser=etree.XMLParser(huge_tree=True)).getroot() stream.close() doc.set( 'id', self.svg.get_unique_id("dxf_dwg_import-" + self.options.dxf_to_svg_parser + "-")) self.document.getroot().append(doc) #get children of the doc and move them one group above - we don't do this for bjnortier tool because this has different structure which we don't want to disturb if self.options.dxf_to_svg_parser == "sk1": elements = [] emptyGroup = None for firstGroup in doc.getchildren(): emptyGroup = firstGroup for element in firstGroup.getchildren(): elements.append(element) #break #only one cycle - could be bad idea or not for element in elements: doc.set('id', self.svg.get_unique_id('dxf_dwg_import')) doc.insert(doc.index(firstGroup), element) if emptyGroup is not None: emptyGroup.getparent().remove(emptyGroup) #empty the following vals because they destroy the size aspects of the import / make viewbox looking wrong if self.options.dxf_to_svg_parser == "bjnortier" or self.options.dxf_to_svg_parser == "kabeja": doc.set('width', '') doc.set('height', '') doc.set('viewBox', '') doc.getchildren()[0].set('transform', '') #adjust viewport and width/height to have the import at the center of the canvas if self.options.resizetoimport: bbox = inkex.elements._selected.ElementList.bounding_box( doc.getchildren()[0]) if bbox is not None: 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', bbox.width + 2 * offset) root.set('height', bbox.height + 2 * offset)