def parse_partfile(partfilepath): partdata = None try: with open(os.path.join(os.getcwd(), partfilepath), "r") as partfile: partdata = json.load(partfile) except IOError: exitwitherror("Unable to open Ara parts list file, {0}".format(partfilepath)) return partdata
def count_object_types(objects, objtype): if objtype == "part": typeid = "Part::Feature" elif objtype == "mesh": typeid = "Mesh::Feature" else: exitwitherror("Invalid object type specified for querying in count_object_types()" + " Type specified: {0}".format(objtype)) return len([obj for obj in objects if obj.TypeId == typeid])
def set_colors(self, colors): """ Sets color for each individual face of object. """ try: self.fc.ViewObject.DiffuseColor = colors except AttributeError: if len(colors) == 1: self.set_color(colors[0]) else: exitwitherror('Error setting colors for {0}. Colors: {1}'.format(self.name, str(colors)))
def grab_object_by_name(self, name): parts = [p for p in self.parts if p.name == name] if len(parts) == 0: logger.error("Parts in assembly {0}: {1}".format(self.name, str([p.name for p in self.parts]))) exitwitherror("Unable to find part {0} in assembly {1}".format(name, self.name)) elif len(parts) > 1: logger.warning("Multiple parts named {0} in assembly {1}.".format(name, self.name) + "Choosing one at random.") return parts[0]
def read_input_files(component_dict, layoutfile): # Parse layout file and populate Layout class layout = eda_layout.Layout() layout.parse_layout(layoutfile) logger.info('EAGLE layout JSON found.') try: with open(component_dict, 'r') as ct_json: CT = json.load(ct_json) except IOError: exitwitherror('Component Dictionary JSON file not found! Message: ' + str(OSError.strerror)) logger.info('Component mapping dictionary CT.JSON found.') return layout, CT
def set_sketch_plane(self, plane, offset=0.0): rot = None loc = None if any([p for p in ['XY', 'YX'] if plane.upper() == p]): rot = FreeCAD.Rotation(0.0, 0.0, 0.0, 1.0) loc = FreeCAD.Vector(0.0, 0.0, offset) elif any([p for p in ['XZ', 'ZX'] if plane.upper() == p]): rot = FreeCAD.Rotation(-0.707107, 0.0, 0.0, -0.707107) loc = FreeCAD.Vector(0.0, offset, 0.0) elif any([p for p in ['YZ', 'ZY'] if plane.upper() == p]): rot = FreeCAD.Rotation(0.5, 0.5, 0.5, 0.5) loc = FreeCAD.Vector(offset, 0.0, 0.0) if rot is None: exitwitherror('Invalid sketch plane specified: {0}'.format(plane.upper())) FreeCAD.ActiveDocument.getObject(self.name).Placement = FreeCAD.Placement(loc, rot)
def parse_layout(self, layoutpath): try: with open(layoutpath, "r") as layoutfile: layout = json.load(layoutfile) except IOError: exitwitherror('Layout JSON file not found! Message: ' + str(IOError.strerror)) self.height = layout.get("boardHeight") self.width = layout.get("boardWidth") self.layers = layout.get("numLayers") if "border" in layout: self.get_board_border(layout["border"]) if "packages" in layout: self.get_packages(layout["packages"])
def visualize(xmldir): # Find cad.js directory/app #commondocs = shell.SHGetFolderPath(0, shellcon.CSIDL_COMMON_DOCUMENTS, 0, 0) # MOT-674: cad.js moved into installer bin directory with _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, r'Software\META', 0, _winreg.KEY_READ | _winreg.KEY_WOW64_32KEY) as key: META_PATH = _winreg.QueryValueEx(key, 'META_PATH')[0] cadjs = os.path.join(META_PATH, 'bin', 'cad.js') cadjs_index = os.path.join(cadjs, 'public', 'index.html') if not os.path.exists(cadjs_index): exitwitherror('Unable to locate cad.js index.html! Is the cad.js repository ' + \ 'in the correct location? Cad.js repository needs to be ' + \ 'in Public Documents folder. Path searched: ' + cadjs_index) # Check existence of index.xml in xmldir step_index_dir = posixpath.join(os.getcwd(), xmldir) step_index = os.path.join(step_index_dir, 'index.xml') if not os.path.exists(step_index): exitwitherror('Index.XML from StepTools converter can not be found! ' + \ 'Path searched: ' + step_index) """ Launch chrome with through subprocess. The webbrowser module does not work... The path contains a query operator followed by XML path "...?resource_url=..." Launched with webbrowser, everything including and following the ? is truncated. Provide absolute paths to both index html and xml files. * Subprocess is in background, so program exits once command is executed. """ try: with _winreg.OpenKey( _winreg.HKEY_LOCAL_MACHINE, r'Software\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe', 0, _winreg.KEY_READ | _winreg.KEY_WOW64_32KEY) as key: CHROME_PATH = _winreg.QueryValueEx(key, 'Path')[0] except WindowsError: try: with _winreg.OpenKey( _winreg.HKEY_LOCAL_MACHINE, r'Software\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe', 0, _winreg.KEY_READ | _winreg.KEY_WOW64_64KEY) as key: CHROME_PATH = _winreg.QueryValueEx(key, 'Path')[0] except WindowsError: exitwitherror("Unable to find Google Chrome registry key!") if CHROME_PATH is None: exitwitherror('Google Chrome install location not found! \n') CHROME = os.path.join(CHROME_PATH, 'chrome.exe') # Use posixpath to force the index.xml path to use a forward slash vis_path = r'file://' + cadjs_index + r"?resource_url=" + posixpath.join( step_index_dir, "index.xml") subprocess.Popen([CHROME, '--allow-file-access-from-files', vis_path])
def export(format, parts, exportdir=None, name=None): """ Export components back out to given format in their assembled positions. STEP files can be exported to STL, but not the other way around. This is not in the Part class since it allows for the export of any number of objects. If exportDir is None, the assembly will be exported to one STL. If exportDir is specified, the individual components will be exported to STL. """ exportpath = os.getcwd() export_individual = False if exportdir is not None: export_individual = True exportpath = os.path.join(os.getcwd(), exportdir) if not os.path.isdir(exportpath): os.makedirs(exportpath) else: exportdir = exportpath if format.lower() in ['step', 'stp']: for obj in parts: if obj.isDerivedFrom("Mesh::Feature"): logger.warning('Component {0} is derived from an STL file. '.format(obj.Label) + 'This can not be exported to the STEP format. This component is being skipped.') continue ImportGui.export([obj], os.path.join(exportpath, obj.Label + '.step')) logger.info('Exported {0}.step to {1}.'.format(obj.Label, exportdir)) elif format.lower() in ['stl', 'mix']: if export_individual: for prt in parts: Mesh.export([prt], os.path.join(exportpath, prt.Label + '.stl')) logger.info('Exported {0}.stl to {1}.'.format(prt.Label, exportdir)) else: Mesh.export(parts, os.path.join(exportpath, FreeCAD.ActiveDocument.Name + '.stl')) logger.info('Exported {0}.stl to {1}.'.format(FreeCAD.ActiveDocument.Name, exportdir)) elif format.lower() == 'fc': if name is not None: FreeCAD.ActiveDocument.saveAs(os.path.join(exportpath, name + '.FCStd')) else: FreeCAD.ActiveDocument.saveAs(os.path.join(exportpath, FreeCAD.ActiveDocument.Name + '.FCStd')) else: exitwitherror('Requested to export parts to unsupported file format {0}.'.format(str(format.lower())))
def load(self, path): """ Loads FC, STEP, or STL file into document object. Will return cadpath and format properties to store into Part/Assembly class if desired. """ cadpath = os.path.join('..', '..', path) # For test bench runs (..\..\ takes you to project root) if not os.path.exists(cadpath): cadpath = path if not os.path.exists(cadpath): exitwitherror('Can\'t find cad file!') fileformat = cadpath.rsplit('.')[-1].lower() try: if fileformat in ["step", "stp"]: ImportGui.insert(cadpath, self.Name) elif fileformat == 'stl': Mesh.insert(cadpath, self.Name) elif fileformat == "fcstd": # Loading a FC file will always open a new doc, so close the current one and set to the new one. self.close() self.document = FreeCAD.openDocument(cadpath) self.Name = self.document.Name else: exitwitherror('Unsupported file format {0} for file {1}.'.format(str(fileformat), str(path))) except (Exception, IOError) as err: logger.error(err) exitwitherror('Error attempting to import {0}'.format(cadpath)) logger.info('Imported CAD file {0}'.format(cadpath)) return cadpath, fileformat
def import_cad(path, doc): """ Imports cad file at path into document doc. May produce more than one FreeCAD object! """ cadpath = os.path.join('..', '..', path) # For test bench runs (..\..\ takes you to project root) if not os.path.exists(cadpath): cadpath = path if not os.path.exists(cadpath): exitwitherror('Can\'t find cad file!') fileformat = cadpath.rsplit('.')[-1].lower() newdoc = None try: if fileformat in ["step", "stp"]: ImportGui.insert(cadpath, doc) elif fileformat == 'stl': Mesh.insert(cadpath, doc) elif fileformat == "fcstd": # This will create a new doc and ignore doc operator newdoc = open_document(cadpath) else: exitwitherror('Unsupported file format {0} for file to import {1}.'.format(str(fileformat), str(path))) except (Exception, IOError) as err: logger.error(err) exitwitherror('Error attempting to import {0}'.format(cadpath)) logger.info('Imported CAD file {0}'.format(cadpath)) return cadpath, fileformat, newdoc
def find_freecad_install(): # Check platform script is running on to account for FreeCAD imports (Mac is for dev testing) platform = sys.platform if platform == 'Darwin': FREECAD = r'/Applications/FreeCAD.app/Contents/bin/FreeCAD' sys.path.append('/Applications/FreeCAD.app/Contents/Mod') elif platform == 'win32': # This is win32 for 32 and 64 bit Windows import _winreg with _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, r'Software\META', 0, _winreg.KEY_READ | _winreg.KEY_WOW64_32KEY) as key: META_PATH = _winreg.QueryValueEx(key, 'META_PATH')[0] try: with _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, r'Software\Microsoft\Windows\CurrentVersion\Uninstall\FreeCAD 0.14', 0, _winreg.KEY_READ | _winreg.KEY_WOW64_32KEY) as key: FREECAD_PATH = _winreg.QueryValueEx(key, 'InstallLocation')[0] except WindowsError: try: with _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, r'Software\Microsoft\Windows\CurrentVersion\Uninstall\FreeCAD 0.14', 0, _winreg.KEY_READ | _winreg.KEY_WOW64_64KEY) as key: FREECAD_PATH = _winreg.QueryValueEx(key, 'InstallLocation')[0] except WindowsError: exitwitherror("Unable to find FreeCAD registry key! \n Message: " + WindowsError.strerror) FREECAD_PATH = FREECAD_PATH.strip('"').rstrip('"') FREECAD = os.path.join(FREECAD_PATH, 'bin', 'FreeCAD.exe') else: exitwitherror('Unsupported system platform detected: ' + platform) if not os.path.exists(FREECAD): exitwitherror('Unable to find FreeCAD.exe') return FREECAD
def convert(stepfile, xmldir): # Check stepfile exists (double-checks assemble() exited gracefully). steppath = os.path.join(os.getcwd(), stepfile) if not os.path.exists(steppath): exitwitherror('STEP file ' + stepfile + ' can not be found in ' + steppath) # Check that StepTools converter is in expected location commondocs = shell.SHGetFolderPath(0, shellcon.CSIDL_COMMON_DOCUMENTS, 0, 0) converter = os.path.join(str(commondocs), 'StepTools', 'bin', 'export_product_asm.exe') if not os.path.exists(converter): print "export_product_asm.exe not found. Attempting to download it..." if not os.path.exists(os.path.dirname(converter)): os.makedirs(os.path.dirname(converter)) steptoolsfile, headers = urllib.urlretrieve( 'http://www.steptools.com/demos/stpidx_author_win32_a7.zip') with zipfile.ZipFile(steptoolsfile, 'r') as steptoolszip: with steptoolszip.open( 'stpidx_author_win32/bin/export_product_asm.exe' ) as srcexe: with open(converter, 'wb') as dstexe: shutil.copyfileobj(srcexe, dstexe) # Make directory that will contain dumped index and shell XMLs dumpdir = os.path.join(os.getcwd(), xmldir) if os.path.exists(dumpdir): shutil.rmtree(dumpdir) os.makedirs(dumpdir) # Launch converter logfile = open("log\\Step2XMLConverterLog_{0}.txt".format(stepfile), "a") rtn = subprocess.call([converter, steppath, '-d', '-o', dumpdir], stdout=logfile) logfile.close() if rtn != 0: exit(rtn)
def get_freecad_path(): try: with _winreg.OpenKey( _winreg.HKEY_LOCAL_MACHINE, r'Software\Microsoft\Windows\CurrentVersion\Uninstall\FreeCAD 0.14', 0, _winreg.KEY_READ | _winreg.KEY_WOW64_32KEY) as key: FREECAD_PATH = _winreg.QueryValueEx(key, 'InstallLocation')[0] except WindowsError: try: with _winreg.OpenKey( _winreg.HKEY_LOCAL_MACHINE, r'Software\Microsoft\Windows\CurrentVersion\Uninstall\FreeCAD 0.14', 0, _winreg.KEY_READ | _winreg.KEY_WOW64_64KEY) as key: FREECAD_PATH = _winreg.QueryValueEx(key, 'InstallLocation')[0] except WindowsError: exitwitherror( "Unable to find FreeCAD registry key. Is FreeCAD installed?") if FREECAD_PATH is None: exitwitherror('FreeCAD install location not found! \n') FREECAD_PATH = FREECAD_PATH.strip('"').rstrip('"') return os.path.join(FREECAD_PATH, "bin", "FreeCAD.exe")
def check_file_exists(filein): if not os.path.exists(filein): exitwitherror("Unable to find file {0}".format(filein))
def generate_assembly(doc, component_dict): [layout, CT] = read_input_files(component_dict, os.path.join(os.getcwd(), 'layout.json')) # The component assembly of a subcircuit will have an entry in layout.json. We want to disregard these entries, # as the components that make up the subcircuit will be added individually later on. no_relative_id_pkgs = [ p for p in layout.packages if p.relComponentId is None ] subcircuit_pkgs = [] for p in layout.packages: if p.relComponentId is not None: for s in no_relative_id_pkgs: if p.relComponentId == s.guid: subcircuit_pkgs.append(s.guid) subcircuit_pkgs = list(set(subcircuit_pkgs)) meta.new_document(doc) # Create PCB board boardthick = 0.6 chipThick = 0.3 create_pcb(layout, boardthick) # Initialize chip counter topchips = 0 bottomchips = 0 placeholders = 0 imported_cad = {} for p in sorted(layout.packages, reverse=True, key=lambda p: p.width * p.height): if p.guid in subcircuit_pkgs: continue placeholder = False # Start by assuming component exists, try to prove otherwise. component = p.guid logger.debug('\n\n') logger.debug('-- COMPONENT --') logger.debug('Component Key: ' + component) if component not in CT: logger.warning( 'Component {0} not found in CT.json, creating placeholder...'. format(component)) placeholder = True ###################################### ''' PLACEMENT CALCULATION ''' # Check for relative placement xc = None yc = None relative_package = None if p.relComponentId is not None: logger.info("Relative constraints found!") relative_package = [ pkg for pkg in layout.packages if pkg.guid == p.relComponentId ][0] logger.info('Package relative to: ' + str(relative_package.guid)) # Need to move the lower left-hand point for rotated reference package [xc, yc] = geom.rotate_and_translate(relative_package, p.x, p.y) # Cumulative rotation between package and its reference rotate = p.rotation + relative_package.rotation do_rotate = 1 if rotate == 1 or rotate == 3 or rotate == 5 else 0 # xc/yc are coordinates of moved LLH point - need to move this so it is back to LLH # --> EG, LLH rotated 180 degrees results in point at URH if relative_package.rotation == 1: xc -= p.width if not do_rotate else p.height elif relative_package.rotation == 2: xc -= p.width if not do_rotate else p.height yc -= p.height if not do_rotate else p.width elif relative_package.rotation == 3: yc -= p.height if not do_rotate else p.width else: logger.info("No constraints present. Using regular X/Y values.") xc = p.x yc = p.y rotate = p.rotation do_rotate = 1 if rotate == 1 or rotate == 3 else 0 item = None cad = None if not placeholder: item = CT[component] cad = CT[component].get("cadpath") [scale, rotation, translation, placeholder] = get_cad_srt(cad, item, placeholder) trans = meta.transform_cad_to_eda( rotation, [t * s for t, s in zip(translation, scale)]) # Define placement variables based on top/bottom layer that the component instance is on if p.layer == 0: # Component is on top layer of PCB Z = boardthick # Component height on board (Top/Bottom of PCB) placeholder_offset = 0.0 alpha = 90.0 # 90.0 degree rotation board_orient = [0, 0, rotate * alpha ] # Component orientation relative to board topchips += 1 elif p.layer == 1: # Component is on bottom layer of PCB Z = 0.0 placeholder_offset = chipThick alpha = -90.0 trans[2] *= -1 # Flip to handle 'pushing down' the component. # Two-way 180 degree flips accounts for switch to bottom board_orient = [0, 180, 180 + rotate * alpha] bottomchips += 1 else: exitwitherror( 'Invalid layer specified for package {0}'.format(component)) rot = [sum(x) for x in zip(rotation, board_orient)] # Component center point. If component is rotated width and height are reversed. width = ((1 - do_rotate) * p.width + do_rotate * p.height) height = ((1 - do_rotate) * p.height + do_rotate * p.width) [originx, originy] = geom.rotate_vector(p.originx, p.originy, rotate) X = xc + 0.5 * width - originx Y = yc + 0.5 * height - originy ###################################### ''' FREECAD OBJECT CREATION ''' if not placeholder: if cad not in imported_cad: solid = meta.Part(component) # Instantiate new part """ Query number of parts before and after import to determine if fuse needs to occur. Some single component may import each SHAPE_REPRESENTATION as its own component. If this happens, solidify these components to get back to one object. """ num_objects_b = len(meta.get_all_objects()) (solid.cadpath, solid.format, newdoc) = meta.import_cad(cad, doc) logger.info(doc) logger.info(newdoc) logger.info(solid.cadpath) logger.info(solid.name) num_objects_a = len(meta.get_all_objects()) if (num_objects_a - num_objects_b) > 1: # Only ever true for STEP files - STL files have no hierarchy concept. objects = [ meta.get_all_objects()[x] for x in range(num_objects_b, num_objects_a) ] solid.fc = meta.fuse_components(objects, solid.name) else: solid.fc = meta.get_active_object() # Store object data to avoid having to import a part multiple times (costly for STEP) imported_cad[cad] = { 'component': solid.name, 'ext': solid.format, 'cadpath': solid.cadpath, 'geometry': solid.get_shape(solid.format), 'color': solid.get_colors() } else: # Component has already been imported, grab data from dictionary and create new FC object. compdef = imported_cad[cad] solid = meta.Part(compdef['component'], compdef['ext'], compdef['cadpath']) if compdef['ext'] == 'stl': solid.create_blank('mesh') else: solid.create_blank('part') solid.set_shape(compdef['geometry'], solid.format) solid.set_colors(compdef['color']) logger.info("Generating " + component + " object...") else: placeholders += 1 logger.warning( "Making placeholder (CAD file not found for component {0}!): ". format(component)) solid = meta.Part(component) solid.create_blank() if do_rotate: solid.set_shape( meta.make_box(p.width, p.height, chipThick, -0.5 * height, -0.5 * width)) else: solid.set_shape( meta.make_box(p.width, p.height, chipThick, -0.5 * width, -0.5 * height)) solid.set_color(brd.PCBColors.PLACEHOLDER) # Red placeholder color # Scale/Translate/Rotate component about X,Y,Z axes if not placeholder: # If scale vector is all ones, no need to scale to identity. This operation is costly. if not all([1 if float(s) == 1.0 else 0 for s in scale]): solid.scale(scale, False) solid.transform([sum(x) for x in zip([X, Y, Z], trans)], rot) # Translate & Rotate else: solid.transform( [X, Y, Z - placeholder_offset], [0.0, 0.0, rotate * alpha]) # Move component to center origin logger.debug('Package LLH <X,Y>: <{0},{1}>'.format(str(xc), str(yc))) logger.debug('Component <Width,Height>: <{0},{1}>'.format( str(p.width), str(p.height))) logger.debug('Global <X,Y,Z>: <{0},{1},{2}>'.format( str(X), str(Y), str(Z))) logger.debug('Translate: ' + str(translation)) logger.debug('EDARotation: ' + str(rotation)) logger.debug('Rotation: ' + str(rotate)) logger.debug('CAD2EDATranslate: ' + str(trans)) logger.info('Adding FreeCAD object {0} for component {1}.'.format( solid.name, component)) return topchips, bottomchips, placeholders
logger.debug('CAD2EDATranslate: ' + str(trans)) logger.info('Adding FreeCAD object {0} for component {1}.'.format( solid.name, component)) return topchips, bottomchips, placeholders if __name__ == '__main__': global logger logger = setup_logger("CADAssembler.log", "main_log") try: if len(sys.argv) == 1: # For debugging/testing in FreeCAD console doc = 'Test' ext = 'step' elif len(sys.argv) < 6: exitwitherror('Missing command-line arguments.') elif len(sys.argv) > 6: for i in range(0, len(sys.argv)): logger.debug(sys.argv[i]) exitwitherror('Too many command-line arguments.') else: doc = sys.argv[2].replace('(', '_').replace(')', '_') ext = sys.argv[3] replace = sys.argv[4] interference_check = sys.argv[5].lower() if ext == 'stp': ext = 'step' t0 = time.clock() ## In time these should be replaced by passed-in parameters.