def _gen_offset(self, obj, offsetVal): """Generates an offset non-destructively.""" # First, we need to check if the object needs special treatment: treatment = 'standard' for partName in self.model.modelDict['3DParts'].keys(): partDict = self.model.modelDict['3DParts'][partName] if obj.Name in partDict['fileNames']: treatment = partDict['directive'] break if treatment == 'extrude' or treatment == 'lithography': treatment = 'standard' if treatment == 'standard': # Apparently the offset function is buggy for very small offsets... if offsetVal < 1e-5: offsetDupe = copy(obj) else: offset = self.doc.addObject("Part::Offset") offset.Source = obj offset.Value = offsetVal offset.Mode = 0 offset.Join = 2 self.doc.recompute() offsetDupe = copy(offset) self.doc.recompute() delete(offset) elif treatment == 'wire': offsetDupe = self._build_wire(partName, offset=offsetVal)[0] elif treatment == 'wireShell': offsetDupe = self._build_wire_shell(partName, offset=offsetVal)[0] elif treatment == 'SAG': offsetDupe = self._build_SAG(partName, offset=offsetVal)[0] self.doc.recompute() return offsetDupe
def _gen_G(self, layerNum, objID): ''' Generate the gate deposition for a given layerNum and objID. ''' if 'G' not in self.lithoDict['layers'][layerNum]['objIDs'][objID]: if () not in self.lithoDict['layers'][layerNum]['objIDs'][objID][ 'HDict']: self.lithoDict['layers'][layerNum]['objIDs'][objID]['HDict'][( )] = self._H_offset(layerNum, objID) H = genUnion( self.lithoDict['layers'][layerNum]['objIDs'][objID]['HDict'][( )], consumeInputs=False) self.trash += [H] if self.fillShells: G = copy(H) else: U = self._gen_U(layerNum, objID) G = subtract(H, U) delete(U) self.lithoDict['layers'][layerNum]['objIDs'][objID]['G'] = G G = self.lithoDict['layers'][layerNum]['objIDs'][objID]['G'] partName = self.lithoDict['layers'][layerNum]['objIDs'][objID][ 'partName'] G.Label = partName return G
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(rectPartTemp, moveVec=(0., 0., zBot - offset)) delete(rectPartTemp) # make the cap of the wire: topSketchTemp = copy(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(capPartTemp, moveVec=(0., 0., zMid - offset)) delete(capPartTemp) delete(topSketchTemp) delete(topSketch) delete(midSketch) delete(botSketch) returnParts += [capPart, rectPart] returnPart = genUnion(returnParts, consumeInputs=True) return returnPart
def buildWire(sketch, zBottom, width, faceOverride=None, offset=0.0): """Given a line segment, build a nanowire of given cross-sectional width with a bottom location at zBottom. Offset produces an offset with a specified offset. """ doc = FreeCAD.ActiveDocument if faceOverride is None: face = makeHexFace(sketch, zBottom - offset, width + 2 * offset) else: face = faceOverride sketchForSweep = extendSketch(sketch, offset) mySweepTemp = doc.addObject('Part::Sweep', sketch.Name + '_wire') mySweepTemp.Sections = [face] mySweepTemp.Spine = sketchForSweep mySweepTemp.Solid = True doc.recompute() mySweep = copy(mySweepTemp) deepRemove(mySweepTemp) return mySweep
def draftOffset(inputSketch, t, tol=1e-8): ''' Attempt to offset the draft figure by a thickness t. Positive t is an inflation, while negative t is a deflation. tol sets how strict we should be when checking if the offset worked. ''' from qmt.freecad import extrude, copy, delete if t == 0.: return copy(inputSketch) deltaT = np.abs(t) offsetVec1 = FreeCAD.Vector(-deltaT, -deltaT, 0.) offsetVec2 = FreeCAD.Vector(deltaT, deltaT, 0.) offset0 = copy(inputSketch) offset1 = Draft.offset(inputSketch, offsetVec1, copy=True) offset2 = Draft.offset(inputSketch, offsetVec2, copy=True) solid0 = extrude(offset0, 10.0) solid1 = extrude(offset1, 10.0) solid2 = extrude(offset2, 10.0) # Compute the volumes of these solids: V0 = solid0.Shape.Volume try: V1 = solid1.Shape.Volume except BaseException: V1 = None try: V2 = solid2.Shape.Volume except BaseException: V2 = None # If everything worked properly, these should either be ordered as # V1<V0<V2 or V2<V0<V1: if V2 > V0 and V0 > V1: bigSketch = offset2 littleSketch = offset1 elif V1 > V0 and V0 > V2: bigSketch = offset1 littleSketch = offset2 elif V2 > V1 and V1 > V0: bigSketch = offset2 littleSketch = None # If we aren't in correct case, we still might be able to salvage things # for certain values of t: elif V1 > V2 and V2 > V0: bigSketch = offset1 littleSketch = None elif V2 < V1 and V1 < V0: bigSketch = None littleSketch = offset2 elif V1 < V2 and V2 < V0: bigSketch = None littleSketch = offset1 else: bigSketch = None littleSketch = None delete(solid0) delete(solid1) delete(solid2) if t < 0 and littleSketch is not None: returnSketch = copy(littleSketch) elif t > 0 and bigSketch is not None: returnSketch = copy(bigSketch) elif abs(t) < tol: returnSketch = copy(inputSketch) else: raise ValueError('Failed to offset the sketch ' + str(inputSketch.Name) + ' by amount ' + str(t)) # # now that we have the three solids, we need to figure out which is bigger # # and which is smaller. # diff10 = subtract(solid1,solid0) # diff20 = subtract(solid2,solid0) # numVerts10 = len(diff10.Shape.Vertexes) # numVerts20 = len(diff20.Shape.Vertexes) # if numVerts10 > 0 and numVerts20 == 0: # positiveOffsetIndex = 1 # elif numVerts10 == 0 and numVerts20 > 0 : # positiveOffsetIndex = 2 # else: # raise ValueError('draftOffset has failed to give a non-empty shape!') # delete(solid0) # delete(solid1) # delete(solid2) # delete(diff10) # delete(diff20) # if t > 0: # if positiveOffsetIndex == 1: # returnSketch = copy(offset1) # else: # returnSketch = copy(offset2) # elif t<0: # if positiveOffsetIndex == 1: # returnSketch = copy(offset2) # else: # returnSketch = copy(offset1) delete(offset0) delete(offset1) delete(offset2) return returnSketch
def buildAlShell(sketch, zBottom, width, verts, thickness, depoZone=None, etchZone=None, offset=0.0): """Builds a shell on a nanowire parameterized by sketch, zBottom, and width. Here, verts describes the vertices that are covered, and thickness describes the thickness of the shell. depoZone, if given, is extruded and intersected with the shell (for an etch). Note that offset here *is not* a real offset - for simplicity we keep this a thin shell that lies cleanly on top of the bigger wire offset. There's no need to include the bottom portion since that's already taken up by the wire. """ lineSegments = findSegments(sketch)[0] x0, y0, z0 = lineSegments[0] x1, y1, z1 = lineSegments[1] dx = x1 - x0 dy = y1 - y0 rAxis = np.array([-dy, dx, 0]) # axis perpendicular to the wire in the xy plane rAxis /= np.sqrt(np.sum(rAxis**2)) zAxis = np.array([0, 0, 1.]) doc = FreeCAD.ActiveDocument shellList = [] for vert in verts: # Make the original wire (including an offset if applicable) originalWire = buildWire(sketch, zBottom, width, offset=offset) # Now make the shifted wire: angle = vert * np.pi / 3. dirVec = rAxis * np.cos(angle) + zAxis * np.sin(angle) shiftVec = (thickness) * dirVec transVec = FreeCAD.Vector(tuple(shiftVec)) face = makeHexFace(sketch, zBottom - offset, width + 2 * offset) # make the bigger face shiftedFace = Draft.move(face, transVec, copy=False) extendedSketch = extendSketch(sketch, offset) # The shell offset is handled manually since we are using faceOverride to # input a shifted starting face: shiftedWire = buildWire(extendedSketch, zBottom, width, faceOverride=shiftedFace) delete(extendedSketch) shellCut = doc.addObject("Part::Cut", sketch.Name + "_cut_" + str(vert)) shellCut.Base = shiftedWire shellCut.Tool = originalWire doc.recompute() shell = Draft.move(shellCut, FreeCAD.Vector(0., 0., 0.), copy=True) doc.recompute() delete(shellCut) delete(originalWire) delete(shiftedWire) shellList.append(shell) if len(shellList) > 1: coatingUnion = doc.addObject("Part::MultiFuse", sketch.Name + "_coating") coatingUnion.Shapes = shellList doc.recompute() coatingUnionClone = copy(coatingUnion) doc.removeObject(coatingUnion.Name) for shell in shellList: doc.removeObject(shell.Name) elif len(shellList) == 1: coatingUnionClone = shellList[0] else: raise NameError( 'Trying to build an empty Al shell. If no shell is desired, omit the AlVerts key from the json.' ) if (depoZone is None) and (etchZone is None): return coatingUnionClone elif depoZone is not None: coatingBB = getBB(coatingUnionClone) zMin = coatingBB[4] zMax = coatingBB[5] depoVol = extrudeBetween(depoZone, zMin, zMax) etchedCoatingUnionClone = intersect([depoVol, coatingUnionClone], consumeInputs=True) return etchedCoatingUnionClone else: # etchZone instead coatingBB = getBB(coatingUnionClone) zMin = coatingBB[4] zMax = coatingBB[5] etchVol = extrudeBetween(etchZone, zMin, zMax) etchedCoatingUnionClone = subtract(coatingUnionClone, etchVol, consumeInputs=True) return etchedCoatingUnionClone