def generate(self): tex_file = os.path.join(self.tempdir, 'input.tex') pdf_file = os.path.join(self.tempdir, 'input.pdf') # Auto-generate by pdflatex svg_file = os.path.join(self.tempdir, 'output.svg') with open(tex_file, 'w') as fhl: self.write_latex(fhl) call('pdflatex', tex_file,\ output_directory=self.tempdir,\ halt_on_error=True, oldie=True) inkscape(pdf_file, export_filename=svg_file, pdf_page=1, pdf_poppler=True, export_type="svg") if not os.path.isfile(svg_file): fn = os.path.basename(svg_file) if os.path.isfile(fn): # Inkscape bug detected, file got saved wrong svg_file = fn with open(svg_file, 'r') as fhl: svg = load_svg(fhl).getroot() svg.set_random_ids(backlinks=True) for child in svg: if isinstance(child, ShapeElement): yield child elif isinstance(child, Defs): for def_child in child: #def_child.set_random_id() self.svg.defs.append(def_child)
def effect(self): """Main entry point to process current document. Not to be called externally.""" actions_list = self.custom_effect(self) if actions_list is None or actions_list == []: self.msg( "No actions received. Perhaps you are calling inkex object methods?" ) elif isinstance(actions_list, list): tempfile = self.options.input_file + "-BaseExtension.svg" # prepare copy2(self.options.input_file, tempfile) actions_list.append("FileSave") actions_list.append("FileQuit") actions = ";".join(actions_list) inkscape(tempfile, "--with-gui", actions=actions) # finish up # replace current document with content of temp copy file self.document = inkex.load_svg(tempfile) # update self.svg self.svg = self.document.getroot() # Clean up tempfile try: os.remove(tempfile) except Exception: # pylint: disable=broad-except pass
def exportNode(self, node, dpi=None, num='1', size=None): skip, kwargs = self.formatExport(node, dpi, num, size) if skip: raise inkex.AbortExtension("File is available in destination.\nPlease change directory location or make sure overwrite option is checked!") svgFile = self.options.input_file inkscape(svgFile, **kwargs)
def export_slice(self, sli, filename): """ Runs inkscape's command line interface and exports the image slice from the 4 coordinates in s, and saves as the filename given. """ coords = ":".join([self.get_localised_string(dim) for dim in sli]) inkscape(self.options.input_file, export_dpi=self.options.dpi, export_area=coords, export_filename=filename)
def export_node(self, node, height=None, width=None): color, kwargs = self.get_color_and_command_kwargs(node, height, width) node_id = node.attrib["id"] self.color_map[node_id] = color if color == ExportSlices.GREY: # skipping return svg_file = self.options.input_file inkscape(svg_file, **kwargs)
def export_image(self, name, size): dir = self.options.output_path file = "{}.png".format(name) output_file = os.path.join(dir, file) kwargs = { 'export-area': '0:0:108:108', 'export-filename': output_file, 'export-width': str(size), 'export-height': str(size) } inkscape(self.options.input_file, **kwargs)
def exportImageToPNG(self, assetID, assetLABEL): filename = assetLABEL + ".png" svg_file = self.options.input_file #DOCNAME = self.document.getroot().xpath('@sodipodi:docname', namespaces=inkex.NSS) #svg_file = os.path.join(self.DIRNAME, DOCNAME[0]) if not os.path.exists(os.path.join(self.DIRNAME, "temp")): os.mkdir(os.path.join(self.DIRNAME, "temp")) filepath = os.path.join(self.DIRNAME, "temp", filename) inkscape(self.options.input_file, export_id=assetID, export_filename=filepath)
def run_pathops(self, svgfile, top_path, id_list, ink_verb, dry_run=False): """Run path ops with top_path on a list of other object ids.""" # build list with command line arguments actions_list = [] for node_id in id_list: actions_list.append("select-by-id:" + top_path) actions_list.append("EditDuplicate") actions_list.append("select-by-id:" + node_id) actions_list.append(ink_verb) actions_list.append("EditDeselect") actions_list.append("FileSave") actions_list.append("FileQuit") actions = ";".join(actions_list) # process command list if dry_run: inkex.utils.debug(" ".join([ "inkscape", "--with-gui", "--actions=" + "\"" + actions + "\"", svgfile ])) else: inkscape(svgfile, "--with-gui", actions=actions)
def export(self, export_base_name): export_file_name = '{0}.{1}'.format(export_base_name, self.options.format) if os.path.exists(self.options.output_folder): export_file_path = os.path.join(self.options.output_folder, export_file_name) else: inkex.errormsg("The selected output folder does not exist.") return False if self.options.format == 'svg': # would use this, but it cannot overwrite, nor handle strings for writing...: # write_svg(self.new_doc, export_file_path) with open(export_file_path, 'w') as f: f.write(self.new_doc) else: actions = { 'png' : 'export-dpi:{dpi};export-filename:{file_name};export-do;FileClose'.\ format(dpi=self.options.dpi, file_name=export_file_path), 'pdf' : 'export-dpi:{dpi};export-pdf-version:1.5;export-text-to-path;export-filename:{file_name};export-do;FileClose'.\ format(dpi=self.options.dpi, file_name=export_file_path), 'ps' : 'export-dpi:{dpi};export-text-to-path;export-filename:{file_name};export-do;FileClose'.\ format(dpi=self.options.dpi, file_name=export_file_path), 'eps' : 'export-dpi:{dpi};export-text-to-path;export-filename:{file_name};export-do;FileClose'.\ format(dpi=self.options.dpi, file_name=export_file_path), } # create a temporary svg file from our string temp_svg_name = '{0}.{1}'.format(export_base_name, 'svg') temp_svg_path = os.path.join(self.tempdir, temp_svg_name) #inkex.utils.debug("temp_svg_path=" + temp_svg_path) with open(temp_svg_path, 'w') as f: f.write(self.new_doc) #inkex.utils.debug("self.new_doc=" + self.new_doc) # let Inkscape do the exporting # self.debug(actions[self.options.format]) cli_output = inkscape(temp_svg_path, actions=actions[self.options.format]) if len(cli_output) > 0: self.debug( _("Inkscape returned the following output when trying to run the file export; the file export may still have worked:" )) self.debug(cli_output) return False return True
def generate_png_separations(temp_dir, area_to_export, resolution, alpha): if alpha: alpha_command = "" else: alpha_command = ";export-background:white" for color in ['C', 'M', 'Y', 'K']: cmd = area_to_export + alpha_command + ';export-dpi:' + str( resolution ) + ';export-background-opacity:1;export-filename:' + os.path.join( temp_dir, "separated" + area_to_export.replace(' ', '') + color + ".png") + ';export-do' #inkex.utils.debug(cmd) cli_output = inkscape(os.path.join(temp_dir, "separation" + color + ".svg"), actions=cmd) if len(cli_output) > 0: inkex.utils.debug(cli_output)
def effect(self): # Adjust the document view for the desired sticker size root = self.svg.getElement("//svg:svg") subline_fontsize = 40 #px; one line of bottom text (id and owner) creates a box of that height #our DataMatrix has size 16x16, each cube is sized by 16x16px -> total size is 256x256px. We use 4px padding for all directions DataMatrix_xy = 16 DataMatrix_height = 16 * DataMatrix_xy DataMatrix_width = DataMatrix_height sticker_padding = 4 sticker_height = DataMatrix_height + subline_fontsize + 3 * sticker_padding sticker_width = 696 #configure font sizes and box heights to define how large the font size may be at maximum (to omit overflow) objectNameMaxHeight = sticker_height - 2 * subline_fontsize - 4 * sticker_padding objectNameMaxLines = 5 objectNameFontSize = objectNameMaxHeight / objectNameMaxLines #px; generate main font size from lines and box size root.set("width", str(sticker_width) + "px") root.set("height", str(sticker_height) + "px") root.set("viewBox", "%f %f %f %f" % (0, 0, sticker_width, sticker_height)) #clean the document (make it blank) to avoid printing duplicated things ct = 0 for node in self.document.xpath('//*', namespaces=inkex.NSS): ct = ct + 1 if ct > 3: #we keep svg:svg, sodipodi:namedview and svg:defs which defines the default canvas without any content inside #inkex.errormsg(str(node)) try: root.remove(node) except Exception as e: pass #set the document units self.document.getroot().find(inkex.addNS("namedview", "sodipodi")).set( "inkscape:document-units", "px") # Download the recent inventory CSV file and parse line by line to create an inventory sticker password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm() password_mgr.add_password(None, self.options.server_address, self.options.htuser, self.options.htpassword) handler = urllib.request.HTTPBasicAuthHandler(password_mgr) opener = urllib.request.build_opener(handler) try: inventoryData = opener.open( self.options.server_address).read().decode("utf-8") urllib.request.install_opener(opener) inventoryCSVParent = os.path.join(self.options.export_dir, "InventorySticker") inventoryCSV = os.path.join(inventoryCSVParent, "inventory.csv") # To avoid messing with old stickers we remove the directory on Client before doing something new shutil.rmtree(inventoryCSVParent, ignore_errors=True ) #remove the output directory before doing new job # we are going to write the imported Server CSV file temporarily. Otherwise CSV reader seems to mess with the file if passed directly if not os.path.exists(inventoryCSVParent): os.mkdir(inventoryCSVParent) with open(inventoryCSV, 'w', encoding="utf-8") as f: f.write(inventoryData) f.close() #parse sticker Ids from user input if self.options.sticker_ids != "*": sticker_ids = self.options.sticker_ids.split(",") else: sticker_ids = None with open(inventoryCSV, 'r', encoding="utf-8") as csv_file: csv_reader = csv.reader(csv_file, delimiter=",") for row in csv_reader: internal_id = row[0] doc_title = row[1] sticker_id = row[2] level = row[3] zone = row[4] if sticker_ids is None or sticker_id in sticker_ids: #create new sub directories for each non-existent FabLab zone (if flat export is disabled) if self.options.flat_export == False: if not zone: zoneDir = os.path.join( inventoryCSVParent, "Keinem Bereich zugeordnet") else: zoneDir = os.path.join( inventoryCSVParent, get_valid_filename(zone) ) #remove invalid charaters from zone if not os.path.exists(zoneDir): os.mkdir(zoneDir) else: zoneDir = inventoryCSVParent #use top directory #Generate the recent sticker content stickerGroup = self.document.getroot().add( inkex.Group( id="InventorySticker_Id" + sticker_id)) #make a new group at root level DataMatrixStyle = inkex.Style({ "stroke": "none", "stroke-width": "1", "fill": "#000000" }) DataMatrixAttribs = { "style": str(DataMatrixStyle), "height": str(DataMatrix_xy) + "px", "width": str(DataMatrix_xy) + "px" } # 1 - create DataMatrix (create a 2d list corresponding to the 1"s and 0s of the DataMatrix) encoded = self.encode(self.options.target_url + "/" + sticker_id) DataMatrixGroup = stickerGroup.add( inkex.Group( id="DataMatrix_Id" + sticker_id)) #make a new group at root level for x, y in self.render_data_matrix( encoded, DataMatrix_xy): DataMatrixAttribs.update({ "x": str(x + sticker_padding), "y": str(y + sticker_padding) }) etree.SubElement(DataMatrixGroup, inkex.addNS("rect", "svg"), DataMatrixAttribs) inline_size = sticker_width - DataMatrix_width - 3 * sticker_padding #remaining width for objects next to the DataMatrix x_pos = DataMatrix_width + 2 * sticker_padding # 2 - Add Object Name Text objectName = etree.SubElement( stickerGroup, inkex.addNS("text", "svg"), { "font-size": str(objectNameFontSize) + "px", "x": str(x_pos) + "px", #"xml:space": "preserve", #we cannot add this here because InkScape throws an error #"y": "4px", #if set it does not correctly apply "text-align": "left", "text-anchor": "left", "vertical-align": "bottom", #style: inline-size required for text wrapping inside box; letter spacing is required to remove the additional whitespaces. The letter spacing depends to the selected font family (Miso) "style": str( inkex.Style( { "fill": "#000000", "inline-size": str(inline_size) + "px", "stroke": "none", "font-family": "Miso", "font-weight": "bold", "letter-spacing": "-3.66px" })) }) objectName.set("id", "objectName_Id" + sticker_id) objectName.set( "xml:space", "preserve" ) #so we add it here instead .. if multiple whitespaces in text are coming after each other just render them (preserve!) objectNameTextSpan = etree.SubElement( objectName, inkex.addNS("tspan", "svg"), {}) objectNameTextSpan.text = splitAt( doc_title, 1 ) #add 1 whitespace after each chacter. So we can simulate a in-word line break (break by char instead by word) # 3 - Add Object Id Text - use the same position but revert text anchors/align objectId = etree.SubElement( stickerGroup, inkex.addNS("text", "svg"), { "font-size": str(subline_fontsize) + "px", "x": str(sticker_padding) + "px", "transform": "translate(0," + str(sticker_height - subline_fontsize) + ")", "text-align": "left", "text-anchor": "left", "vertical-align": "bottom", "style": str( inkex.Style( { "fill": "#000000", "inline-size": str(inline_size) + "px", "stroke": "none", "font-family": "Miso", "font-weight": "bold" }) ) #inline-size required for text wrapping }) objectId.set("id", "objectId_Id" + sticker_id) objectIdTextSpan = etree.SubElement( objectId, inkex.addNS("tspan", "svg"), {}) objectIdTextSpan.text = "Thing #" + sticker_id # 4 - Add Owner Text owner = etree.SubElement( stickerGroup, inkex.addNS("text", "svg"), { "font-size": str(subline_fontsize) + "px", "x": str(x_pos) + "px", "transform": "translate(0," + str(sticker_height - subline_fontsize) + ")", "text-align": "right", "text-anchor": "right", "vertical-align": "bottom", "style": str( inkex.Style( { "fill": "#000000", "inline-size": str(inline_size) + "px", "stroke": "none", "font-family": "Miso", "font-weight": "300" }) ) #inline-size required for text wrapping }) owner.set("id", "owner_Id" + sticker_id) ownerTextSpan = etree.SubElement( owner, inkex.addNS("tspan", "svg"), {}) ownerTextSpan.text = self.options.target_owner # 5 - Add Level Text levelText = etree.SubElement( stickerGroup, inkex.addNS("text", "svg"), { "font-size": str(subline_fontsize) + "px", "x": str(x_pos) + "px", "transform": "translate(0," + str(sticker_height - subline_fontsize - subline_fontsize) + ")", "text-align": "right", "text-anchor": "right", "vertical-align": "bottom", "style": str( inkex.Style( { "fill": "#000000", "inline-size": str(inline_size) + "px", "stroke": "none", "font-family": "Miso", "font-weight": "bold" }) ) #inline-size required for text wrapping }) levelText.set("id", "level_Id" + sticker_id) levelTextTextSpan = etree.SubElement( levelText, inkex.addNS("tspan", "svg"), {}) levelTextTextSpan.text = level # 6 - Add horizontal divider line line_thickness = 2 #px line_x_pos = 350 #px; start of the line (left coord) line_length = sticker_width - line_x_pos divider = etree.SubElement( stickerGroup, inkex.addNS("path", "svg"), { "d": "m " + str(line_x_pos) + "," + str(sticker_height - subline_fontsize - subline_fontsize) + " h " + str(line_length), "style": str( inkex.Style( { "fill": "none", "stroke": "#000000", "stroke-width": str(line_thickness) + "px", "stroke-linecap": "butt", "stroke-linejoin": "miter", "stroke-opacity": "1" }) ) #inline-size required for text wrapping }) divider.set("id", "divider_Id" + sticker_id) if self.options.preview == False: export_file_name = sticker_id + "_" + get_valid_filename( doc_title) export_file_path = os.path.join( zoneDir, export_file_name) #"Export" as SVG by just copying the recent SVG document to the target directory. We need to remove special characters to have valid file names on Windows/Linux export_file_svg = open(export_file_path + ".svg", "w", encoding="utf-8") export_file_svg.write( str(etree.tostring(self.document), "utf-8")) export_file_svg.close() if self.options.export_png == False and self.options.export_svg == False: inkex.errormsg( "Nothing to export. Generating preview only ..." ) break if self.options.export_png == True: #we need to generate SVG before to get PNG. But if user selected PNG only we need to remove SVG afterwards #Make PNG from SVG (slow because each file is picked up separately. Takes about 10 minutes for 600 files inkscape( export_file_path + ".svg", actions= "export-dpi:96;export-background:white;export-filename:{file_name};export-do;FileClose" .format(file_name=export_file_path + ".png")) #fix for "usb.core.USBError: [Errno 13] Access denied (insufficient permissions)" #echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="04f9", ATTR{idProduct}=="2044", MODE="666"' > /etc/udev/rules.d/99-garmin.rules && sudo udevadm trigger if self.options.print_png > 0: if self.options.export_png == False: inkex.errormsg( "No file output for printing. Please set 'Export PNG' to true first." ) else: for x in range(self.options.print_png): command = "brother_ql -m QL-720NW --backend pyusb --printer usb://" + self.options.print_device + " print -l 62 --600dpi -r auto " + export_file_path + ".png" p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE ) #forr Windows: shell=False stdout, stderr = p.communicate() p.wait() if p.returncode != 0: inkex.errormsg( "brother_ql returned: %d %s %s" % (p.returncode, stdout, stderr)) if self.options.export_svg != True: #If user selected PNG only we need to remove SVG again os.remove(export_file_path + ".svg") self.document.getroot().remove( stickerGroup) #remove the stickerGroup again else: #create preview by just breaking the for loop without executing remove(stickerGroup) break csv_file.close() except Exception as e: inkex.errormsg(e)
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)
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 effect(self): if not self.svg.selected: inkex.errormsg( "Selection is empty. Please select some objects first!") return export_dir = Path(self.absolute_href(self.options.export_dir)) os.makedirs(export_dir, exist_ok=True) offset = self.options.border_offset bbox = inkex.BoundingBox() for elem in self.svg.selected.values(): transform = inkex.Transform() parent = elem.getparent() if parent is not None and isinstance(parent, inkex.ShapeElement): transform = parent.composed_transform() try: bbox += elem.bounding_box(transform) except Exception: logger.exception("Bounding box not computed") logger.info("Skipping bounding box") transform = elem.composed_transform() x1, y1 = transform.apply_to_point([0, 0]) x2, y2 = transform.apply_to_point([1, 1]) bbox += inkex.BoundingBox((x1, x2), (y1, y2)) template = self.create_document() filename = None group = etree.SubElement(template, '{http://www.w3.org/2000/svg}g') group.attrib['id'] = GROUP_ID group.attrib['transform'] = str( inkex.Transform(((1, 0, -bbox.left), (0, 1, -bbox.top)))) for elem in self.svg.selected.values(): elem_copy = deepcopy(elem) elem_copy.attrib['transform'] = str(elem.composed_transform()) group.append(elem_copy) template.attrib[ 'viewBox'] = f'{-offset} {-offset} {bbox.width + offset * 2} {bbox.height + offset * 2}' template.attrib[ 'width'] = f'{bbox.width + offset * 2}' + self.svg.unit template.attrib[ 'height'] = f'{bbox.height + offset * 2}' + self.svg.unit if filename is None: filename = elem.attrib.get('id', None) if filename: filename = filename.replace(os.sep, '_') + '.svg' if not filename: #should never be the case. Inkscape might crash if the id attribute is empty or not existent due to invalid SVG filename = self.svg.get_unique_id("selection") + '.svg' template.append(group) if not self.options.wrap_transform: self.load( inkscape_command(template.tostring(), select=GROUP_ID, verbs=['SelectionUnGroup'])) template = self.svg for child in template.getchildren(): if child.tag == '{http://www.w3.org/2000/svg}metadata': template.remove(child) self.save_document(template, export_dir / filename) if self.options.opendir is True: self.openExplorer(export_dir) if self.options.newwindow is True: inkscape(os.path.join(export_dir, filename)) if self.options.export_dxf is True: #ensure that python3 command is available #we pass 25.4/96 which stands for unit mm. See inkex.units.UNITS and dxf_outlines.inx cmd = [ 'python3', self.options.dxf_exporter_path, '--output=' + os.path.join(export_dir, filename + '.dxf'), r'--units=25.4/96', os.path.join(export_dir, filename) ] proc = Popen(cmd, shell=False, stdout=PIPE, stderr=PIPE) stdout, stderr = proc.communicate() #inkex.utils.debug("%d %s %s" % (proc.returncode, stdout, stderr)) if self.options.export_pdf is True: cli_output = inkscape( os.path.join(export_dir, filename), actions= 'export-pdf-version:1.5;export-text-to-path;export-filename:{file_name};export-do;FileClose' .format(file_name=os.path.join(export_dir, filename + '.pdf'))) if len(cli_output) > 0: self.msg( "Inkscape returned the following output when trying to run the file export; the file export may still have worked:" ) self.msg(cli_output)
def export_image(source, output, resolution=None): args = [source, "-o", output] if resolution: args += ["-h", str(resolution)] debug("Calling Inkscape with args %s" % args) command.inkscape(*args)