def gen_U(info, layerNum, objID): """For a given layerNum and objID, compute the quantity: ```latex U_{n,i}(t) = (\cup_{m<n;j} G_{m,j}) \cup (\cup_{k} A_k), ``` where the inner union terms are not included if their intersection with B_i is empty. """ layers = info.lithoDict["layers"] B = layers[layerNum]["objIDs"][objID]["B"] # B prism for this layer & objID GList = [] for m in layers.keys(): if m < layerNum: # then this is a lower layer for j in layers[m].keys(): if "G" not in layers[layerNum]["objIDs"][objID]: gen_G(info, m, j) G = layers[layerNum]["objIDs"][objID]["G"] if checkOverlap([B, G]): GList.append(G) AList = [] for A in info.lithoDict["substrate"][()]: if checkOverlap([B, A]): AList.append(A) unionList = GList + AList unionObj = genUnion(unionList, consumeInputs=False) return unionObj
def screened_A_UnionList(info, opts, obj, t, ti, offsetTuple, checkOffsetTuple): """Form the screened union list of obj with the substrate A that has been offset according to offsetTuple. """ logging.debug(">>> %s (%s)", obj.Name, obj.Label) # First, we need to see if we have built the objects before: if checkOffsetTuple not in info.lithoDict["substrate"]: info.lithoDict["substrate"][checkOffsetTuple] = [] for A in info.lithoDict["substrate"][()]: AObj = gen_offset(opts, A, t) info.trash.append(AObj) info.lithoDict["substrate"][checkOffsetTuple].append(AObj) if offsetTuple not in info.lithoDict["substrate"]: info.lithoDict["substrate"][offsetTuple] = [] for A in info.lithoDict["substrate"][()]: AObj = gen_offset(opts, A, t + ti) info.trash.append(AObj) info.lithoDict["substrate"][offsetTuple].append(AObj) returnList = [] for i, ACheck in enumerate(info.lithoDict["substrate"][checkOffsetTuple]): if checkOverlap([obj, ACheck]): returnList.append(info.lithoDict["substrate"][offsetTuple][i]) logging.debug("<<< %s", [o.Name + " (" + o.Label + ")" for o in returnList]) return returnList
def screened_H_union_list(info, opts, obj, m, j, offsetTuple, checkOffsetTuple): """Foremd the "screened union list" of obj with the layer m, objID j H object that has been offset according to offsetTuple. The screened union list is defined by checking first whether the object intersects with the components of the checkOffset version of the H object. Then, for each component that would intersect, we return the a list of the offsetTuple version of the object. """ # First, we need to check to see if we need to compute either of the # underlying H obj lists: HDict = info.lithoDict['layers'][m]['objIDs'][j]['HDict'] # HDict stores a collection of H object component lists for each (layerNum,objID) # pair. The index of this dictionary is a tuple: () indicates no # offset, while other indices indicate an offset by summing the thicknesses # from corresponding layers. if checkOffsetTuple not in HDict: # If we haven't computed this yet HDict[checkOffsetTuple] = H_offset( info, opts, m, j, tList=list(checkOffsetTuple)) # list of H parts info.trash += HDict[checkOffsetTuple] if offsetTuple not in HDict: # If we haven't computed this yet HDict[offsetTuple] = H_offset( info, opts, m, j, tList=list(offsetTuple)) # list of H parts info.trash += HDict[offsetTuple] HObjCheckList = HDict[checkOffsetTuple] HObjList = HDict[offsetTuple] returnList = [] for i, HObjPart in enumerate(HObjCheckList): if checkOverlap([obj, HObjPart]): # if we need to include an overlap returnList.append(HObjList[i]) return returnList
def screened_H_union_list(info, opts, obj, m, j, offsetTuple, checkOffsetTuple): """Form the screened union list of obj with the layer m, objID j H object that has been offset according to offsetTuple. The screened union list is defined by checking first whether the object intersects with the components of the checkOffset version of the H object. Then, for each component that would intersect, we return the a list of the offsetTuple version of the object. """ logging.debug(">>> %s (%s)", obj.Name, obj.Label) # First, we need to check to see if we need to compute either of the # underlying H obj lists: HDict = info.lithoDict["layers"][m]["objIDs"][j]["HDict"] # HDict stores a collection of H object component lists for each (layerNum,objID) # pair. The index of this dictionary is a tuple: () indicates no # offset, while other indices indicate an offset by summing the thicknesses # from corresponding layers. if checkOffsetTuple not in HDict: # If we haven't computed this yet HDict[checkOffsetTuple] = H_offset( info, opts, m, j, tList=list(checkOffsetTuple) ) # list of H parts info.trash += HDict[checkOffsetTuple] if offsetTuple not in HDict: # If we haven't computed this yet HDict[offsetTuple] = H_offset( info, opts, m, j, tList=list(offsetTuple) ) # list of H parts info.trash += HDict[offsetTuple] HObjCheckList = HDict[checkOffsetTuple] HObjList = HDict[offsetTuple] returnList = [] for i, HObjPart in enumerate(HObjCheckList): if checkOverlap([obj, HObjPart]): # if we need to include an overlap returnList.append(HObjList[i]) # fix for multilayer intersections: make sure we really check all overlaps for i, HObjPart in enumerate(HObjList): if checkOverlap([obj, HObjPart]): # if we need to include an overlap returnList.append(HObjList[i]) logging.debug("<<< %s", [o.Name + " (" + o.Label + ")" for o in returnList]) return returnList
def test_overlapping_parts(datadir): """ This tests that two parts that were generated from lithography over a wire register as intersecting. Due to an OCC bug, FC 0.18 at one point would claim that these didn't intersect, resulting in geometry and meshing errors. """ path = os.path.join(datadir, "intersection_test.FCStd") doc = FreeCAD.newDocument("instance") FreeCAD.setActiveDocument("instance") doc.load(path) shape_1 = doc.Objects[0] shape_2 = doc.Objects[1] assert checkOverlap([shape_1, shape_2]) FreeCAD.closeDocument(doc.Name)
def screened_A_UnionList(info, opts, obj, t, ti, offsetTuple, checkOffsetTuple): """Form the screened union list of obj with the substrate A that has been offset according to offsetTuple. Parameters ---------- info : opts : dict Options dict in the QMT Geometry3D.__init__ input format. obj : FreeCAD.App.Document A FreeCAD object. t : ti : offsetTuple : checkOffsetTuple : Returns ------- """ logging.debug(">>> %s (%s)", obj.Name, obj.Label) # First, we need to see if we have built the objects before: if checkOffsetTuple not in info.lithoDict["substrate"]: info.lithoDict["substrate"][checkOffsetTuple] = [] for A in info.lithoDict["substrate"][()]: AObj = gen_offset(opts, A, t) info.trash.append(AObj) info.lithoDict["substrate"][checkOffsetTuple].append(AObj) if offsetTuple not in info.lithoDict["substrate"]: info.lithoDict["substrate"][offsetTuple] = [] for A in info.lithoDict["substrate"][()]: AObj = gen_offset(opts, A, t + ti) info.trash.append(AObj) info.lithoDict["substrate"][offsetTuple].append(AObj) returnList = [] for i, ACheck in enumerate(info.lithoDict["substrate"][checkOffsetTuple]): if checkOverlap([obj, ACheck]): returnList.append(info.lithoDict["substrate"][offsetTuple][i]) logging.debug("<<< %s", [f"{o.Name} ({o.Label})" for o in returnList]) return returnList
def screened_A_UnionList(info, opts, obj, t, ti, offsetTuple, checkOffsetTuple): """Foremd the "screened union list" of obj with the substrate A that has been offset according to offsetTuple. """ # First, we need to see if we have built the objects before: if checkOffsetTuple not in info.lithoDict['substrate']: info.lithoDict['substrate'][checkOffsetTuple] = [] for A in info.lithoDict['substrate'][()]: AObj = gen_offset(opts, A, t) info.trash.append(AObj) info.lithoDict['substrate'][checkOffsetTuple].append(AObj) if offsetTuple not in info.lithoDict['substrate']: info.lithoDict['substrate'][offsetTuple] = [] for A in info.lithoDict['substrate'][()]: AObj = gen_offset(opts, A, t + ti) info.trash.append(AObj) info.lithoDict['substrate'][offsetTuple].append(AObj) returnList = [] for i, ACheck in enumerate(info.lithoDict['substrate'][checkOffsetTuple]): if checkOverlap([obj, ACheck]): returnList.append(info.lithoDict['substrate'][offsetTuple][i]) return returnList
def build(opts): """Build the 3D geometry in FreeCAD. Parameters ---------- opts : dict Options dict in the QMT Geometry3D.__init__ input format. Options dict in the QMT Geometry3D.__init__ input format. Returns ------- Geo3DData object. """ doc = FreeCAD.ActiveDocument geo = Geo3DData(opts.get("lunit", None)) # Schedule for deletion all objects not explicitly selected by the user input_parts_names = [] for part in opts["input_parts"]: if part.fc_name is None: obj_list = doc.getObjectsByLabel(part.label) if len(obj_list) != 1: msg = f"Part labeled {part.label} returned object list {obj_list}" raise KeyError(msg) fc_name = obj_list[0].Name part.fc_name = fc_name else: fc_name = part.fc_name input_parts_names += [fc_name] blacklist = [] for obj in doc.Objects: if (obj.Name not in input_parts_names) and (obj.TypeId != "Spreadsheet::Sheet"): blacklist.append(obj) # Update the model parameters if "params" in opts: # Extend params dictionary to original parts schema fcdict = { key: (value, "freeCAD") for (key, value) in opts["params"].items() } set_params(doc, fcdict) doc.recompute( ) # recompute here to update any sketches that change due to parameters if "built_part_names" not in opts: opts["built_part_names"] = {} if "serial_stp_parts" not in opts: opts["serial_stp_parts"] = {} # Build the parts info_holder = DummyInfo() # temporary workaround to support old litho code built_parts = [] for input_part in opts["input_parts"]: if isinstance(input_part, part_3d.ExtrudePart): part = build_extrude(input_part) elif isinstance(input_part, part_3d.SAGPart): part = build_sag(input_part) elif isinstance(input_part, part_3d.WirePart): part = build_wire(input_part) elif isinstance(input_part, part_3d.WireShellPart): part = build_wire_shell(input_part) elif isinstance(input_part, part_3d.LithographyPart): part = build_lithography(input_part, opts, info_holder) elif isinstance(input_part, part_3d.Geo3DPart): part = build_pass(input_part) else: raise ValueError( f"{input_part} is not a recognized Geo3DPart type") assert part is not None doc.recompute() built_parts.append(part) # needed for litho steps opts["built_part_names"][input_part.label] = part.Name # Cleanup if not DBG_OUT: collect_garbage(info_holder) for obj in blacklist: delete(obj) doc.recompute() # Subtraction (removes the need for subtractlists) for i, (input_part, part) in enumerate(zip(opts["input_parts"], built_parts)): if input_part.virtual: continue for other_input_part, other_part in zip(opts["input_parts"][0:i], built_parts[0:i]): if other_input_part.virtual: continue if checkOverlap([part, other_part]): cut = subtract( part, copy_move(other_part), consumeInputs=True if not DBG_OUT else False, ) simple_copy = doc.addObject("Part::Feature", "simple_copy") # no solid, just its shape (can be disjoint) simple_copy.Shape = cut.Shape delete(cut) part = simple_copy built_parts[i] = simple_copy # Update names and store the built parts built_parts_dict = {} # dict for cross sections for input_part, built_part in zip(opts["input_parts"], built_parts): built_part.Label = input_part.label # here it's collision free output_part = deepcopy(input_part) output_part.serial_stp = store_serial([built_part], exportCAD, "stp") output_part.serial_stl = store_serial([built_part], exportMeshed, "stl") output_part.built_fc_name = built_part.Name geo.add_part(output_part.label, output_part) # dict for cross sections built_parts_dict[input_part.label] = built_part # Build cross sections: for xsec_name in opts["xsec_dict"]: axis = opts["xsec_dict"][xsec_name]["axis"] distance = opts["xsec_dict"][xsec_name]["distance"] polygons = buildCrossSection(xsec_name, axis, distance, built_parts_dict) geo.add_xsec(xsec_name, polygons, axis=axis, distance=distance) # Store the FreeCAD document geo.set_data(doc) return geo
def build(opts): '''Build the 3D geometry in FreeCAD. :param dict opts: Options dict in the QMT Geometry3D.__init__ input format. :return geo: Geo3DData object with the built objects. ''' doc = FreeCAD.ActiveDocument geo = Geo3DData() # Schedule for deletion all objects not explicitly selected by the user input_parts_names = [part.fc_name for part in opts['input_parts']] blacklist = [] for obj in doc.Objects: if (obj.Name not in input_parts_names) and (obj.TypeId != 'Spreadsheet::Sheet'): blacklist.append(obj) # Update the model parameters if 'params' in opts: # Extend params dictionary to original parts schema fcdict = { key: (value, 'freeCAD') for (key, value) in opts['params'].items() } set_params(doc, fcdict) if 'built_part_names' not in opts: opts['built_part_names'] = {} if 'serial_stp_parts' not in opts: opts['serial_stp_parts'] = {} # Build the parts info_holder = DummyInfo() # temporary workaround to support old litho code built_parts = [] for input_part in opts['input_parts']: if input_part.directive == 'extrude': part = build_extrude(input_part) elif input_part.directive == 'SAG': part = build_sag(input_part) elif input_part.directive == 'wire': part = build_wire(input_part) elif input_part.directive == 'wire_shell': part = build_wire_shell(input_part) elif input_part.directive == 'lithography': part = build_lithography(input_part, opts, info_holder) elif input_part.directive == '3d_shape': part = build_pass(input_part) else: raise ValueError('Directive ' + input_part.directive + ' is not a recognized directive type.') assert part is not None doc.recompute() built_parts.append(part) opts['built_part_names'][ input_part.label] = part.Name # needed for litho steps # Cleanup collect_garbage(info_holder) for obj in blacklist: delete(obj) doc.recompute() # Subtraction (removes the need for subtractlists) for i, part in enumerate(built_parts): for other_part in built_parts[0:i]: if checkOverlap([part, other_part]): cut = subtract(part, copy_move(other_part), consumeInputs=True) simple_copy = doc.addObject('Part::Feature', "simple_copy") simple_copy.Shape = cut.Shape # no solid, just its shape (can be disjoint) delete(cut) part = simple_copy built_parts[i] = simple_copy # Update names and store the built parts for input_part, built_part in zip(opts['input_parts'], built_parts): built_part.Label = input_part.label # here it's collision free input_part.serial_stp = store_serial([built_part], exportCAD, 'stp') input_part.built_fc_name = built_part.Name geo.add_part(input_part.label, input_part) # Store the FreeCAD document geo.set_data('fcdoc', doc) return geo