def build_extrude(part): """Build an extrude part. Parameters ---------- part : Returns ------- """ assert isinstance(part, part_3d.ExtrudePart) z0 = part.z0 deltaz = part.thickness doc = FreeCAD.ActiveDocument sketch = get_freecad_object(doc, part.fc_name) splitSketches = splitSketch(sketch) extParts = [] for sketch in splitSketches: extParts.append( extrudeBetween(sketch, z0, z0 + deltaz, name=part.label)) delete(sketch) doc.recompute() return genUnion(extParts, consumeInputs=True if not DBG_OUT else False)
def build_extrude(part): """Build an extrude part.""" assert part.directive == "extrude" z0 = part.z0 deltaz = part.thickness doc = FreeCAD.ActiveDocument sketch = doc.getObject(part.fc_name) splitSketches = splitSketch(sketch) extParts = [] for sketch in splitSketches: extParts.append(extrudeBetween(sketch, z0, z0 + deltaz, name=part.label)) delete(sketch) doc.recompute() return genUnion(extParts, consumeInputs=True if not DBG_OUT else False)
def makeSAG(sketch, zBot, zMid, zTop, tIn, tOut, offset=0.): doc = FreeCAD.ActiveDocument # First, compute the geometric quantities we will need: a = zTop - zMid # height of the top part b = tOut + tIn # width of one of the trianglular pieces of the top alpha = np.abs(np.arctan(a / np.float(b))) # lower angle of the top part c = a + 2 * offset # height of the top part including the offset # horizontal width of the trianglular part of the top after offset d = c / np.tan(alpha) # horizontal shift in the triangular part of the top after an offset f = offset / np.sin(alpha) sketchList = splitSketch(sketch) returnParts = [] for tempSketch in sketchList: # TODO: right now, if we try to taper the top of the SAG wire to a point, this # breaks, since the offset of topSketch is empty. We should detect and handle this. # For now, just make sure that the wire has a small flat top. botSketch = draftOffset(tempSketch, offset) # the base of the wire midSketch = draftOffset(tempSketch, f + d - tIn) # the base of the cap topSketch = draftOffset(tempSketch, -tIn + f) # the top of the cap delete(tempSketch) # remove the copied sketch part # Make the bottom wire: rectPartTemp = extrude(botSketch, zMid - zBot) rectPart = copy_move(rectPartTemp, moveVec=(0., 0., zBot - offset)) delete(rectPartTemp) # make the cap of the wire: topSketchTemp = copy_move(topSketch, moveVec=( 0., 0., zTop - zMid + 2 * offset)) capPartTemp = doc.addObject('Part::Loft', sketch.Name + '_cap') capPartTemp.Sections = [midSketch, topSketchTemp] capPartTemp.Solid = True doc.recompute() capPart = copy_move(capPartTemp, moveVec=(0., 0., zMid - offset)) delete(capPartTemp) delete(topSketchTemp) delete(topSketch) delete(midSketch) delete(botSketch) returnParts += [capPart, rectPart] returnPart = genUnion(returnParts, consumeInputs=True if not DBG_OUT else False) return returnPart
def initialize_lithography(info, opts, fillShells=True): doc = FreeCAD.ActiveDocument info.fillShells = fillShells # The lithography step requires some infrastructure to track things # throughout. info.lithoDict = { } # dictionary containing objects for the lithography step layers = info.lithoDict["layers"] = {} # Dictionary for containing the substrate. () indicates un-offset objects, # and subsequent tuples are offset by t_i for each index in the tuple. info.lithoDict["substrate"] = {(): []} # To start, we need to collect up all the lithography directives, and # organize them by layer_num and objectIDs within layers. base_substrate_parts = [] for part in opts["input_parts"]: # If this part is a litho step if isinstance(part, part_3d.LithographyPart): layer_num = part.layer_num # layer_num of this part # Add the layer_num to the layer dictionary: if layer_num not in layers: layers[layer_num] = {"objIDs": {}} layer = layers[layer_num] # Generate the base and thickness of the layer: layer_base = float(part.z0) layer_thickness = float(part.thickness) # All parts within a given layer number are required to have # identical thickness and base, so check that: if "base" in layer: assert layer_base == layer["base"] else: layer["base"] = layer_base if "thickness" in layer: assert layer_thickness == layer["thickness"] else: layer["thickness"] = layer_thickness # A given part references a base sketch. However, we need to split # the sketch here into possibly disjoint sub-sketches to work # with them: sketch = get_freecad_object(doc, part.fc_name) splitSketches = splitSketch(sketch) for mySplitSketch in splitSketches: objID = len(layer["objIDs"]) objDict = {} objDict["partName"] = part.fc_name objDict["sketch"] = mySplitSketch info.trash.append(mySplitSketch) layers[layer_num]["objIDs"][objID] = objDict # Add the base substrate to the appropriate dictionary base_substrate_parts += part.litho_base # Get rid of any duplicates: base_substrate_parts = list(set(base_substrate_parts)) # Now convert the part names for the substrate into 3D freeCAD objects, which # should have already been rendered. for base_substrate in base_substrate_parts: try: built_part_name = opts["built_part_names"][base_substrate.label] except: raise KeyError(f"No substrate built for '{base_substrate.label}'") info.lithoDict["substrate"][()] += [ get_freecad_object(doc, built_part_name) ] # ~ import sys # ~ sys.stderr.write(">>> litdic " + str(info.lithoDict) + "\n") # Now that we have ordered the primitives, we need to compute a few # aux quantities that we will need. First, we compute the total bounding # box of the lithography procedure: bottom = min(layer["base"] for layer in layers.values()) totalThickness = sum(layer["thickness"] for layer in layers.values()) assert (len(info.lithoDict["substrate"][()]) > 0 ) # Otherwise, we don't have a reference for the lateral BB substrateUnion = genUnion(info.lithoDict["substrate"][()], consumeInputs=False) # total substrate BB = list(getBB(substrateUnion)) # bounding box BB[4] = min([bottom, BB[4]]) BB[5] = max([BB[5] + totalThickness, bottom + totalThickness]) BB = tuple(BB) constructionZone = makeBB(BB) # box that encompases the whole domain. info.lithoDict["boundingBox"] = [BB, constructionZone] delete(substrateUnion) # not needed for next steps delete(constructionZone) # not needed for next steps ... WHY? # Next, we add two prisms for each sketch. The first, which we denote "B", # is bounded by the base from the bottom and the layer thickness on the top. # These serve as "stencils" that would be the deposited shape if no other. # objects got in the way. The second set of prisms, denoted "C", covers the # base of the layer to the top of the entire domain box. This is used for # forming the volumes occupied when substrate objects are offset and # checking for overlaps. for layer_num in layers.keys(): base = layers[layer_num]["base"] thickness = layers[layer_num]["thickness"] for objID in layers[layer_num]["objIDs"]: sketch = layers[layer_num]["objIDs"][objID]["sketch"] B = extrudeBetween(sketch, base, base + thickness) C = extrudeBetween(sketch, base, BB[5]) layers[layer_num]["objIDs"][objID]["B"] = B layers[layer_num]["objIDs"][objID]["C"] = C info.trash.append(B) info.trash.append(C) # In addition, add a hook for the HDict, which will contain the "H" # constructions for this object, but offset to thicknesses of various # layers, according to the keys. layers[layer_num]["objIDs"][objID]["HDict"] = {}
def makeSAG(sketch, zBot, zMid, zTop, tIn, tOut, offset=0.0): doc = FreeCAD.ActiveDocument assert zBot <= zMid assert zMid <= zTop # First, compute the geometric quantities we will need: a = zTop - zMid # height of the top part b = tOut + tIn # width of one of the triangular pieces of the top # if there is no slope to the roof, it's a different geometry which we don't handle: assert not np.isclose( b, 0 ), "Either overshoot or inner displacement values need to be non-zero for SAG \ (otherwise use extrude)" # This also means there would be no slope to the roof: assert not np.isclose( a, 0 ), "Top and middle z values need to be different for SAG (otherwise use extrude)." alpha = np.arctan(a / b) # lower angle of the top part c = a + 2 * offset # height of the top part including the offset # horizontal width of the trianglular part of the top after offset d = c / np.tan(alpha) # horizontal shift in the triangular part of the top after an offset f = offset * (1 - np.cos(alpha)) / np.sin(alpha) sketchList = splitSketch(sketch) returnParts = [] for tempSketch in sketchList: botSketch = draftOffset(tempSketch, offset) # the base of the wire midSketch = draftOffset(tempSketch, f + d - tIn) # the base of the cap top_offset = f - tIn topSketch = draftOffset(tempSketch, top_offset) # the top of the cap # If topSketch has been shrunk exactly to a line or a point, relax the offset to 5E-5. Any closer and FreeCAD seems to suffer from numerical errors if topSketch.Shape.Area == 0: top_offset -= 5e-5 delete(topSketch) topSketch = draftOffset(tempSketch, top_offset) delete(tempSketch) # remove the copied sketch part # Make the bottom wire: if zMid - zBot != 0: rectPartTemp = extrude(botSketch, zMid - zBot) rectPart = copy_move(rectPartTemp, moveVec=(0.0, 0.0, zBot - offset)) delete(rectPartTemp) else: rectPart = None # make the cap of the wire: topSketchTemp = copy_move(topSketch, moveVec=(0.0, 0.0, zTop - zMid + 2 * offset)) capPartTemp = doc.addObject("Part::Loft", f"{sketch.Name}_cap") capPartTemp.Sections = [midSketch, topSketchTemp] capPartTemp.Solid = True doc.recompute() capPart = copy_move(capPartTemp, moveVec=(0.0, 0.0, zMid - offset)) delete(capPartTemp) delete(topSketchTemp) delete(topSketch) delete(midSketch) delete(botSketch) returnPart = (genUnion([capPart, rectPart], consumeInputs=True if not DBG_OUT else False) if rectPart is not None else capPart) returnParts.append(returnPart) return returnParts