def create_basic_pcb(self): pcb = meta.Part("PCB") pcb.create_blank() pcb.fc.Shape = meta.make_box(self.width, self.height, self.boardthick) pcb.fc.ViewObject.ShapeColor = PCBColors.BOARD logger.info('Board dimensions: h={0} w={1} lay={2}'.format( self.height, self.width, self.layers))
def board_border(self, border): """ Generate the base structure of the PCB. """ pcb = meta.Sketch("PCB_Border") pcb.create_blank() pcb.set_sketch_plane('XY', 0.0) pcb.sketch_from_coords(border) pad = pcb.pad_sketch(self.boardthick, 0, 0, 0.0, 0, None) pcb_part = meta.Part("PCB") pcb_part.create_blank("part") meta.copy_shape(pad, pcb_part, True) color_array = [] for i in range(len(pcb_part.fc.Shape.Faces)): color_array.append(PCBColors.BOARD) pcb_part.set_colors(color_array)
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
def replace(aradata): module_file = aradata["template_cadpath"] check_file_exists(module_file) logger.info("Ara template module: {0}".format(module_file)) # Create new Document and Assembly object to store template module information. module = meta.Assembly(module_file.rsplit('.')[0]) module.doc = meta.Document("AraModule") module.doc.new() (module.cadpath, module.format) = module.doc.load(module_file) # Loop through assembly and create a Part object for each element. Add these parts to the list of objects in doc. module.get_parts() module.doc.objects = module.parts # We are only interested in replacing parts that are not stock components. for rep_part in [ p for p in aradata.items() if p[1] != "stock" and p[0] != "template_cadpath" ]: assembly_file = rep_part[1] # Path to component module_label = rep_part[ 0] # FreeCAD internal label of component in Ara template file. check_file_exists(assembly_file) logger.info("Replacing part {0}...".format(assembly_file)) logger.info("FreeCAD part label: {0}".format(module_label)) # Create new Document and Part/Assembly object for the replacing part we are about to load. if module_label == "PCB": replace_obj = meta.Assembly(module_label) else: replace_obj = meta.Part(module_label) replace_obj.doc = meta.Document("ReplacingObject") replace_obj.doc.new() try: (replace_obj.cadpath, replace_obj.format) = replace_obj.doc.load(assembly_file) except IOError: # Already checked for existence, so it means file is already open. Grab by label. logger.debug( "FreeCAD file {0} already open! Grabbing existing document.". format(assembly_file)) # Grab object in module that we wish to replace, along with its bounding box in its assembled position. module_prt = module.grab_object_by_name(module_label) module_prt_bb = module_prt.get_bounding_box() # The only assembly that should be replaced is that of the PCB. if replace_obj.of_type() == "assembly": # Grab all objects in the assembly that will need to be translated. replace_obj.get_parts() replace_obj.doc.objects = replace_obj.parts replace_prt = replace_obj.grab_object_by_name( "PCB") # This is the element whose bounding box we want. else: # Otherwise, the document only has the one part that will be copied. replace_obj.doc.objects.append(replace_obj) replace_obj.set_object(meta.get_active_object(replace_obj.doc)) replace_prt = replace_obj replace_prt_bb = replace_prt.get_bounding_box() # Grab list of parts to copy into module doc, provided it is a part or mesh part (no sketches). objs = [ x for x in replace_obj.doc.objects if (x.parent() == "Part::Feature" or x.parent() == "Mesh::Feature") ] module_objs_before = len(module.doc.objects) for obj in objs: new_obj = module.doc.copy_object( obj) # Copy object from replace doc to module doc new_obj.set_colors(obj.get_colors( )) # Color is lost in copying, so add color back to object. # Now copied objects are in module doc, but at the coordinates as they appear in replace doc. module_objs = module.doc.get_objects(module_objs_before, len(module.doc.objects)) for obj in module_objs: logger.info("Translating object: {0}".format(obj.name)) logger.debug("starting placement: {0}".format(obj.fc.Placement)) logger.debug("deltaX: {0}".format( str(module_prt_bb.XMin - replace_prt_bb.XMin))) logger.debug("deltaY: {0}".format( str(module_prt_bb.YMin - replace_prt_bb.YMin))) logger.debug("deltaZ: {0}".format( str(module_prt_bb.ZMin - replace_prt_bb.ZMin))) logger.debug("ending placement: {0}".format(obj.fc.Placement)) obj.translate([ module_prt_bb.XMin - replace_prt_bb.XMin, module_prt_bb.YMin - replace_prt_bb.YMin, module_prt_bb.ZMin - replace_prt_bb.ZMin ]) # Update assembly list, as parts have changed. module_prt.delete() module.parts = [] module.get_parts() module.doc.objects = module.parts # Close current "replace" part and delete it's corresponding objects. replace_obj.doc.close() del replace_obj del replace_prt # Export to FreeCAD and STEP/STL meta.set_active_doc(module.doc) meta.export("FC", []) if len([x for x in module.doc.objects if x.parent() == "Mesh::Feature"]) > 0: meta.convert_parts_to_meshes(meta.get_all_objects(), logger.name) meta.export("STL", meta.get_all_objects()) else: meta.fuse_components(meta.get_all_objects(), module.doc.Name) # For cad.js meta.export("STEP", [meta.get_active_object()])